Commit 4b442abf authored by dfalcantara's avatar dfalcantara Committed by Commit bot

[Downloads] Open media downloads internally

* Force the Custom Tab Intent to use the same package as Chrome
  when opening the media viewer.

* Open media downloads inside of the internal media viewer when
  opening the file in the notification or via the snackbar.

* Shuffle code around for creating media viewer Intents in the
  hopes of consolidating them in places that makes sense.

BUG=650448

Review-Url: https://codereview.chromium.org/2376893006
Cr-Commit-Position: refs/heads/master@{#422624}
parent 1e865bea
...@@ -5,13 +5,14 @@ ...@@ -5,13 +5,14 @@
package org.chromium.chrome.browser.download; package org.chromium.chrome.browser.download;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import org.chromium.chrome.browser.util.IntentUtils;
/** /**
* This {@link BroadcastReceiver} handles clicks to download notifications and their action buttons. * This {@link BroadcastReceiver} handles clicks to download notifications and their action buttons.
* Clicking on an in-progress or failed download will open the download manager. Clicking on * Clicking on an in-progress or failed download will open the download manager. Clicking on
...@@ -58,12 +59,11 @@ public class DownloadBroadcastReceiver extends BroadcastReceiver { ...@@ -58,12 +59,11 @@ public class DownloadBroadcastReceiver extends BroadcastReceiver {
// Open the downloads page // Open the downloads page
DownloadManagerService.openDownloadsPage(context); DownloadManagerService.openDownloadsPage(context);
} else { } else {
Intent launchIntent = new Intent(Intent.ACTION_VIEW); String downloadFilename = IntentUtils.safeGetStringExtra(
launchIntent.setDataAndType(uri, manager.getMimeTypeForDownloadedFile(id)); intent, DownloadNotificationService.EXTRA_DOWNLOAD_FILE_PATH);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Intent launchIntent = DownloadManagerService.getLaunchIntentFromDownloadId(
try { context, downloadFilename, id);
context.startActivity(launchIntent); if (!DownloadUtils.fireOpenIntentForDownload(context, launchIntent)) {
} catch (ActivityNotFoundException e) {
DownloadManagerService.openDownloadsPage(context); DownloadManagerService.openDownloadsPage(context);
} }
} }
......
...@@ -17,6 +17,7 @@ import android.net.Uri; ...@@ -17,6 +17,7 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.LongSparseArray; import android.util.LongSparseArray;
import android.util.Pair; import android.util.Pair;
...@@ -624,7 +625,7 @@ public class DownloadManagerService extends BroadcastReceiver implements ...@@ -624,7 +625,7 @@ public class DownloadManagerService extends BroadcastReceiver implements
download.getDownloadInfo(), download.getSystemDownloadId()); download.getDownloadInfo(), download.getSystemDownloadId());
return; return;
} }
openDownloadedContent(download.getSystemDownloadId()); openDownloadedContent(download.getDownloadInfo(), download.getSystemDownloadId());
} }
/** /**
...@@ -944,52 +945,32 @@ public class DownloadManagerService extends BroadcastReceiver implements ...@@ -944,52 +945,32 @@ public class DownloadManagerService extends BroadcastReceiver implements
downloadInfo.getMimeType()); downloadInfo.getMimeType());
} }
/**
* Launch the best activity for the given intent. If a default activity is provided,
* choose the default one. Otherwise, return the Intent picker if there are more than one
* capable activities. If the intent is pdf type, return the platform pdf viewer if
* it is available so user don't need to choose it from Intent picker.
*
* @param context Context of the app.
* @param intent Intent to open.
* @param allowSelfOpen Whether chrome itself is allowed to open the intent.
* @return true if an Intent is launched, or false otherwise.
*/
public static boolean openIntent(Context context, Intent intent, boolean allowSelfOpen) {
boolean activityResolved = ExternalNavigationDelegateImpl.resolveIntent(
context, intent, allowSelfOpen);
if (activityResolved) {
try {
context.startActivity(intent);
return true;
} catch (ActivityNotFoundException ex) {
Log.d(TAG, "activity not found for " + intent.getType()
+ " over " + intent.getData().getScheme(), ex);
} catch (SecurityException ex) {
Log.d(TAG, "cannot open intent: " + intent, ex);
}
}
return false;
}
/** /**
* Return the intent to launch for a given download item. * Return the intent to launch for a given download item.
* *
* @param context Context of the app. * @param context Context of the app.
* @param filePath Path to the file.
* @param downloadId ID of the download item in DownloadManager. * @param downloadId ID of the download item in DownloadManager.
* @return the intent to launch for the given download item. * @return the intent to launch for the given download item.
*/ */
private static Intent getLaunchIntentFromDownloadId(Context context, long downloadId) { static Intent getLaunchIntentFromDownloadId(
Context context, @Nullable String filePath, long downloadId) {
assert !ThreadUtils.runningOnUiThread(); assert !ThreadUtils.runningOnUiThread();
DownloadManager manager = DownloadManager manager =
(DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Uri uri = manager.getUriForDownloadedFile(downloadId); Uri contentUri = manager.getUriForDownloadedFile(downloadId);
if (uri == null) return null; if (contentUri == null) return null;
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
launchIntent.setDataAndType(uri, manager.getMimeTypeForDownloadedFile(downloadId)); String mimeType = manager.getMimeTypeForDownloadedFile(downloadId);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK if (isSupportedMimeType(mimeType)) {
| Intent.FLAG_GRANT_READ_URI_PERMISSION); // Redirect the user to an internal media viewer. The file path is necessary to show
return launchIntent; // the real file path to the user instead of a content:// download ID.
Uri fileUri = contentUri;
if (filePath != null) fileUri = Uri.fromFile(new File(filePath));
return DownloadUtils.getMediaViewerIntentForDownloadItem(fileUri, contentUri, mimeType);
}
return DownloadUtils.createViewIntentForDownloadItem(contentUri, mimeType);
} }
/** /**
...@@ -1001,7 +982,9 @@ public class DownloadManagerService extends BroadcastReceiver implements ...@@ -1001,7 +982,9 @@ public class DownloadManagerService extends BroadcastReceiver implements
*/ */
static boolean canResolveDownloadItem(Context context, DownloadItem download) { static boolean canResolveDownloadItem(Context context, DownloadItem download) {
assert !ThreadUtils.runningOnUiThread(); assert !ThreadUtils.runningOnUiThread();
Intent intent = getLaunchIntentFromDownloadId(context, download.getSystemDownloadId()); Intent intent = getLaunchIntentFromDownloadId(
context, download.getDownloadInfo().getFilePath(),
download.getSystemDownloadId());
return (intent == null) ? false : ExternalNavigationDelegateImpl.resolveIntent( return (intent == null) ? false : ExternalNavigationDelegateImpl.resolveIntent(
context, intent, true); context, intent, true);
} }
...@@ -1010,19 +993,24 @@ public class DownloadManagerService extends BroadcastReceiver implements ...@@ -1010,19 +993,24 @@ public class DownloadManagerService extends BroadcastReceiver implements
* Launch the intent for a given download item. * Launch the intent for a given download item.
* TODO(qinmin): Move this to DownloadManagerDelegate. * TODO(qinmin): Move this to DownloadManagerDelegate.
* *
* @param downloadId ID of the download item in DownloadManager. * @param downloadInfo Info about the downloaded item.
* @param downloadId ID of the download item in DownloadManager.
*/ */
protected void openDownloadedContent(final long downloadId) { protected void openDownloadedContent(final DownloadInfo downloadInfo, final long downloadId) {
new AsyncTask<Void, Void, Intent>() { new AsyncTask<Void, Void, Intent>() {
@Override @Override
public Intent doInBackground(Void... params) { public Intent doInBackground(Void... params) {
return getLaunchIntentFromDownloadId(mContext, downloadId); return getLaunchIntentFromDownloadId(
mContext, downloadInfo.getFilePath(), downloadId);
} }
@Override @Override
protected void onPostExecute(Intent intent) { protected void onPostExecute(Intent intent) {
if (intent != null) { if (intent == null) return;
openIntent(mContext, intent, true);
Context context = ContextUtils.getApplicationContext();
if (ExternalNavigationDelegateImpl.resolveIntent(context, intent, true)) {
DownloadUtils.fireOpenIntentForDownload(context, intent);
} }
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
...@@ -1169,6 +1157,15 @@ public class DownloadManagerService extends BroadcastReceiver implements ...@@ -1169,6 +1157,15 @@ public class DownloadManagerService extends BroadcastReceiver implements
getNativeDownloadManagerService(), downloadGuid, isOffTheRecord); getNativeDownloadManagerService(), downloadGuid, isOffTheRecord);
} }
/**
* Checks whether a file with the given MIME type can be opened by the browser.
* @param mimeType MIME type of the file.
* @return Whether the file would be openable by the browser.
*/
public static boolean isSupportedMimeType(String mimeType) {
return nativeIsSupportedMimeType(mimeType);
}
/** /**
* Helper method to create and retrieve the native DownloadManagerService when needed. * Helper method to create and retrieve the native DownloadManagerService when needed.
* @return pointer to native DownloadManagerService. * @return pointer to native DownloadManagerService.
...@@ -1645,6 +1642,8 @@ public class DownloadManagerService extends BroadcastReceiver implements ...@@ -1645,6 +1642,8 @@ public class DownloadManagerService extends BroadcastReceiver implements
return downloadItem; return downloadItem;
} }
private static native boolean nativeIsSupportedMimeType(String mimeType);
private native long nativeInit(); private native long nativeInit();
private native void nativeResumeDownload( private native void nativeResumeDownload(
long nativeDownloadManagerService, String downloadGuid, boolean isOffTheRecord); long nativeDownloadManagerService, String downloadGuid, boolean isOffTheRecord);
......
...@@ -48,6 +48,7 @@ public class DownloadNotificationService extends Service { ...@@ -48,6 +48,7 @@ public class DownloadNotificationService extends Service {
static final String EXTRA_DOWNLOAD_NOTIFICATION_ID = "DownloadNotificationId"; static final String EXTRA_DOWNLOAD_NOTIFICATION_ID = "DownloadNotificationId";
static final String EXTRA_DOWNLOAD_GUID = "DownloadGuid"; static final String EXTRA_DOWNLOAD_GUID = "DownloadGuid";
static final String EXTRA_DOWNLOAD_FILE_NAME = "DownloadFileName"; static final String EXTRA_DOWNLOAD_FILE_NAME = "DownloadFileName";
static final String EXTRA_DOWNLOAD_FILE_PATH = "DownloadFilePath";
static final String EXTRA_NOTIFICATION_DISMISSED = "NotificationDismissed"; static final String EXTRA_NOTIFICATION_DISMISSED = "NotificationDismissed";
static final String EXTRA_DOWNLOAD_IS_OFF_THE_RECORD = "DownloadIsOffTheRecord"; static final String EXTRA_DOWNLOAD_IS_OFF_THE_RECORD = "DownloadIsOffTheRecord";
static final String EXTRA_DOWNLOAD_IS_OFFLINE_PAGE = "DownloadIsOfflinePage"; static final String EXTRA_DOWNLOAD_IS_OFFLINE_PAGE = "DownloadIsOfflinePage";
...@@ -326,13 +327,15 @@ public class DownloadNotificationService extends Service { ...@@ -326,13 +327,15 @@ public class DownloadNotificationService extends Service {
/** /**
* Add a download successful notification. * Add a download successful notification.
* @param downloadGuid GUID of the download. * @param downloadGuid GUID of the download.
* @param fileName GUID of the download. * @param filePath Full path to the download.
* @param fileName Filename of the download.
* @param systemDownloadId Download ID assigned by system DownloadManager. * @param systemDownloadId Download ID assigned by system DownloadManager.
* @return ID of the successful download notification. Used for removing the notification when * @return ID of the successful download notification. Used for removing the notification when
* user click on the snackbar. * user click on the snackbar.
*/ */
public int notifyDownloadSuccessful( public int notifyDownloadSuccessful(
String downloadGuid, String fileName, long systemDownloadId, boolean isOfflinePage) { String downloadGuid, String filePath, String fileName, long systemDownloadId,
boolean isOfflinePage) {
int notificationId = getNotificationId(downloadGuid); int notificationId = getNotificationId(downloadGuid);
NotificationCompat.Builder builder = buildNotification( NotificationCompat.Builder builder = buildNotification(
R.drawable.offline_pin, fileName, R.drawable.offline_pin, fileName,
...@@ -347,6 +350,7 @@ public class DownloadNotificationService extends Service { ...@@ -347,6 +350,7 @@ public class DownloadNotificationService extends Service {
intent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); intent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED);
long[] idArray = {systemDownloadId}; long[] idArray = {systemDownloadId};
intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, idArray); intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, idArray);
intent.putExtra(EXTRA_DOWNLOAD_FILE_PATH, filePath);
} }
intent.setComponent(component); intent.setComponent(component);
builder.setContentIntent(PendingIntent.getBroadcast( builder.setContentIntent(PendingIntent.getBroadcast(
......
...@@ -39,9 +39,8 @@ public class DownloadSnackbarController implements SnackbarManager.SnackbarContr ...@@ -39,9 +39,8 @@ public class DownloadSnackbarController implements SnackbarManager.SnackbarContr
} }
@Override @Override
@SuppressWarnings("unchecked")
public void onAction(Object actionData) { public void onAction(Object actionData) {
if (actionData == null) { if (!(actionData instanceof ActionDataInfo)) {
DownloadManagerService.openDownloadsPage(mContext); DownloadManagerService.openDownloadsPage(mContext);
return; return;
} }
...@@ -51,7 +50,7 @@ public class DownloadSnackbarController implements SnackbarManager.SnackbarContr ...@@ -51,7 +50,7 @@ public class DownloadSnackbarController implements SnackbarManager.SnackbarContr
return; return;
} }
DownloadManagerService manager = DownloadManagerService.getDownloadManagerService(mContext); DownloadManagerService manager = DownloadManagerService.getDownloadManagerService(mContext);
manager.openDownloadedContent(download.systemDownloadId); manager.openDownloadedContent(download.downloadInfo, download.systemDownloadId);
if (download.notificationId != INVALID_NOTIFICATION_ID) { if (download.notificationId != INVALID_NOTIFICATION_ID) {
NotificationManager notificationManager = NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
......
...@@ -5,12 +5,19 @@ ...@@ -5,12 +5,19 @@
package org.chromium.chrome.browser.download; package org.chromium.chrome.browser.download;
import android.app.Activity; import android.app.Activity;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.StrictMode; import android.os.StrictMode;
import android.provider.Browser;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.customtabs.CustomTabsIntent;
import android.text.TextUtils; import android.text.TextUtils;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
...@@ -24,11 +31,12 @@ import org.chromium.chrome.browser.ChromeFeatureList; ...@@ -24,11 +31,12 @@ import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.UrlConstants; import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.download.ui.BackendProvider; import org.chromium.chrome.browser.download.ui.BackendProvider;
import org.chromium.chrome.browser.download.ui.BackendProvider.DownloadDelegate; import org.chromium.chrome.browser.download.ui.BackendProvider.DownloadDelegate;
import org.chromium.chrome.browser.download.ui.DownloadFilter; import org.chromium.chrome.browser.download.ui.DownloadFilter;
import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper; import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper;
import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.DownloadItemWrapper;
import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBridge; import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBridge;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
...@@ -38,6 +46,7 @@ import org.chromium.content_public.browser.LoadUrlParams; ...@@ -38,6 +46,7 @@ import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.DeviceFormFactor; import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.widget.Toast; import org.chromium.ui.widget.Toast;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -209,17 +218,17 @@ public class DownloadUtils { ...@@ -209,17 +218,17 @@ public class DownloadUtils {
/** /**
* Creates an Intent to open the file in another app by firing an Intent to Android. * Creates an Intent to open the file in another app by firing an Intent to Android.
* @param item Item to open. * @param fileUri Uri pointing to the file.
* @return Intent that can be used to start an Activity for the DownloadItem. * @param mimeType MIME type for the file.
* @return Intent that can be used to start an Activity for the file.
*/ */
public static Intent createViewIntentForDownloadItem(DownloadItemWrapper item) { public static Intent createViewIntentForDownloadItem(Uri fileUri, String mimeType) {
String mimeType = Intent.normalizeMimeType(item.getMimeType());
Uri fileUri = Uri.fromFile(item.getFile());
Intent fileIntent = new Intent(Intent.ACTION_VIEW); Intent fileIntent = new Intent(Intent.ACTION_VIEW);
if (TextUtils.isEmpty(mimeType)) { String normalizedMimeType = Intent.normalizeMimeType(mimeType);
if (TextUtils.isEmpty(normalizedMimeType)) {
fileIntent.setData(fileUri); fileIntent.setData(fileUri);
} else { } else {
fileIntent.setDataAndType(fileUri, mimeType); fileIntent.setDataAndType(fileUri, normalizedMimeType);
} }
fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
fileIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); fileIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
...@@ -254,7 +263,7 @@ public class DownloadUtils { ...@@ -254,7 +263,7 @@ public class DownloadUtils {
} }
offlinePagesString.append(wrappedItem.getUrl()); offlinePagesString.append(wrappedItem.getUrl());
} else { } else {
itemUris.add(getUriForItem(wrappedItem)); itemUris.add(getUriForItem(wrappedItem.getFile()));
} }
if (selectedItemsFilterType != wrappedItem.getFilterType()) { if (selectedItemsFilterType != wrappedItem.getFilterType()) {
...@@ -306,7 +315,8 @@ public class DownloadUtils { ...@@ -306,7 +315,8 @@ public class DownloadUtils {
} }
if (itemUris.size() == 1 && offlinePagesString.length() == 0) { if (itemUris.size() == 1 && offlinePagesString.length() == 0) {
shareIntent.putExtra(Intent.EXTRA_STREAM, getUriForItem(items.get(0))); // Sharing a DownloadItem.
shareIntent.putExtra(Intent.EXTRA_STREAM, getUriForItem(items.get(0).getFile()));
} else { } else {
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, itemUris); shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, itemUris);
} }
...@@ -324,7 +334,74 @@ public class DownloadUtils { ...@@ -324,7 +334,74 @@ public class DownloadUtils {
return shareIntent; return shareIntent;
} }
private static Uri getUriForItem(DownloadHistoryItemWrapper itemWrapper) { private static Intent createShareIntent(Uri fileUri, String mimeType) {
if (TextUtils.isEmpty(mimeType)) mimeType = DEFAULT_MIME_TYPE;
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
intent.setType(mimeType);
return intent;
}
/**
* Creates an Intent that allows viewing the given file in an internal media viewer.
* @param fileUri URI pointing at the file, ideally in file:// form.
* @param shareUri URI pointing at the file, ideally in content:// form.
* @param mimeType MIME type of the file.
* @return Intent that can be fired to open the file.
*/
public static Intent getMediaViewerIntentForDownloadItem(
Uri fileUri, Uri shareUri, String mimeType) {
Context context = ContextUtils.getApplicationContext();
Intent viewIntent = createViewIntentForDownloadItem(fileUri, mimeType);
Bitmap closeIcon = BitmapFactory.decodeResource(
context.getResources(), R.drawable.ic_arrow_back_white_24dp);
Bitmap shareIcon = BitmapFactory.decodeResource(
context.getResources(), R.drawable.ic_share_white_24dp);
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setToolbarColor(Color.BLACK);
builder.setCloseButtonIcon(closeIcon);
builder.setShowTitle(true);
// Create a PendingIntent that can be used to view the file externally.
// TODO(dfalcantara): Check if this is problematic in multi-window mode, where two
// different viewers could be visible at the same time.
Intent chooserIntent = Intent.createChooser(viewIntent, null);
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String openWithStr = context.getString(R.string.download_manager_open_with);
PendingIntent pendingViewIntent = PendingIntent.getActivity(
context, 0, chooserIntent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.addMenuItem(openWithStr, pendingViewIntent);
// Create a PendingIntent that shares the file with external apps.
PendingIntent pendingShareIntent = PendingIntent.getActivity(
context, 0, createShareIntent(shareUri, mimeType), 0);
builder.setActionButton(
shareIcon, context.getString(R.string.share), pendingShareIntent, true);
// Build up the Intent further.
Intent intent = builder.build().intent;
intent.setPackage(context.getPackageName());
intent.setData(fileUri);
intent.putExtra(CustomTabIntentDataProvider.EXTRA_IS_MEDIA_VIEWER, true);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
IntentHandler.addTrustedIntentExtras(intent, context);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(context, CustomTabActivity.class);
return intent;
}
/**
* Returns a URI that points at the file.
* @param file File to get a URI for.
* @return URI that points at that file, either as a content:// URI or a file:// URI.
*/
public static Uri getUriForItem(File file) {
Uri uri = null; Uri uri = null;
// #getContentUriFromFile causes a disk read when it calls into FileProvider#getUriForFile. // #getContentUriFromFile causes a disk read when it calls into FileProvider#getUriForFile.
...@@ -334,16 +411,15 @@ public class DownloadUtils { ...@@ -334,16 +411,15 @@ public class DownloadUtils {
// know/preload which URIs we need until the user presses share. // know/preload which URIs we need until the user presses share.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try { try {
// Try to obtain a content:// URI, which is preferred to a file:/// URI so that // Try to obtain a content:// URI, which is preferred to a file:// URI so that
// receiving apps don't attempt to determine the file's mime type (which often fails). // receiving apps don't attempt to determine the file's mime type (which often fails).
uri = ContentUriUtils.getContentUriFromFile(ContextUtils.getApplicationContext(), uri = ContentUriUtils.getContentUriFromFile(ContextUtils.getApplicationContext(), file);
itemWrapper.getFile());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.e(TAG, "Could not create content uri: " + e); Log.e(TAG, "Could not create content uri: " + e);
} }
StrictMode.setThreadPolicy(oldPolicy); StrictMode.setThreadPolicy(oldPolicy);
if (uri == null) uri = Uri.fromFile(itemWrapper.getFile()); if (uri == null) uri = Uri.fromFile(file);
return uri; return uri;
} }
...@@ -355,4 +431,28 @@ public class DownloadUtils { ...@@ -355,4 +431,28 @@ public class DownloadUtils {
RecordHistogram.recordLinearCountHistogram("Android.DownloadManager.Share.Count", RecordHistogram.recordLinearCountHistogram("Android.DownloadManager.Share.Count",
count, 1, 20, 20); count, 1, 20, 20);
} }
/**
* Fires an Intent to open a downloaded item.
* @param context Context to use.
* @param intent Intent that can be fired.
* @return Whether an Activity was successfully started for the Intent.
*/
static boolean fireOpenIntentForDownload(Context context, Intent intent) {
try {
if (TextUtils.equals(intent.getPackage(), context.getPackageName())) {
IntentHandler.startActivityForTrustedIntent(intent, context);
} else {
context.startActivity(intent);
}
return true;
} catch (ActivityNotFoundException ex) {
Log.d(TAG, "Activity not found for " + intent.getType() + " over "
+ intent.getData().getScheme(), ex);
} catch (SecurityException ex) {
Log.d(TAG, "cannot open intent: " + intent, ex);
}
return false;
}
} }
...@@ -280,7 +280,7 @@ public class SystemDownloadNotifier implements DownloadNotifier { ...@@ -280,7 +280,7 @@ public class SystemDownloadNotifier implements DownloadNotifier {
break; break;
case DOWNLOAD_NOTIFICATION_TYPE_SUCCESS: case DOWNLOAD_NOTIFICATION_TYPE_SUCCESS:
final int notificationId = mBoundService.notifyDownloadSuccessful( final int notificationId = mBoundService.notifyDownloadSuccessful(
info.getDownloadGuid(), info.getFileName(), info.getDownloadGuid(), info.getFilePath(), info.getFileName(),
notificationInfo.systemDownloadId, info.isOfflinePage()); notificationInfo.systemDownloadId, info.isOfflinePage());
onSuccessNotificationShown(notificationInfo, notificationId); onSuccessNotificationShown(notificationInfo, notificationId);
stopServiceIfNeeded(); stopServiceIfNeeded();
......
...@@ -10,6 +10,8 @@ import org.chromium.base.Log; ...@@ -10,6 +10,8 @@ import org.chromium.base.Log;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.UrlConstants; import org.chromium.chrome.browser.UrlConstants;
import java.util.Locale;
/** /**
* A class holding constants and convenience methods about filters and their corresponding * A class holding constants and convenience methods about filters and their corresponding
* resources. * resources.
...@@ -26,6 +28,11 @@ public class DownloadFilter { ...@@ -26,6 +28,11 @@ public class DownloadFilter {
static final int FILTER_OTHER = 6; static final int FILTER_OTHER = 6;
public static final int FILTER_BOUNDARY = 7; public static final int FILTER_BOUNDARY = 7;
private static final String MIMETYPE_VIDEO = "video";
private static final String MIMETYPE_AUDIO = "audio";
private static final String MIMETYPE_IMAGE = "image";
private static final String MIMETYPE_DOCUMENT = "text";
/** /**
* Icons and labels for each filter in the menu. * Icons and labels for each filter in the menu.
* *
...@@ -91,4 +98,24 @@ public class DownloadFilter { ...@@ -91,4 +98,24 @@ public class DownloadFilter {
} }
return result; return result;
} }
/** Identifies the type of file represented by the given MIME type string. */
public static int fromMimeType(String mimeType) {
if (TextUtils.isEmpty(mimeType)) return DownloadFilter.FILTER_OTHER;
String[] pieces = mimeType.toLowerCase(Locale.getDefault()).split("/");
if (pieces.length != 2) return DownloadFilter.FILTER_OTHER;
if (MIMETYPE_VIDEO.equals(pieces[0])) {
return DownloadFilter.FILTER_VIDEO;
} else if (MIMETYPE_AUDIO.equals(pieces[0])) {
return DownloadFilter.FILTER_AUDIO;
} else if (MIMETYPE_IMAGE.equals(pieces[0])) {
return DownloadFilter.FILTER_IMAGE;
} else if (MIMETYPE_DOCUMENT.equals(pieces[0])) {
return DownloadFilter.FILTER_DOCUMENT;
} else {
return DownloadFilter.FILTER_OTHER;
}
}
} }
...@@ -4,26 +4,17 @@ ...@@ -4,26 +4,17 @@
package org.chromium.chrome.browser.download.ui; package org.chromium.chrome.browser.download.ui;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.provider.Browser;
import android.support.customtabs.CustomTabsIntent;
import android.text.TextUtils; import android.text.TextUtils;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.download.DownloadItem; import org.chromium.chrome.browser.download.DownloadItem;
import org.chromium.chrome.browser.download.DownloadUtils; import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadItem; import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadItem;
...@@ -31,14 +22,9 @@ import org.chromium.chrome.browser.widget.DateDividedAdapter.TimedItem; ...@@ -31,14 +22,9 @@ import org.chromium.chrome.browser.widget.DateDividedAdapter.TimedItem;
import org.chromium.ui.widget.Toast; import org.chromium.ui.widget.Toast;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/** Wraps different classes that contain information about downloads. */ /** Wraps different classes that contain information about downloads. */
public abstract class DownloadHistoryItemWrapper implements TimedItem { public abstract class DownloadHistoryItemWrapper implements TimedItem {
private static final String TAG = "download_ui";
protected final BackendProvider mBackendProvider; protected final BackendProvider mBackendProvider;
protected final ComponentName mComponentName; protected final ComponentName mComponentName;
private Long mStableId; private Long mStableId;
...@@ -118,11 +104,6 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem { ...@@ -118,11 +104,6 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem {
/** Wraps a {@link DownloadItem}. */ /** Wraps a {@link DownloadItem}. */
public static class DownloadItemWrapper extends DownloadHistoryItemWrapper { public static class DownloadItemWrapper extends DownloadHistoryItemWrapper {
private static final String MIMETYPE_VIDEO = "video";
private static final String MIMETYPE_AUDIO = "audio";
private static final String MIMETYPE_IMAGE = "image";
private static final String MIMETYPE_DOCUMENT = "text";
private final DownloadItem mItem; private final DownloadItem mItem;
private final boolean mIsOffTheRecord; private final boolean mIsOffTheRecord;
private File mFile; private File mFile;
...@@ -177,7 +158,7 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem { ...@@ -177,7 +158,7 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem {
@Override @Override
public int getFilterType() { public int getFilterType() {
return convertMimeTypeToFilterType(getMimeType()); return DownloadFilter.fromMimeType(getMimeType());
} }
@Override @Override
...@@ -188,7 +169,8 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem { ...@@ -188,7 +169,8 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem {
@Override @Override
public void open() { public void open() {
Context context = ContextUtils.getApplicationContext(); Context context = ContextUtils.getApplicationContext();
Intent viewIntent = DownloadUtils.createViewIntentForDownloadItem(this); Intent viewIntent = DownloadUtils.createViewIntentForDownloadItem(
Uri.fromFile(getFile()), getMimeType());
if (mItem.hasBeenExternallyRemoved()) { if (mItem.hasBeenExternallyRemoved()) {
Toast.makeText(context, context.getString(R.string.download_cant_open_file), Toast.makeText(context, context.getString(R.string.download_cant_open_file),
...@@ -199,43 +181,14 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem { ...@@ -199,43 +181,14 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem {
// Check if Chrome should open the file itself. // Check if Chrome should open the file itself.
if (mBackendProvider.getDownloadDelegate().isDownloadOpenableInBrowser( if (mBackendProvider.getDownloadDelegate().isDownloadOpenableInBrowser(
mItem.getId(), mIsOffTheRecord)) { mItem.getId(), mIsOffTheRecord)) {
Bitmap closeIcon = BitmapFactory.decodeResource( // Share URIs use the content:// scheme when able, which looks bad when displayed
context.getResources(), R.drawable.ic_arrow_back_white_24dp); // in the URL bar.
Bitmap shareIcon = BitmapFactory.decodeResource( Uri fileUri = Uri.fromFile(getFile());
context.getResources(), R.drawable.ic_share_white_24dp); Uri shareUri = DownloadUtils.getUriForItem(getFile());
String mimeType = Intent.normalizeMimeType(getMimeType());
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setToolbarColor(Color.BLACK); Intent intent = DownloadUtils.getMediaViewerIntentForDownloadItem(
builder.setCloseButtonIcon(closeIcon); fileUri, shareUri, mimeType);
builder.setShowTitle(true);
// Create a PendingIntent that can be used to view the file externally.
// TODO(dfalcantara): Check if this is problematic in multi-window mode, where two
// different viewers could be visible at the same time.
Intent chooserIntent = Intent.createChooser(viewIntent, null);
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String openWithStr = context.getString(R.string.download_manager_open_with);
PendingIntent pendingViewIntent = PendingIntent.getActivity(
context, 0, chooserIntent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.addMenuItem(openWithStr, pendingViewIntent);
// Create a PendingIntent that shares the file with external apps.
List<DownloadHistoryItemWrapper> items = new ArrayList<>();
items.add(this);
PendingIntent pendingShareIntent = PendingIntent.getActivity(
context, 0, DownloadUtils.createShareIntent(items), 0);
builder.setActionButton(
shareIcon, context.getString(R.string.share), pendingShareIntent, true);
// Build up the Intent further.
Intent intent = builder.build().intent;
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(UrlConstants.FILE_SCHEME + getFilePath()));
intent.putExtra(CustomTabIntentDataProvider.EXTRA_IS_MEDIA_VIEWER, true);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(context, CustomTabActivity.class);
IntentHandler.startActivityForTrustedIntent(intent, context); IntentHandler.startActivityForTrustedIntent(intent, context);
return; return;
} }
...@@ -268,26 +221,6 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem { ...@@ -268,26 +221,6 @@ public abstract class DownloadHistoryItemWrapper implements TimedItem {
boolean isOffTheRecord() { boolean isOffTheRecord() {
return mIsOffTheRecord; return mIsOffTheRecord;
} }
/** Identifies the type of file represented by the given MIME type string. */
private static int convertMimeTypeToFilterType(String mimeType) {
if (TextUtils.isEmpty(mimeType)) return DownloadFilter.FILTER_OTHER;
String[] pieces = mimeType.toLowerCase(Locale.getDefault()).split("/");
if (pieces.length != 2) return DownloadFilter.FILTER_OTHER;
if (MIMETYPE_VIDEO.equals(pieces[0])) {
return DownloadFilter.FILTER_VIDEO;
} else if (MIMETYPE_AUDIO.equals(pieces[0])) {
return DownloadFilter.FILTER_AUDIO;
} else if (MIMETYPE_IMAGE.equals(pieces[0])) {
return DownloadFilter.FILTER_IMAGE;
} else if (MIMETYPE_DOCUMENT.equals(pieces[0])) {
return DownloadFilter.FILTER_DOCUMENT;
} else {
return DownloadFilter.FILTER_OTHER;
}
}
} }
/** Wraps a {@link OfflinePageDownloadItem}. */ /** Wraps a {@link OfflinePageDownloadItem}. */
......
...@@ -9,7 +9,6 @@ import android.content.Context; ...@@ -9,7 +9,6 @@ import android.content.Context;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log; import android.util.Log;
...@@ -24,6 +23,7 @@ import org.chromium.base.test.util.RetryOnFailure; ...@@ -24,6 +23,7 @@ import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.base.test.util.UrlUtils; import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.download.DownloadInfo.Builder; import org.chromium.chrome.browser.download.DownloadInfo.Builder;
import org.chromium.chrome.browser.download.DownloadManagerServiceTest.MockDownloadNotifier.MethodID; import org.chromium.chrome.browser.download.DownloadManagerServiceTest.MockDownloadNotifier.MethodID;
import org.chromium.content.browser.test.NativeLibraryTestBase;
import org.chromium.content.browser.test.util.Criteria; import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper; import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.net.ConnectionType; import org.chromium.net.ConnectionType;
...@@ -40,7 +40,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; ...@@ -40,7 +40,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
/** /**
* Test for DownloadManagerService. * Test for DownloadManagerService.
*/ */
public class DownloadManagerServiceTest extends InstrumentationTestCase { public class DownloadManagerServiceTest extends NativeLibraryTestBase {
private static final int UPDATE_DELAY_FOR_TEST = 1; private static final int UPDATE_DELAY_FOR_TEST = 1;
private static final int DELAY_BETWEEN_CALLS = 10; private static final int DELAY_BETWEEN_CALLS = 10;
private static final int LONG_UPDATE_DELAY_FOR_TEST = 500; private static final int LONG_UPDATE_DELAY_FOR_TEST = 500;
...@@ -315,6 +315,7 @@ public class DownloadManagerServiceTest extends InstrumentationTestCase { ...@@ -315,6 +315,7 @@ public class DownloadManagerServiceTest extends InstrumentationTestCase {
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
RecordHistogram.disableForTests(); RecordHistogram.disableForTests();
loadNativeLibraryAndInitBrowserProcess();
} }
private static Handler getTestHandler() { private static Handler getTestHandler() {
......
...@@ -245,7 +245,7 @@ public class DownloadNotificationServiceTest extends ...@@ -245,7 +245,7 @@ public class DownloadNotificationServiceTest extends
sharedPrefs, DownloadNotificationService.PENDING_DOWNLOAD_NOTIFICATIONS); sharedPrefs, DownloadNotificationService.PENDING_DOWNLOAD_NOTIFICATIONS);
assertEquals(3, entries.size()); assertEquals(3, entries.size());
service.notifyDownloadSuccessful(guid1, "success", 100L, false); service.notifyDownloadSuccessful(guid1, "/path/to/success", "success", 100L, false);
entries = DownloadManagerService.getStoredDownloadInfo( entries = DownloadManagerService.getStoredDownloadInfo(
sharedPrefs, DownloadNotificationService.PENDING_DOWNLOAD_NOTIFICATIONS); sharedPrefs, DownloadNotificationService.PENDING_DOWNLOAD_NOTIFICATIONS);
assertEquals(2, entries.size()); assertEquals(2, entries.size());
...@@ -270,7 +270,7 @@ public class DownloadNotificationServiceTest extends ...@@ -270,7 +270,7 @@ public class DownloadNotificationServiceTest extends
startNotificationService(); startNotificationService();
DownloadNotificationService service = bindNotificationService(); DownloadNotificationService service = bindNotificationService();
String guid = UUID.randomUUID().toString(); String guid = UUID.randomUUID().toString();
service.notifyDownloadSuccessful(guid, "test", 100L, false); service.notifyDownloadSuccessful(guid, "/path/to/test", "test", 100L, false);
assertEquals(1, getService().getNotificationIds().size()); assertEquals(1, getService().getNotificationIds().size());
} }
......
...@@ -441,3 +441,12 @@ content::DownloadManager* DownloadManagerService::GetDownloadManager( ...@@ -441,3 +441,12 @@ content::DownloadManager* DownloadManagerService::GetDownloadManager(
return manager; return manager;
} }
// static
jboolean IsSupportedMimeType(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& jmime_type) {
std::string mime_type = ConvertJavaStringToUTF8(env, jmime_type);
return mime_util::IsSupportedMimeType(mime_type);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment