From f53931bd8b5a75c4e6d08aebc2b05bd3f1cb3337 Mon Sep 17 00:00:00 2001 From: "Tony Lin(Online Monetization)" Date: Tue, 10 Apr 2018 12:50:04 -0700 Subject: [PATCH 1/4] Allow users to add more attachments to report a bug --- .../android/shaky/FeedbackActivity.java | 23 +- .../linkedin/android/shaky/FormFragment.java | 224 ++++++++++++++++-- .../linkedin/android/shaky/ShakeDelegate.java | 7 + .../com/linkedin/android/shaky/Shaky.java | 13 +- .../com/linkedin/android/shaky/Utils.java | 129 +++++++++- shaky/src/main/res/layout-v17/shaky_form.xml | 16 +- shaky/src/main/res/layout-v21/shaky_form.xml | 16 +- .../main/res/layout/shaky_attachment_view.xml | 48 ++++ shaky/src/main/res/layout/shaky_form.xml | 14 +- shaky/src/main/res/values/shaky_strings.xml | 3 + 10 files changed, 456 insertions(+), 37 deletions(-) create mode 100644 shaky/src/main/res/layout/shaky_attachment_view.xml diff --git a/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java b/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java index 15063e7..ed88f71 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java @@ -21,6 +21,7 @@ import android.content.IntentFilter; import android.net.Uri; import android.os.Bundle; +import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; @@ -29,6 +30,8 @@ import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; +import java.util.ArrayList; + /** * The main activity used capture and send feedback. */ @@ -40,18 +43,23 @@ public class FeedbackActivity extends AppCompatActivity { static final String MESSAGE = "message"; static final String TITLE = "title"; static final String USER_DATA = "userData"; + static final String EXTRA_ATTACHMENTS = "extraAttachments"; + static final String ADD_ATTACHMENT = "addAttachment"; private Uri imageUri; private @FeedbackItem.FeedbackType int feedbackType; private Bundle userData; + private boolean addAttachmentShown; @NonNull public static Intent newIntent(@NonNull Context context, @Nullable Uri screenshotUri, - @Nullable Bundle userData) { + @Nullable Bundle userData, + boolean addAttachmentShown) { Intent intent = new Intent(context, FeedbackActivity.class); intent.putExtra(SCREENSHOT_URI, screenshotUri); intent.putExtra(USER_DATA, userData); + intent.putExtra(ADD_ATTACHMENT, addAttachmentShown); return intent; } @@ -63,7 +71,7 @@ public void onCreate(Bundle savedInstanceState) { imageUri = getIntent().getParcelableExtra(SCREENSHOT_URI); userData = getIntent().getBundleExtra(USER_DATA); - + addAttachmentShown = getIntent().getBooleanExtra(ADD_ATTACHMENT, false); if (savedInstanceState == null) { getSupportFragmentManager() .beginTransaction() @@ -110,7 +118,7 @@ private void changeToFragment(@NonNull Fragment fragment) { private void startFormFragment(@FeedbackItem.FeedbackType int feedbackType) { String title = getString(getTitleResId(feedbackType)); String hint = getString(getHintResId(feedbackType)); - changeToFragment(FormFragment.newInstance(title, hint, imageUri)); + changeToFragment(FormFragment.newInstance(title, hint, imageUri, addAttachmentShown)); } /** @@ -142,18 +150,23 @@ public void onReceive(Context context, Intent intent) { } else if (DrawFragment.ACTION_DRAWING_COMPLETE.equals(intent.getAction())) { onBackPressed(); } else if (FormFragment.ACTION_SUBMIT_FEEDBACK.equals(intent.getAction())) { - submitFeedbackIntent(intent.getStringExtra(FormFragment.EXTRA_USER_MESSAGE)); + submitFeedbackIntent(intent.getStringExtra(FormFragment.EXTRA_USER_MESSAGE), + intent.getParcelableArrayListExtra(FormFragment.EXTRA_ATTACHMENTS)); } } }; - private void submitFeedbackIntent(@Nullable String userMessage) { + private void submitFeedbackIntent(@Nullable String userMessage, + @Nullable ArrayList attachments) { Intent intent = new Intent(ACTION_END_FEEDBACK_FLOW); intent.putExtra(SCREENSHOT_URI, imageUri); intent.putExtra(TITLE, getString(getTitleResId(feedbackType))); intent.putExtra(MESSAGE, userMessage); intent.putExtra(USER_DATA, userData); + if (attachments != null && !attachments.isEmpty()) { + intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, attachments); + } LocalBroadcastManager.getInstance(this).sendBroadcast(intent); finish(); diff --git a/shaky/src/main/java/com/linkedin/android/shaky/FormFragment.java b/shaky/src/main/java/com/linkedin/android/shaky/FormFragment.java index 1a914ad..372f8a9 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/FormFragment.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/FormFragment.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,9 +15,13 @@ */ package com.linkedin.android.shaky; +import android.app.Activity; +import android.content.ClipData; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -25,12 +29,20 @@ import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; +import android.text.Html; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; /** * The main form used to send feedback. @@ -41,18 +53,39 @@ public class FormFragment extends Fragment { static final String ACTION_EDIT_IMAGE = "ActionEditImage"; static final String EXTRA_USER_MESSAGE = "ExtraUserMessage"; + static final String EXTRA_ATTACHMENTS = "ExtraAttachments"; private static final String KEY_SCREENSHOT_URI = "ScreenshotUri"; private static final String KEY_TITLE = "title"; private static final String KEY_HINT = "hint"; + private static final String KEY_ADD_ATTACHMENT = "addAttachment"; + private static final String ALL = "*/*"; + private static final int ATTACHMENT_REQUEST_CODE = 0x1234; + private LinearLayout attachmentsView; + private final OnAttachmentClickListener onAttachmentClickListener = new OnAttachmentClickListener() { + @Override + public void onRemoved(@NonNull Uri fileUri) { + removeAttachment(fileUri); + } + + @Override + public void onClicked(@NonNull Uri fileUri) { + if (fileUri.equals(screenshotUri)) { + editScreenshot(); + } + } + }; + private Uri screenshotUri; public static FormFragment newInstance(@NonNull String title, @NonNull String hint, - @Nullable Uri screenshotUri) { + @Nullable Uri screenshotUri, + boolean addAttachmentShown) { Bundle args = new Bundle(); args.putParcelable(KEY_SCREENSHOT_URI, screenshotUri); args.putString(KEY_TITLE, title); args.putString(KEY_HINT, hint); + args.putBoolean(KEY_ADD_ATTACHMENT, addAttachmentShown); FormFragment fragment = new FormFragment(); fragment.setArguments(args); @@ -71,10 +104,12 @@ public void onViewCreated(View view, Bundle savedInstanceState) { Toolbar toolbar = (Toolbar) view.findViewById(R.id.shaky_toolbar); EditText messageEditText = (EditText) view.findViewById(R.id.shaky_form_message); + attachmentsView = (LinearLayout) view.findViewById(R.id.attachments_view); ImageView attachmentImageView = (ImageView) view.findViewById(R.id.shaky_form_attachment); + Button addAttachmentButton = (Button) view.findViewById(R.id.add_attachment_button); - Uri screenshotUri = getArguments().getParcelable(KEY_SCREENSHOT_URI); - + screenshotUri = getArguments().getParcelable(KEY_SCREENSHOT_URI); + boolean addAttachmentShown = getArguments().getBoolean(KEY_ADD_ATTACHMENT, false); String title = getArguments().getString(KEY_TITLE); toolbar.setTitle(title); toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp); @@ -86,8 +121,85 @@ public void onViewCreated(View view, Bundle savedInstanceState) { messageEditText.setHint(hint); messageEditText.requestFocus(); - attachmentImageView.setImageURI(screenshotUri); - attachmentImageView.setOnClickListener(createNavigationClickListener()); + if (addAttachmentShown) { + addAttachmentButton.setVisibility(View.VISIBLE); + addAttachmentButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDocumentPicker(); + } + }); + attachmentsView.removeAllViews(); + addAttachment(screenshotUri, getString(R.string.shaky_screenshot), false); + } else { + addAttachmentButton.setVisibility(View.GONE); + attachmentImageView.setImageURI(screenshotUri); + attachmentImageView.setOnClickListener(createNavigationClickListener()); + } + } + + private void showDocumentPicker() { + Intent intent = new Intent(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT + ? Intent.ACTION_OPEN_DOCUMENT + : Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(ALL); + // only choosing from local storage due to permission issues + // and lack of loading progress when downloading from a network location + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + } + startActivityForResult(intent, ATTACHMENT_REQUEST_CODE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == ATTACHMENT_REQUEST_CODE) { + if (resultCode == Activity.RESULT_OK) { + List list = new ArrayList<>(); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN + && intent.getClipData() != null) { + ClipData clipData = intent.getClipData(); + for (int i = 0, size = clipData.getItemCount(); i < size; i++) { + list.add(clipData.getItemAt(i).getUri()); + } + } else if (intent.getData() != null) { + list.add(intent.getData()); + } + Context context = getContext(); + for (Uri uri : list) { + Utils.persistFilePermissions(context, uri, intent); + addAttachment(uri, null, true); + } + } + } else { + super.onActivityResult(requestCode, resultCode, intent); + } + } + + private void addAttachment(@Nullable Uri fileUri, @Nullable String defaultFilename, boolean removable) { + if (fileUri == null) { + return; + } + View view = LayoutInflater.from(getContext()).inflate(R.layout.shaky_attachment_view, attachmentsView, false); + AttachmentViewHolder viewHolder = new AttachmentViewHolder(view, onAttachmentClickListener); + viewHolder.bind(fileUri, defaultFilename, removable); + attachmentsView.addView(view); + } + + private void removeAttachment(@NonNull Uri fileUri) { + for (int i = 0, count = attachmentsView.getChildCount(); i < count; i++) { + Object tag = attachmentsView.getChildAt(i).getTag(); + if (tag instanceof Uri) { + if (tag.equals(fileUri)) { + attachmentsView.removeViewAt(i); + return; + } + } + } } @NonNull @@ -95,12 +207,16 @@ private View.OnClickListener createNavigationClickListener() { return new View.OnClickListener() { @Override public void onClick(View v) { - Intent intent = new Intent(ACTION_EDIT_IMAGE); - LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); + editScreenshot(); } }; } + private void editScreenshot() { + Intent intent = new Intent(ACTION_EDIT_IMAGE); + LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); + } + @NonNull private Toolbar.OnMenuItemClickListener createMenuClickListener(@NonNull final EditText messageEditText) { return new Toolbar.OnMenuItemClickListener() { @@ -112,6 +228,7 @@ public boolean onMenuItemClick(MenuItem item) { if (validate(message)) { Intent intent = new Intent(ACTION_SUBMIT_FEEDBACK); intent.putExtra(EXTRA_USER_MESSAGE, message); + intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, getAttachments()); LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); return true; } @@ -121,23 +238,96 @@ public boolean onMenuItemClick(MenuItem item) { }; } + private ArrayList getAttachments() { + int attachmentCount = attachmentsView.getChildCount(); + ArrayList list = new ArrayList<>(attachmentCount - 1); + // ignore the first screenshot + for (int i = 1; i < attachmentCount; i++) { + Object tag = attachmentsView.getChildAt(i).getTag(); + if (tag instanceof Uri) { + list.add((Uri) tag); + } + } + return list; + } + /** * Validates the message and returns true if the form is valid. */ private boolean validate(@NonNull String message) { if (message.trim().length() == 0) { - AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).create(); - alertDialog.setMessage(getString(R.string.shaky_empty_feedback_message)); - alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.shaky_empty_feedback_confirm), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - alertDialog.show(); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), + R.style.Theme_AppCompat_Light_Dialog_Alert); + builder.setMessage(getString(R.string.shaky_empty_feedback_message)); + builder.setPositiveButton(getString(R.string.shaky_empty_feedback_confirm), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + builder.show(); return false; } return true; } + + private static class AttachmentViewHolder { + + private static final String IMAGE_PREFIX = "image/"; + private final TextView filenameView; + private final ImageView thumbnailView; + private final ImageView deleteIcon; + private final View rootView; + + AttachmentViewHolder(@NonNull View view, @NonNull final OnAttachmentClickListener listener) { + filenameView = (TextView) view.findViewById(R.id.filename_view); + deleteIcon = (ImageView) view.findViewById(R.id.delete_icon); + thumbnailView = (ImageView) view.findViewById(R.id.thumbnail_view); + rootView = view; + rootView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v.getTag() instanceof Uri) { + listener.onClicked((Uri) v.getTag()); + } + } + }); + deleteIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v.getTag() instanceof Uri) { + listener.onRemoved((Uri) v.getTag()); + } + } + }); + } + + void bind(@NonNull Uri fileUri, @Nullable String defaultFilename, boolean removable) { + filenameView.setText(Html.fromHtml(getFilename(fileUri, defaultFilename))); + deleteIcon.setVisibility(removable ? View.VISIBLE : View.GONE); + rootView.setTag(fileUri); + deleteIcon.setTag(fileUri); + String mimeType = Utils.getMimeType(rootView.getContext(), fileUri); + if (mimeType != null && mimeType.startsWith(IMAGE_PREFIX)) { + thumbnailView.setVisibility(View.VISIBLE); + thumbnailView.setImageURI(fileUri); + } else { + thumbnailView.setVisibility(View.GONE); + } + } + + private String getFilename(@NonNull Uri fileUri, @Nullable String defaultFilename) { + String name = TextUtils.isEmpty(defaultFilename) ? Utils.getFilename(rootView.getContext(), fileUri) + : defaultFilename; + return name; + } + } + + private interface OnAttachmentClickListener { + + void onRemoved(@NonNull Uri fileUri); + + void onClicked(@NonNull Uri fileUri); + } } diff --git a/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java b/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java index e0fb791..794e457 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java @@ -69,4 +69,11 @@ public void collectData(Activity activity, Result data) { * This method can be overridden to send data to a custom URL endpoint, etc. */ public abstract void submit(Activity activity, Result result); + + /** + * @return true if more attachments are allowed to be attached to the reports + */ + public boolean isAddAttachmentFeatureEnabled() { + return false; + } } diff --git a/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java b/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java index 457457b..f60d307 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.hardware.SensorManager; import android.net.Uri; +import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -224,7 +225,8 @@ public void onDataReady(@Nullable Result result) { * Launches the main feedback activity with the bundle extra data. */ private void startFeedbackActivity(@NonNull Result result) { - Intent intent = FeedbackActivity.newIntent(activity, result.getScreenshotUri(), result.getData()); + Intent intent = FeedbackActivity.newIntent(activity, result.getScreenshotUri(), result.getData(), + delegate.isAddAttachmentFeatureEnabled()); activity.startActivity(intent); } @@ -239,6 +241,15 @@ private Result unpackResult(Intent intent) { for (Uri attachment : result.getAttachments()) { fileProviderAttachments.add(Utils.getProviderUri(activity, attachment)); } + // add extra attachments provided by the user + ArrayList extraAttachments = intent.getParcelableArrayListExtra(FeedbackActivity.EXTRA_ATTACHMENTS); + if (extraAttachments != null && !extraAttachments.isEmpty()) { + for (Parcelable parcelable: extraAttachments) { + if (parcelable instanceof Uri) { + fileProviderAttachments.add((Uri) parcelable); + } + } + } result.setAttachments(fileProviderAttachments); return result; diff --git a/shaky/src/main/java/com/linkedin/android/shaky/Utils.java b/shaky/src/main/java/com/linkedin/android/shaky/Utils.java index aefce4e..286c751 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/Utils.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/Utils.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,27 +15,37 @@ */ package com.linkedin.android.shaky; +import android.content.ContentResolver; import android.content.Context; -import android.content.res.Configuration; +import android.content.Intent; +import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.provider.OpenableColumns; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.support.v4.content.FileProvider; import android.util.Log; import android.view.View; +import android.webkit.MimeTypeMap; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Locale; final class Utils { + private static final String TAG = Utils.class.getSimpleName(); - private static final String FILE_NAME_TEMPLATE = "%s_%s.jpg"; + private static final String FILE_NAME_TEMPLATE = "%s_%s.png"; private static final String BITMAP_PREFIX = "bitmap"; private static final String FILE_PROVIDER_SUFFIX = ".fileprovider"; @@ -130,4 +140,117 @@ static Uri getProviderUri(@NonNull Context context, @NonNull Uri uri) { File file = new File(uri.getPath()); return getProviderUri(context, file); } + + /** + * @param uri The uri to evaluate. + * @return Whether or not the given uri points to resource on the device. This check is basic, so it does not + * guarantee the resource actually exists. + */ + static boolean isLocalUri(@Nullable Uri uri) { + if (uri != null) { + String scheme = uri.getScheme(); + return ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme) + || ContentResolver.SCHEME_ANDROID_RESOURCE.equalsIgnoreCase(scheme) + || ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme); + } + return false; + } + + /** + * Tries to determine the current mime type of the pending attachment by reading the file at it's uri. There's + * fallback logic included. + * + * @param context A context used to read the file at the attachment's uri. + * @param uri Media uri + */ + @Nullable + static String getMimeType(@NonNull Context context, @NonNull Uri uri) { + if (!isLocalUri(uri)) { + return null; + } + String newMediaType = null; + + // Try to decode the bitmap to get its mime type. In some cases on Android M, the decoder may not return a + // mime type. Fallback logic kicks in afterwards. + InputStream inputStream = null; + try { + inputStream = context.getContentResolver().openInputStream(uri); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(inputStream, null, options); + + if (options.outMimeType != null) { + newMediaType = options.outMimeType; + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Error getting mediaType for : " + uri.toString(), e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + // ignore + } + } + } + + // Fallback to the content resolver if the bitmap decoding hasn't worked. + if (newMediaType == null) { + newMediaType = context.getContentResolver().getType(uri); + } + + // Fallback to the file extension if everything else fails. + if (newMediaType == null) { + String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); + if (extension != null) { + newMediaType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + } + + return newMediaType; + } + + /** + * Make the file uri persistent + */ + static void persistFilePermissions(@NonNull Context context, @NonNull Uri uri, @Nullable Intent data) { + // Only works for API level 19 or above. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && data != null) { + final int takeFlags = data.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + // We're not guaranteed to get the permission grants for this uri. It's not fatal if we don't. + try { + context.getContentResolver().takePersistableUriPermission(uri, takeFlags); + } catch (SecurityException e) { + if (BuildConfig.DEBUG) { + Log.e(TAG, "Could not persist permission grants for " + uri); + } + } + } + } + + @NonNull + static String getFilename(@NonNull Context context, @NonNull Uri uri) { + String scheme = uri.getScheme(); + String filename = null; + if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + filename = cursor.getString(nameIndex); + } + } finally { + cursor.close(); + } + } + } + // use the last path segment as the filename + if (filename == null) { + filename = uri.getLastPathSegment(); + } + return filename; + } } diff --git a/shaky/src/main/res/layout-v17/shaky_form.xml b/shaky/src/main/res/layout-v17/shaky_form.xml index 6a05e8e..5b701a2 100644 --- a/shaky/src/main/res/layout-v17/shaky_form.xml +++ b/shaky/src/main/res/layout-v17/shaky_form.xml @@ -54,7 +54,15 @@ android:scrollbars="vertical" android:textColor="@color/dark_gray"/> +