Commit 0f02ee54 authored by Jeffrey Cohen's avatar Jeffrey Cohen Committed by Commit Bot

[Screenshot] enable saving screenshot as a download

https://hsv.googleplex.com/4770250551721984

Bug: 1093374
Change-Id: I7405ba988173d0266ea122572419b7a028680fa2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2236470Reviewed-by: default avatarTravis Skare <skare@chromium.org>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarGayane Petrosyan <gayane@chromium.org>
Reviewed-by: default avatarKristi Park <kristipark@chromium.org>
Commit-Queue: Jeffrey Cohen <jeffreycohen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#779904}
parent f6ad69c1
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.share;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.core.app.NotificationCompat;
import org.chromium.base.IntentUtils;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.media.MediaViewerUtils;
import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
import org.chromium.chrome.browser.notifications.NotificationConstants;
import org.chromium.chrome.browser.notifications.NotificationIntentInterceptor;
import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
import org.chromium.components.browser_ui.notifications.ChromeNotificationBuilder;
import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
/**
* Manages save image related notifications for Android.
*/
public class SaveImageNotificationManager {
private static final String TAG = "share";
private static final String MIME_TYPE = "image/JPEG";
private static final String EXTRA_INTENT_TYPE =
"org.chromium.chrome.browser.share.SaveImageNotificationManager.EXTRA_INTENT_TYPE";
private static final String EXTRA_ID =
"org.chromium.chrome.browser.share.SaveImageNotificationManager.EXTRA_ID";
private static int sNextId;
/**
* Handles the tapping of a notification by starting an intent to view downloaded content.
*/
public static final class TapReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int intentType = IntentUtils.safeGetIntExtra(
intent, EXTRA_INTENT_TYPE, NotificationIntentInterceptor.IntentType.UNKNOWN);
int id = IntentUtils.safeGetIntExtra(
intent, EXTRA_ID, NotificationIntentInterceptor.IntentType.UNKNOWN);
switch (intentType) {
case NotificationIntentInterceptor.IntentType.UNKNOWN:
break;
case NotificationIntentInterceptor.IntentType.CONTENT_INTENT:
Uri uri = intent.getData();
Intent viewIntent = MediaViewerUtils.getMediaViewerIntent(
uri, uri, Intent.normalizeMimeType(MIME_TYPE), true);
IntentHandler.startActivityForTrustedIntent(viewIntent);
close(context, id);
RecordUserAction.record("SharingQRCode.SuccessNotificationTapped");
break;
case NotificationIntentInterceptor.IntentType.DELETE_INTENT:
RecordUserAction.record("SharingQRCode.FailureNotificationTapped");
close(context, id);
}
}
}
/**
* Displays a notification for successful download.
*
* @param context The Context to use for accessing notification manager.
* @param uri The Uri of the notification to show.
*/
public static void showSuccessNotification(Context context, Uri uri) {
String notificationTitle =
context.getResources().getString(R.string.qr_code_successful_download_title);
String notificationText =
context.getResources().getString(R.string.qr_code_successful_download_text);
int iconId = R.drawable.offline_pin;
showNotification(context, uri, notificationTitle, notificationText, iconId,
NotificationIntentInterceptor.IntentType.CONTENT_INTENT);
}
/**
* Displays a notification for failed download.
*
* @param context The Context to use for accessing notification manager.
* @param uri The Uri of the notification to hide.
*/
public static void showFailureNotification(Context context, Uri uri) {
String notificationTitle =
context.getResources().getString(R.string.qr_code_failed_download_title);
String notificationText =
context.getResources().getString(R.string.qr_code_failed_download_text);
int iconId = android.R.drawable.stat_sys_download_done;
showNotification(context, uri, notificationTitle, notificationText, iconId,
NotificationIntentInterceptor.IntentType.DELETE_INTENT);
}
/**
* Displays a notification.
*
* @param context The Context to use for accessing notification manager.
* @param uri The Uri of the notification to hide.
* @param title The title of the notification.
* @param text The text of the notification.
* @param iconId The resource id for icon of the notification.
* @param intentType The type of the notification.
*/
private static void showNotification(
Context context, Uri uri, String title, String text, int iconId, int intentType) {
PendingIntentProvider contentIntent = PendingIntentProvider.getBroadcast(context, sNextId++,
new Intent(context, SaveImageNotificationManager.TapReceiver.class)
.setData(uri)
.putExtra(EXTRA_INTENT_TYPE, intentType)
.putExtra(EXTRA_ID, sNextId),
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManagerProxy manager = new NotificationManagerProxyImpl(context);
// Build the notification.
ChromeNotificationBuilder builder =
NotificationBuilderFactory
.createChromeNotificationBuilder(true,
ChromeChannelDefinitions.ChannelId.SHARING, null,
new NotificationMetadata(
NotificationUmaTracker.SystemNotificationType
.SHARE_SAVE_IMAGE,
NotificationConstants.GROUP_SHARE_SAVE_IMAGE, sNextId))
.setContentIntent(contentIntent)
.setContentTitle(title)
.setContentText(text)
.setGroup(NotificationConstants.GROUP_SHARE_SAVE_IMAGE)
.setPriorityBeforeO(NotificationCompat.PRIORITY_HIGH)
.setVibrate(new long[0])
.setSmallIcon(iconId)
.setDefaults(Notification.DEFAULT_ALL);
manager.notify(builder.buildChromeNotification());
}
/**
* Closes a notification.
*
* @param context The Context to use for accessing notification manager.
* @param guid The GUID of the notification to hide.
*/
private static void close(Context context, int id) {
new NotificationManagerProxyImpl(context).cancel(
NotificationConstants.GROUP_SHARE_SAVE_IMAGE, id);
}
}
...@@ -18,6 +18,7 @@ import java.util.Arrays; ...@@ -18,6 +18,7 @@ import java.util.Arrays;
* Coordinator for displaying the screenshot share sheet. * Coordinator for displaying the screenshot share sheet.
*/ */
public class ScreenshotShareSheetCoordinator { public class ScreenshotShareSheetCoordinator {
private final ScreenshotShareSheetSaveDelegate mSaveDelegate;
private final ScreenshotShareSheetMediator mMediator; private final ScreenshotShareSheetMediator mMediator;
private final PropertyModel mModel; private final PropertyModel mModel;
...@@ -36,7 +37,9 @@ public class ScreenshotShareSheetCoordinator { ...@@ -36,7 +37,9 @@ public class ScreenshotShareSheetCoordinator {
mModel = new PropertyModel(allProperties); mModel = new PropertyModel(allProperties);
mModel.set(ScreenshotShareSheetViewProperties.SCREENSHOT_BITMAP, screenshot); mModel.set(ScreenshotShareSheetViewProperties.SCREENSHOT_BITMAP, screenshot);
mMediator = new ScreenshotShareSheetMediator(mModel, deleteRunnable); mSaveDelegate = new ScreenshotShareSheetSaveDelegate(context, mModel);
mMediator = new ScreenshotShareSheetMediator(
context, mModel, deleteRunnable, mSaveDelegate::save);
PropertyModelChangeProcessor.create( PropertyModelChangeProcessor.create(
mModel, screenshotShareSheetView, ScreenshotShareSheetViewBinder::bind); mModel, screenshotShareSheetView, ScreenshotShareSheetViewBinder::bind);
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.share.screenshot; package org.chromium.chrome.browser.share.screenshot;
import android.content.Context;
import org.chromium.chrome.browser.share.screenshot.ScreenshotShareSheetViewProperties.NoArgOperation; import org.chromium.chrome.browser.share.screenshot.ScreenshotShareSheetViewProperties.NoArgOperation;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
...@@ -13,15 +15,21 @@ import org.chromium.ui.modelutil.PropertyModel; ...@@ -13,15 +15,21 @@ import org.chromium.ui.modelutil.PropertyModel;
*/ */
class ScreenshotShareSheetMediator { class ScreenshotShareSheetMediator {
private final PropertyModel mModel; private final PropertyModel mModel;
private final Context mContext;
private final Runnable mDeleteRunnable; private final Runnable mDeleteRunnable;
private final Runnable mSaveRunnable;
/** /**
* The ScreenshotShareSheetMediator constructor. * The ScreenshotShareSheetMediator constructor.
* @param context The context to use. * @param context The context to use.
* @param propertyModel The property modelto use to communicate with views. * @param propertyModel The property model to use to communicate with views.
* @param deleteRunnable The action to take when cancel or delete is called.
*/ */
ScreenshotShareSheetMediator(PropertyModel propertyModel, Runnable deleteRunnable) { ScreenshotShareSheetMediator(Context context, PropertyModel propertyModel,
Runnable deleteRunnable, Runnable saveRunnable) {
mDeleteRunnable = deleteRunnable; mDeleteRunnable = deleteRunnable;
mSaveRunnable = saveRunnable;
mContext = context;
mModel = propertyModel; mModel = propertyModel;
mModel.set(ScreenshotShareSheetViewProperties.NO_ARG_OPERATION_LISTENER, mModel.set(ScreenshotShareSheetViewProperties.NO_ARG_OPERATION_LISTENER,
operation -> { performNoArgOperation(operation); }); operation -> { performNoArgOperation(operation); });
...@@ -37,7 +45,7 @@ class ScreenshotShareSheetMediator { ...@@ -37,7 +45,7 @@ class ScreenshotShareSheetMediator {
if (NoArgOperation.SHARE == operation) { if (NoArgOperation.SHARE == operation) {
share(); share();
} else if (NoArgOperation.SAVE == operation) { } else if (NoArgOperation.SAVE == operation) {
save(); mSaveRunnable.run();
} else if (NoArgOperation.DELETE == operation) { } else if (NoArgOperation.DELETE == operation) {
mDeleteRunnable.run(); mDeleteRunnable.run();
} }
...@@ -49,11 +57,4 @@ class ScreenshotShareSheetMediator { ...@@ -49,11 +57,4 @@ class ScreenshotShareSheetMediator {
private void share() { private void share() {
// TODO(crbug/1024586): export image // TODO(crbug/1024586): export image
} }
/**
* Saves the current image.
*/
private void save() {
// TODO(crbug/1024586):save image
}
} }
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.share.screenshot;
import android.content.Context;
import android.graphics.Bitmap;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.share.BitmapDownloadRequest;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.ui.modelutil.PropertyModel;
/**
* ScreenshotShareSheetSaveDelegate is in charge of download the current bitmap.
*/
class ScreenshotShareSheetSaveDelegate {
private final PropertyModel mModel;
private final Context mContext;
/**
* The ScreenshotShareSheetSaveDelegate constructor.
* @param context The context to use.
* @param propertyModel The property model to use to communicate with views.
*/
ScreenshotShareSheetSaveDelegate(Context context, PropertyModel propertyModel) {
mContext = context;
mModel = propertyModel;
}
/**
* Saves the current image.
*/
protected void save() {
Bitmap bitmap = mModel.get(ScreenshotShareSheetViewProperties.SCREENSHOT_BITMAP);
if (bitmap != null) {
String url;
String fileName = mContext.getString(R.string.screenshot_filename_prefix,
String.valueOf(System.currentTimeMillis()));
Tab tab = ((ChromeActivity) mContext).getActivityTabProvider().get();
if (tab != null) {
url = tab.getUrl().getSpec();
}
BitmapDownloadRequest.downloadBitmap(fileName, bitmap);
}
}
}
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
# dependencies are removed. # dependencies are removed.
share_java_sources = [ share_java_sources = [
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/BitmapDownloadRequest.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/BitmapDownloadRequest.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/SaveImageNotificationManager.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProvider.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProvider.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QRCodeGenerationRequest.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QRCodeGenerationRequest.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java",
...@@ -31,6 +30,7 @@ share_java_sources = [ ...@@ -31,6 +30,7 @@ share_java_sources = [
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetDialog.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetDialog.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetDialogCoordinator.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetDialogCoordinator.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetMediator.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetMediator.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetSaveDelegate.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetView.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetView.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewBinder.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewBinder.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewProperties.java", "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewProperties.java",
......
...@@ -7,6 +7,8 @@ package org.chromium.chrome.browser.share.screenshot; ...@@ -7,6 +7,8 @@ package org.chromium.chrome.browser.share.screenshot;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.app.Activity;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
...@@ -38,6 +40,12 @@ public class ScreenshotShareSheetMediatorUnitTest { ...@@ -38,6 +40,12 @@ public class ScreenshotShareSheetMediatorUnitTest {
@Mock @Mock
Runnable mDeleteRunnable; Runnable mDeleteRunnable;
@Mock
Runnable mSaveRunnable;
@Mock
Activity mContext;
private PropertyModel mModel; private PropertyModel mModel;
private ScreenshotShareSheetMediator mMediator; private ScreenshotShareSheetMediator mMediator;
...@@ -46,10 +54,12 @@ public class ScreenshotShareSheetMediatorUnitTest { ...@@ -46,10 +54,12 @@ public class ScreenshotShareSheetMediatorUnitTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
doNothing().when(mDeleteRunnable).run(); doNothing().when(mDeleteRunnable).run();
doNothing().when(mSaveRunnable).run();
mModel = new PropertyModel(ScreenshotShareSheetViewProperties.ALL_KEYS); mModel = new PropertyModel(ScreenshotShareSheetViewProperties.ALL_KEYS);
mMediator = new ScreenshotShareSheetMediator(mModel, mDeleteRunnable); mMediator =
new ScreenshotShareSheetMediator(mContext, mModel, mDeleteRunnable, mSaveRunnable);
} }
@Test @Test
public void onClickDelete() { public void onClickDelete() {
...@@ -59,6 +69,16 @@ public class ScreenshotShareSheetMediatorUnitTest { ...@@ -59,6 +69,16 @@ public class ScreenshotShareSheetMediatorUnitTest {
verify(mDeleteRunnable).run(); verify(mDeleteRunnable).run();
} }
@Test
public void onClickSave() {
Callback<Integer> callback =
mModel.get(ScreenshotShareSheetViewProperties.NO_ARG_OPERATION_LISTENER);
callback.onResult(ScreenshotShareSheetViewProperties.NoArgOperation.SAVE);
verify(mSaveRunnable).run();
}
@After @After
public void tearDown() {} public void tearDown() {}
} }
...@@ -3718,22 +3718,6 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p ...@@ -3718,22 +3718,6 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
chrome_qrcode_<ph name="CURRENT_TIMESTAMP_MS">%1$s<ex>1582667748515</ex></ph> chrome_qrcode_<ph name="CURRENT_TIMESTAMP_MS">%1$s<ex>1582667748515</ex></ph>
</message> </message>
<message name="IDS_QR_CODE_SUCCESSFUL_DOWNLOAD_TITLE" desc="Notification title for successful QR code download.">
QR code downloaded
</message>
<message name="IDS_QR_CODE_SUCCESSFUL_DOWNLOAD_TEXT" desc="Notification text for successful QR code download.">
Tap to open QR code
</message>
<message name="IDS_QR_CODE_FAILED_DOWNLOAD_TITLE" desc="Notification title for failed QR code download.">
Can't download QR code
</message>
<message name="IDS_QR_CODE_FAILED_DOWNLOAD_TEXT" desc="Notification text for failed QR code download.">
Something went wrong
</message>
<!-- Share Screenshot strings --> <!-- Share Screenshot strings -->
<message name="IDS_SCREENSHOT_EDIT_TITLE" desc="The text shown on the share option for screenshots."> <message name="IDS_SCREENSHOT_EDIT_TITLE" desc="The text shown on the share option for screenshots.">
Edit Edit
...@@ -3751,6 +3735,10 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p ...@@ -3751,6 +3735,10 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
Share Share
</message> </message>
<message name="IDS_SCREENSHOT_FILENAME_PREFIX" desc="File name prefix for downloaded screenshot that is followed by timestamp.">
chrome_screenshot_<ph name="CURRENT_TIMESTAMP_MS">%1$s<ex>1582667748515</ex></ph>
</message>
<!-- Chime DFM module strings --> <!-- Chime DFM module strings -->
<message name="IDS_CHIME_MODULE_TITLE" desc="Text shown when the chime module is referenced in install start, success, failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to 'Installing Google Notifications Platform for Chrome…')."> <message name="IDS_CHIME_MODULE_TITLE" desc="Text shown when the chime module is referenced in install start, success, failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to 'Installing Google Notifications Platform for Chrome…').">
Google Notifications Platform Google Notifications Platform
......
...@@ -20166,6 +20166,7 @@ should be able to be added at any place in this file. ...@@ -20166,6 +20166,7 @@ should be able to be added at any place in this file.
</action> </action>
<action name="SharingQRCode.FailureNotificationTapped"> <action name="SharingQRCode.FailureNotificationTapped">
<obsolete>Deprecated 6/2020</obsolete>
<owner>tgupta@chromium.org</owner> <owner>tgupta@chromium.org</owner>
<owner>src/components/send_tab_to_self/OWNERS</owner> <owner>src/components/send_tab_to_self/OWNERS</owner>
<description> <description>
...@@ -20190,6 +20191,7 @@ should be able to be added at any place in this file. ...@@ -20190,6 +20191,7 @@ should be able to be added at any place in this file.
</action> </action>
<action name="SharingQRCode.SuccessNotificationTapped"> <action name="SharingQRCode.SuccessNotificationTapped">
<obsolete>Deprecated 6/2020</obsolete>
<owner>tgupta@chromium.org</owner> <owner>tgupta@chromium.org</owner>
<owner>src/components/send_tab_to_self/OWNERS</owner> <owner>src/components/send_tab_to_self/OWNERS</owner>
<description> <description>
......
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