Merge "Passing feedback for screenshots in SysUI" into qt-qpr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a681b9d205
@@ -369,6 +369,10 @@
|
|||||||
<receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
|
<receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<!-- Callback for invoking a smart action from the screenshot notification. -->
|
||||||
|
<receiver android:name=".screenshot.GlobalScreenshot$SmartActionsReceiver"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<!-- started from UsbDeviceSettingsManager -->
|
<!-- started from UsbDeviceSettingsManager -->
|
||||||
<activity android:name=".usb.UsbConfirmActivity"
|
<activity android:name=".usb.UsbConfirmActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import com.android.systemui.statusbar.phone.UnlockMethodCache;
|
|||||||
import com.android.systemui.statusbar.policy.KeyguardMonitor;
|
import com.android.systemui.statusbar.policy.KeyguardMonitor;
|
||||||
import com.android.systemui.volume.VolumeDialogComponent;
|
import com.android.systemui.volume.VolumeDialogComponent;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
@@ -116,7 +117,9 @@ public class SystemUIFactory {
|
|||||||
* This method is overridden in vendor specific implementation of Sys UI.
|
* This method is overridden in vendor specific implementation of Sys UI.
|
||||||
*/
|
*/
|
||||||
public ScreenshotNotificationSmartActionsProvider
|
public ScreenshotNotificationSmartActionsProvider
|
||||||
createScreenshotNotificationSmartActionsProvider() {
|
createScreenshotNotificationSmartActionsProvider(Context context,
|
||||||
|
Executor executor,
|
||||||
|
Handler uiHandler) {
|
||||||
return new ScreenshotNotificationSmartActionsProvider();
|
return new ScreenshotNotificationSmartActionsProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import android.graphics.Rect;
|
|||||||
import android.media.MediaActionSound;
|
import android.media.MediaActionSound;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
@@ -103,9 +104,12 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -143,6 +147,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
private static final String TAG = "SaveImageInBackgroundTask";
|
private static final String TAG = "SaveImageInBackgroundTask";
|
||||||
|
|
||||||
private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
|
private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
|
||||||
|
private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
|
||||||
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
|
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
|
||||||
|
|
||||||
private final SaveImageInBackgroundData mParams;
|
private final SaveImageInBackgroundData mParams;
|
||||||
@@ -153,8 +158,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
private final BigPictureStyle mNotificationStyle;
|
private final BigPictureStyle mNotificationStyle;
|
||||||
private final int mImageWidth;
|
private final int mImageWidth;
|
||||||
private final int mImageHeight;
|
private final int mImageHeight;
|
||||||
private final Handler mHandler;
|
|
||||||
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
|
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
|
||||||
|
private final String mScreenshotId;
|
||||||
|
private final boolean mSmartActionsEnabled;
|
||||||
|
private final Random mRandom = new Random();
|
||||||
|
|
||||||
SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
|
SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
|
||||||
NotificationManager nManager) {
|
NotificationManager nManager) {
|
||||||
@@ -165,11 +172,20 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
mImageTime = System.currentTimeMillis();
|
mImageTime = System.currentTimeMillis();
|
||||||
String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
|
String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
|
||||||
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
|
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
|
||||||
|
mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
|
||||||
|
|
||||||
// Initialize screenshot notification smart actions provider.
|
// Initialize screenshot notification smart actions provider.
|
||||||
mHandler = new Handler();
|
mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
|
SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
|
||||||
|
if (mSmartActionsEnabled) {
|
||||||
mSmartActionsProvider =
|
mSmartActionsProvider =
|
||||||
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider();
|
SystemUIFactory.getInstance()
|
||||||
|
.createScreenshotNotificationSmartActionsProvider(
|
||||||
|
context, THREAD_POOL_EXECUTOR, new Handler());
|
||||||
|
} else {
|
||||||
|
// If smart actions is not enabled use empty implementation.
|
||||||
|
mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
|
||||||
|
}
|
||||||
|
|
||||||
// Create the large notification icon
|
// Create the large notification icon
|
||||||
mImageWidth = data.image.getWidth();
|
mImageWidth = data.image.getWidth();
|
||||||
@@ -244,6 +260,38 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
mNotificationStyle.bigLargeIcon((Bitmap) null);
|
mNotificationStyle.bigLargeIcon((Bitmap) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Notification.Action> buildSmartActions(
|
||||||
|
List<Notification.Action> actions, Context context) {
|
||||||
|
List<Notification.Action> broadcastActions = new ArrayList<>();
|
||||||
|
for (Notification.Action action : actions) {
|
||||||
|
// Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
|
||||||
|
// for logging smart actions.
|
||||||
|
Bundle extras = action.getExtras();
|
||||||
|
String actionType = extras.getString(
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
|
||||||
|
Intent intent = new Intent(context,
|
||||||
|
GlobalScreenshot.SmartActionsReceiver.class).putExtra(
|
||||||
|
GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
|
||||||
|
addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
|
||||||
|
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
|
||||||
|
mRandom.nextInt(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
|
||||||
|
broadcastIntent).setContextual(true).addExtras(extras).build());
|
||||||
|
}
|
||||||
|
return broadcastActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
|
||||||
|
boolean smartActionsEnabled) {
|
||||||
|
intent
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
private int getUserHandleOfForegroundApplication(Context context) {
|
private int getUserHandleOfForegroundApplication(Context context) {
|
||||||
// This logic matches
|
// This logic matches
|
||||||
// com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
|
// com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
|
||||||
@@ -287,15 +335,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
|
|
||||||
Context context = mParams.context;
|
Context context = mParams.context;
|
||||||
Bitmap image = mParams.image;
|
Bitmap image = mParams.image;
|
||||||
boolean smartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
|
||||||
SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
|
|
||||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
|
||||||
GlobalScreenshot.getSmartActionsFuture(
|
|
||||||
context, image, mSmartActionsProvider, mHandler, smartActionsEnabled,
|
|
||||||
isManagedProfile(context));
|
|
||||||
Resources r = context.getResources();
|
Resources r = context.getResources();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||||
|
GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
|
||||||
|
mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
|
||||||
|
|
||||||
// Save the screenshot to the MediaStore
|
// Save the screenshot to the MediaStore
|
||||||
final MediaStore.PendingParams params = new MediaStore.PendingParams(
|
final MediaStore.PendingParams params = new MediaStore.PendingParams(
|
||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
|
||||||
@@ -318,6 +364,31 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
IoUtils.closeQuietly(session);
|
IoUtils.closeQuietly(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
|
||||||
|
|
||||||
|
mParams.imageUri = uri;
|
||||||
|
mParams.image = null;
|
||||||
|
mParams.errorMsgResId = 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// IOException/UnsupportedOperationException may be thrown if external storage is not
|
||||||
|
// mounted
|
||||||
|
Slog.e(TAG, "unable to save screenshot", e);
|
||||||
|
mParams.clearImage();
|
||||||
|
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recycle the bitmap data
|
||||||
|
if (image != null) {
|
||||||
|
image.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void populateNotificationActions(Context context, Resources r, Uri uri,
|
||||||
|
CompletableFuture<List<Notification.Action>> smartActionsFuture,
|
||||||
|
Notification.Builder notificationBuilder) {
|
||||||
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
|
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
|
||||||
// order to do some common work like dismissing the keyguard and sending
|
// order to do some common work like dismissing the keyguard and sending
|
||||||
// closeSystemWindows
|
// closeSystemWindows
|
||||||
@@ -350,12 +421,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
|
PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
|
||||||
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
|
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
|
||||||
.putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
|
.putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
|
||||||
.putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
|
.putExtra(EXTRA_DISALLOW_ENTER_PIP, true)
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
|
||||||
|
mSmartActionsEnabled),
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
|
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
|
||||||
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
|
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
|
||||||
R.drawable.ic_screenshot_share,
|
R.drawable.ic_screenshot_share,
|
||||||
r.getString(com.android.internal.R.string.share), shareAction);
|
r.getString(com.android.internal.R.string.share), shareAction);
|
||||||
mNotificationBuilder.addAction(shareActionBuilder.build());
|
notificationBuilder.addAction(shareActionBuilder.build());
|
||||||
|
|
||||||
// Create an edit intent, if a specific package is provided as the editor, then launch
|
// Create an edit intent, if a specific package is provided as the editor, then launch
|
||||||
// that directly
|
// that directly
|
||||||
@@ -373,53 +447,41 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
|
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
|
||||||
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
|
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
|
||||||
.putExtra(EXTRA_ACTION_INTENT, editIntent)
|
.putExtra(EXTRA_ACTION_INTENT, editIntent)
|
||||||
.putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
|
.putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null)
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
|
||||||
|
mSmartActionsEnabled),
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
|
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
|
||||||
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
|
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
|
||||||
R.drawable.ic_screenshot_edit,
|
R.drawable.ic_screenshot_edit,
|
||||||
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
|
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
|
||||||
mNotificationBuilder.addAction(editActionBuilder.build());
|
notificationBuilder.addAction(editActionBuilder.build());
|
||||||
|
|
||||||
// Create a delete action for the notification
|
// Create a delete action for the notification
|
||||||
PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
|
PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
|
||||||
new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
|
new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
|
||||||
.putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
|
.putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
|
||||||
|
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
|
||||||
|
mSmartActionsEnabled),
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
|
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
|
||||||
Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
|
Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
|
||||||
R.drawable.ic_screenshot_delete,
|
R.drawable.ic_screenshot_delete,
|
||||||
r.getString(com.android.internal.R.string.delete), deleteAction);
|
r.getString(com.android.internal.R.string.delete), deleteAction);
|
||||||
mNotificationBuilder.addAction(deleteActionBuilder.build());
|
notificationBuilder.addAction(deleteActionBuilder.build());
|
||||||
|
|
||||||
mParams.imageUri = uri;
|
if (mSmartActionsEnabled) {
|
||||||
mParams.image = null;
|
|
||||||
mParams.errorMsgResId = 0;
|
|
||||||
|
|
||||||
if (smartActionsEnabled) {
|
|
||||||
int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
|
int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags
|
SystemUiDeviceConfigFlags
|
||||||
.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
|
.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
|
||||||
1000);
|
1000);
|
||||||
List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(
|
List<Notification.Action> smartActions = buildSmartActions(
|
||||||
smartActionsFuture,
|
GlobalScreenshot.getSmartActions(mScreenshotId, smartActionsFuture,
|
||||||
timeoutMs);
|
timeoutMs, mSmartActionsProvider), context);
|
||||||
for (Notification.Action action : smartActions) {
|
for (Notification.Action action : smartActions) {
|
||||||
mNotificationBuilder.addAction(action);
|
notificationBuilder.addAction(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
// IOException/UnsupportedOperationException may be thrown if external storage is not
|
|
||||||
// mounted
|
|
||||||
Slog.e(TAG, "unable to save screenshot", e);
|
|
||||||
mParams.clearImage();
|
|
||||||
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recycle the bitmap data
|
|
||||||
if (image != null) {
|
|
||||||
image.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -504,6 +566,15 @@ class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GlobalScreenshot {
|
class GlobalScreenshot {
|
||||||
|
// These strings are used for communicating the action invoked to
|
||||||
|
// ScreenshotNotificationSmartActionsProvider.
|
||||||
|
static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
|
||||||
|
static final String EXTRA_ID = "android:screenshot_id";
|
||||||
|
static final String ACTION_TYPE_DELETE = "Delete";
|
||||||
|
static final String ACTION_TYPE_SHARE = "Share";
|
||||||
|
static final String ACTION_TYPE_EDIT = "Edit";
|
||||||
|
static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
|
||||||
|
|
||||||
static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
|
static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
|
||||||
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
|
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
|
||||||
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
|
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
|
||||||
@@ -963,9 +1034,9 @@ class GlobalScreenshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(Context context,
|
static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
|
||||||
Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
|
Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
|
||||||
Handler handler, boolean smartActionsEnabled, boolean isManagedProfile) {
|
boolean smartActionsEnabled, boolean isManagedProfile) {
|
||||||
if (!smartActionsEnabled) {
|
if (!smartActionsEnabled) {
|
||||||
Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
|
Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
|
||||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||||
@@ -979,6 +1050,7 @@ class GlobalScreenshot {
|
|||||||
|
|
||||||
Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
|
Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
|
||||||
CompletableFuture<List<Notification.Action>> smartActionsFuture;
|
CompletableFuture<List<Notification.Action>> smartActionsFuture;
|
||||||
|
long startTimeMs = SystemClock.uptimeMillis();
|
||||||
try {
|
try {
|
||||||
ActivityManager.RunningTaskInfo runningTask =
|
ActivityManager.RunningTaskInfo runningTask =
|
||||||
ActivityManagerWrapper.getInstance().getRunningTask();
|
ActivityManagerWrapper.getInstance().getRunningTask();
|
||||||
@@ -986,34 +1058,74 @@ class GlobalScreenshot {
|
|||||||
(runningTask != null && runningTask.topActivity != null)
|
(runningTask != null && runningTask.topActivity != null)
|
||||||
? runningTask.topActivity
|
? runningTask.topActivity
|
||||||
: new ComponentName("", "");
|
: new ComponentName("", "");
|
||||||
smartActionsFuture = smartActionsProvider.getActions(image, context,
|
smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
|
||||||
THREAD_POOL_EXECUTOR,
|
|
||||||
handler,
|
|
||||||
componentName,
|
componentName,
|
||||||
isManagedProfile);
|
isManagedProfile);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
|
||||||
smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
|
smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
|
||||||
Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
|
Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
|
||||||
|
notifyScreenshotOp(screenshotId, smartActionsProvider,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
|
||||||
|
waitTimeMs);
|
||||||
}
|
}
|
||||||
return smartActionsFuture;
|
return smartActionsFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static List<Notification.Action> getSmartActions(
|
static List<Notification.Action> getSmartActions(String screenshotId,
|
||||||
CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs) {
|
CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
|
||||||
try {
|
ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
|
||||||
long startTimeMs = SystemClock.uptimeMillis();
|
long startTimeMs = SystemClock.uptimeMillis();
|
||||||
|
try {
|
||||||
List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
|
List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
|
||||||
TimeUnit.MILLISECONDS);
|
TimeUnit.MILLISECONDS);
|
||||||
|
long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
|
||||||
Slog.d(TAG, String.format("Wait time for smart actions: %d ms",
|
Slog.d(TAG, String.format("Wait time for smart actions: %d ms",
|
||||||
SystemClock.uptimeMillis() - startTimeMs));
|
waitTimeMs));
|
||||||
|
notifyScreenshotOp(screenshotId, smartActionsProvider,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
|
||||||
|
waitTimeMs);
|
||||||
return actions;
|
return actions;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Slog.e(TAG, "Failed to obtain screenshot notification smart actions.", e);
|
long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
|
||||||
|
Slog.d(TAG, "Failed to obtain screenshot notification smart actions.", e);
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
|
||||||
|
(e instanceof TimeoutException)
|
||||||
|
? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
|
||||||
|
: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
|
||||||
|
notifyScreenshotOp(screenshotId, smartActionsProvider,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
|
||||||
|
status, waitTimeMs);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void notifyScreenshotOp(String screenshotId,
|
||||||
|
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
|
||||||
|
ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
|
||||||
|
try {
|
||||||
|
smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notifyScreenshotAction(Context context, String screenshotId, String action,
|
||||||
|
boolean isSmartAction) {
|
||||||
|
try {
|
||||||
|
ScreenshotNotificationSmartActionsProvider provider =
|
||||||
|
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
|
||||||
|
context, THREAD_POOL_EXECUTOR, new Handler());
|
||||||
|
provider.notifyAction(screenshotId, action, isSmartAction);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receiver to proxy the share or edit intent, used to clean up the notification and send
|
* Receiver to proxy the share or edit intent, used to clean up the notification and send
|
||||||
* appropriate signals to the system (ie. to dismiss the keyguard if necessary).
|
* appropriate signals to the system (ie. to dismiss the keyguard if necessary).
|
||||||
@@ -1023,6 +1135,7 @@ class GlobalScreenshot {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, final Intent intent) {
|
public void onReceive(Context context, final Intent intent) {
|
||||||
|
Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
|
||||||
Runnable startActivityRunnable = () -> {
|
Runnable startActivityRunnable = () -> {
|
||||||
try {
|
try {
|
||||||
ActivityManagerWrapper.getInstance().closeSystemWindows(
|
ActivityManagerWrapper.getInstance().closeSystemWindows(
|
||||||
@@ -1033,7 +1146,6 @@ class GlobalScreenshot {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
|
|
||||||
if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
|
if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
|
||||||
cancelScreenshotNotification(context);
|
cancelScreenshotNotification(context);
|
||||||
}
|
}
|
||||||
@@ -1045,6 +1157,14 @@ class GlobalScreenshot {
|
|||||||
StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
|
StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
|
||||||
statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
|
statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
|
||||||
true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
|
true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
|
||||||
|
|
||||||
|
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
|
||||||
|
String actionType = Intent.ACTION_EDIT.equals(actionIntent.getAction())
|
||||||
|
? ACTION_TYPE_EDIT
|
||||||
|
: ACTION_TYPE_SHARE;
|
||||||
|
notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
|
||||||
|
actionType, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1075,6 +1195,29 @@ class GlobalScreenshot {
|
|||||||
// And delete the image from the media store
|
// And delete the image from the media store
|
||||||
final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
|
final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
|
||||||
new DeleteImageInBackgroundTask(context).execute(uri);
|
new DeleteImageInBackgroundTask(context).execute(uri);
|
||||||
|
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
|
||||||
|
notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
|
||||||
|
ACTION_TYPE_DELETE,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the smart action tapped by the user in the notification.
|
||||||
|
*/
|
||||||
|
public static class SmartActionsReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
|
||||||
|
ActivityOptions opts = ActivityOptions.makeBasic();
|
||||||
|
context.startActivityAsUser(actionIntent.getIntent(), opts.toBundle(),
|
||||||
|
UserHandle.CURRENT);
|
||||||
|
|
||||||
|
Slog.d(TAG, "Screenshot notification smart action is invoked.");
|
||||||
|
notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
|
||||||
|
intent.getStringExtra(EXTRA_ACTION_TYPE),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,41 +18,84 @@ package com.android.systemui.screenshot;
|
|||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class can be overridden by a vendor-specific sys UI implementation,
|
* This class can be overridden by a vendor-specific sys UI implementation,
|
||||||
* in order to provide smart actions in the screenshot notification.
|
* in order to provide smart actions in the screenshot notification.
|
||||||
*/
|
*/
|
||||||
public class ScreenshotNotificationSmartActionsProvider {
|
public class ScreenshotNotificationSmartActionsProvider {
|
||||||
|
/* Key provided in the notification action to get the type of smart action. */
|
||||||
|
public static final String ACTION_TYPE = "action_type";
|
||||||
|
public static final String DEFAULT_ACTION_TYPE = "Smart Action";
|
||||||
|
|
||||||
|
/* Define phases of screenshot execution. */
|
||||||
|
protected enum ScreenshotOp {
|
||||||
|
OP_UNKNOWN,
|
||||||
|
RETRIEVE_SMART_ACTIONS,
|
||||||
|
REQUEST_SMART_ACTIONS,
|
||||||
|
WAIT_FOR_SMART_ACTIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enum to report success or failure for screenshot execution phases. */
|
||||||
|
protected enum ScreenshotOpStatus {
|
||||||
|
OP_STATUS_UNKNOWN,
|
||||||
|
SUCCESS,
|
||||||
|
ERROR,
|
||||||
|
TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
private static final String TAG = "ScreenshotActions";
|
private static final String TAG = "ScreenshotActions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation that returns an empty list.
|
* Default implementation that returns an empty list.
|
||||||
* This method is overridden in vendor-specific Sys UI implementation.
|
* This method is overridden in vendor-specific Sys UI implementation.
|
||||||
*
|
*
|
||||||
|
* @param screenshotId A generated random unique id for the screenshot.
|
||||||
* @param bitmap The bitmap of the screenshot. The bitmap config must be {@link
|
* @param bitmap The bitmap of the screenshot. The bitmap config must be {@link
|
||||||
* HARDWARE}.
|
* HARDWARE}.
|
||||||
* @param context The current app {@link Context}.
|
|
||||||
* @param executor A {@link Executor} that can be used to execute tasks in parallel.
|
|
||||||
* @param handler A {@link Handler} to possibly run UI-thread code.
|
|
||||||
* @param componentName Contains package and activity class names where the screenshot was
|
* @param componentName Contains package and activity class names where the screenshot was
|
||||||
* taken. This is used as an additional signal to generate and rank more
|
* taken. This is used as an additional signal to generate and rank more
|
||||||
* relevant actions.
|
* relevant actions.
|
||||||
* @param isManagedProfile The screenshot was taken for a work profile app.
|
* @param isManagedProfile The screenshot was taken for a work profile app.
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<List<Notification.Action>> getActions(Bitmap bitmap, Context context,
|
public CompletableFuture<List<Notification.Action>> getActions(
|
||||||
Executor executor, Handler handler, ComponentName componentName,
|
String screenshotId,
|
||||||
|
Bitmap bitmap,
|
||||||
|
ComponentName componentName,
|
||||||
boolean isManagedProfile) {
|
boolean isManagedProfile) {
|
||||||
Log.d(TAG, "Returning empty smart action list.");
|
Log.d(TAG, "Returning empty smart action list.");
|
||||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify exceptions and latency encountered during generating smart actions.
|
||||||
|
* This method is overridden in vendor-specific Sys UI implementation.
|
||||||
|
*
|
||||||
|
* @param screenshotId Unique id of the screenshot.
|
||||||
|
* @param op screenshot execution phase defined in {@link ScreenshotOp}
|
||||||
|
* @param status {@link ScreenshotOpStatus} to report success or failure.
|
||||||
|
* @param durationMs latency experienced in different phases of screenshots.
|
||||||
|
*/
|
||||||
|
public void notifyOp(String screenshotId, ScreenshotOp op, ScreenshotOpStatus status,
|
||||||
|
long durationMs) {
|
||||||
|
Log.d(TAG, "Return without notify.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify screenshot notification action invoked.
|
||||||
|
* This method is overridden in vendor-specific Sys UI implementation.
|
||||||
|
*
|
||||||
|
* @param screenshotId Unique id of the screenshot.
|
||||||
|
* @param action type of notification action invoked.
|
||||||
|
* @param isSmartAction whether action invoked was a smart action.
|
||||||
|
*/
|
||||||
|
public void notifyAction(String screenshotId, String action, boolean isSmartAction) {
|
||||||
|
Log.d(TAG, "Return without notify.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,12 @@
|
|||||||
|
|
||||||
package com.android.systemui.screenshot;
|
package com.android.systemui.screenshot;
|
||||||
|
|
||||||
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
@@ -25,14 +29,20 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
import com.android.systemui.SystemUIFactory;
|
import com.android.systemui.SystemUIFactory;
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
|
import com.android.systemui.util.NotificationChannels;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -70,12 +80,11 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
||||||
ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
|
ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
|
||||||
ScreenshotNotificationSmartActionsProvider.class);
|
ScreenshotNotificationSmartActionsProvider.class);
|
||||||
when(smartActionsProvider.getActions(any(), any(), any(), any(), any(),
|
when(smartActionsProvider.getActions(any(), any(), any(),
|
||||||
eq(false))).thenThrow(
|
eq(false))).thenThrow(RuntimeException.class);
|
||||||
RuntimeException.class);
|
|
||||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
|
GlobalScreenshot.getSmartActionsFuture("", bitmap,
|
||||||
smartActionsProvider, mHandler, true, false);
|
smartActionsProvider, true, false);
|
||||||
Assert.assertNotNull(smartActionsFuture);
|
Assert.assertNotNull(smartActionsFuture);
|
||||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||||
Assert.assertEquals(Collections.emptyList(), smartActions);
|
Assert.assertEquals(Collections.emptyList(), smartActions);
|
||||||
@@ -92,10 +101,18 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
|
when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
|
||||||
RuntimeException.class);
|
RuntimeException.class);
|
||||||
List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
|
List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
|
||||||
smartActionsFuture, timeoutMs);
|
"", smartActionsFuture, timeoutMs, mSmartActionsProvider);
|
||||||
Assert.assertEquals(Collections.emptyList(), actions);
|
Assert.assertEquals(Collections.emptyList(), actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests any exception thrown in notifying feedback does not affect regular screenshot flow.
|
||||||
|
@Test
|
||||||
|
public void testExceptionHandlingInNotifyingFeedback() {
|
||||||
|
doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(),
|
||||||
|
anyLong());
|
||||||
|
GlobalScreenshot.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
// Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
|
// Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
|
||||||
// and a completed future is returned.
|
// and a completed future is returned.
|
||||||
@Test
|
@Test
|
||||||
@@ -104,9 +121,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
Bitmap bitmap = mock(Bitmap.class);
|
Bitmap bitmap = mock(Bitmap.class);
|
||||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
|
when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
|
||||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
|
GlobalScreenshot.getSmartActionsFuture("", bitmap,
|
||||||
mSmartActionsProvider, mHandler, true, true);
|
mSmartActionsProvider, true, true);
|
||||||
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any(),
|
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(),
|
||||||
eq(false));
|
eq(false));
|
||||||
Assert.assertNotNull(smartActionsFuture);
|
Assert.assertNotNull(smartActionsFuture);
|
||||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||||
@@ -118,10 +135,10 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
|
public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
|
||||||
Bitmap bitmap = mock(Bitmap.class);
|
Bitmap bitmap = mock(Bitmap.class);
|
||||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
||||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap, mSmartActionsProvider,
|
GlobalScreenshot.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
|
||||||
mHandler, true, true);
|
true, true);
|
||||||
verify(mSmartActionsProvider, times(1))
|
verify(mSmartActionsProvider, times(1))
|
||||||
.getActions(any(), any(), any(), any(), any(), eq(true));
|
.getActions(any(), any(), any(), eq(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests for a hardware bitmap, a completed future is returned.
|
// Tests for a hardware bitmap, a completed future is returned.
|
||||||
@@ -131,13 +148,64 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
Bitmap bitmap = mock(Bitmap.class);
|
Bitmap bitmap = mock(Bitmap.class);
|
||||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
||||||
ScreenshotNotificationSmartActionsProvider actionsProvider =
|
ScreenshotNotificationSmartActionsProvider actionsProvider =
|
||||||
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider();
|
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
|
||||||
|
mContext, null, mHandler);
|
||||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
|
GlobalScreenshot.getSmartActionsFuture("", bitmap,
|
||||||
actionsProvider,
|
actionsProvider,
|
||||||
mHandler, true, true);
|
true, true);
|
||||||
Assert.assertNotNull(smartActionsFuture);
|
Assert.assertNotNull(smartActionsFuture);
|
||||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||||
Assert.assertEquals(smartActions.size(), 0);
|
Assert.assertEquals(smartActions.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests for notification action extras.
|
||||||
|
@Test
|
||||||
|
public void testNotificationActionExtras() {
|
||||||
|
if (Looper.myLooper() == null) {
|
||||||
|
Looper.prepare();
|
||||||
|
}
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
|
||||||
|
data.context = mContext;
|
||||||
|
data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
|
||||||
|
data.iconSize = 10;
|
||||||
|
data.finisher = null;
|
||||||
|
data.previewWidth = 10;
|
||||||
|
data.previewheight = 10;
|
||||||
|
SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data,
|
||||||
|
notificationManager);
|
||||||
|
Uri uri = Uri.parse("Screenshot_123.png");
|
||||||
|
Notification.Builder notificationBuilder = new Notification.Builder(mContext,
|
||||||
|
NotificationChannels.SCREENSHOTS_HEADSUP);
|
||||||
|
task.populateNotificationActions(mContext, mContext.getResources(),
|
||||||
|
uri,
|
||||||
|
CompletableFuture.completedFuture(Collections.emptyList()), notificationBuilder);
|
||||||
|
|
||||||
|
Notification notification = notificationBuilder.build();
|
||||||
|
Assert.assertEquals(notification.actions.length, 3);
|
||||||
|
boolean isShareFound = false;
|
||||||
|
boolean isEditFound = false;
|
||||||
|
boolean isDeleteFound = false;
|
||||||
|
for (Notification.Action action : notification.actions) {
|
||||||
|
Intent intent = action.actionIntent.getIntent();
|
||||||
|
Intent actionIntent = intent.getParcelableExtra(GlobalScreenshot.EXTRA_ACTION_INTENT);
|
||||||
|
Assert.assertNotNull(intent);
|
||||||
|
Bundle bundle = intent.getExtras();
|
||||||
|
Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
|
||||||
|
Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
|
||||||
|
if (uri.toString().equals(bundle.getString(GlobalScreenshot.SCREENSHOT_URI_ID))) {
|
||||||
|
isDeleteFound = true;
|
||||||
|
} else if (Intent.ACTION_EDIT.equals(actionIntent.getAction())) {
|
||||||
|
isEditFound = true;
|
||||||
|
} else if (Intent.ACTION_CHOOSER.equals(actionIntent.getAction())) {
|
||||||
|
isShareFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertTrue(isEditFound);
|
||||||
|
Assert.assertTrue(isShareFound);
|
||||||
|
Assert.assertTrue(isDeleteFound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user