Loading packages/SystemUI/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -369,6 +369,10 @@ <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver" 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 --> <activity android:name=".usb.UsbConfirmActivity" android:exported="true" Loading packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +5 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.concurrent.Executor; import dagger.Module; import dagger.Provides; Loading Loading @@ -124,7 +126,9 @@ public class SystemUIFactory { * This method is overridden in vendor specific implementation of Sys UI. */ public ScreenshotNotificationSmartActionsProvider createScreenshotNotificationSmartActionsProvider() { createScreenshotNotificationSmartActionsProvider(Context context, Executor executor, Handler uiHandler) { return new ScreenshotNotificationSmartActionsProvider(); } Loading packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +92 −11 Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.SystemUIFactory; import com.android.systemui.dagger.qualifiers.MainResources; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.phone.StatusBar; Loading Loading @@ -126,8 +127,17 @@ public class GlobalScreenshot { } } static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; // 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 EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; Loading Loading @@ -689,9 +699,9 @@ public class GlobalScreenshot { } @VisibleForTesting static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(Context context, static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId, Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider, Handler handler, boolean smartActionsEnabled, boolean isManagedProfile) { boolean smartActionsEnabled, boolean isManagedProfile) { if (!smartActionsEnabled) { Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list."); return CompletableFuture.completedFuture(Collections.emptyList()); Loading @@ -705,6 +715,7 @@ public class GlobalScreenshot { Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile); CompletableFuture<List<Notification.Action>> smartActionsFuture; long startTimeMs = SystemClock.uptimeMillis(); try { ActivityManager.RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask(); Loading @@ -712,34 +723,74 @@ public class GlobalScreenshot { (runningTask != null && runningTask.topActivity != null) ? runningTask.topActivity : new ComponentName("", ""); smartActionsFuture = smartActionsProvider.getActions(image, context, THREAD_POOL_EXECUTOR, handler, smartActionsFuture = smartActionsProvider.getActions(screenshotId, image, componentName, isManagedProfile); } catch (Throwable e) { long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs; smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList()); 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; } @VisibleForTesting static List<Notification.Action> getSmartActions( CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs) { try { static List<Notification.Action> getSmartActions(String screenshotId, CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs, ScreenshotNotificationSmartActionsProvider smartActionsProvider) { long startTimeMs = SystemClock.uptimeMillis(); try { List<Notification.Action> actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS); long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs; 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; } 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(); } } 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 * appropriate signals to the system (ie. to dismiss the keyguard if necessary). Loading Loading @@ -783,6 +834,13 @@ public class GlobalScreenshot { } else { startActivityRunnable.run(); } if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT : ACTION_TYPE_SHARE; notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID), actionType, false); } } } Loading Loading @@ -813,6 +871,29 @@ public class GlobalScreenshot { // And delete the image from the media store final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID)); 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); } } Loading packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +167 −105 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.media.ExifInterface; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.ParcelFileDescriptor; Loading @@ -50,6 +51,7 @@ import android.provider.MediaStore; import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.R; Loading @@ -69,9 +71,12 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; /** Loading @@ -81,6 +86,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String TAG = "SaveImageInBackgroundTask"; 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 final GlobalScreenshot.SaveImageInBackgroundData mParams; Loading @@ -91,8 +97,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final Notification.BigPictureStyle mNotificationStyle; private final int mImageWidth; private final int mImageHeight; private final Handler mHandler; private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private final String mScreenshotId; private final boolean mSmartActionsEnabled; private final Random mRandom = new Random(); SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data, NotificationManager nManager) { Loading @@ -103,11 +111,20 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageTime = System.currentTimeMillis(); String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime)); mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate); mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID()); // 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 = 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 mImageWidth = data.image.getWidth(); Loading Loading @@ -201,6 +218,38 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { return info.isManagedProfile(); } 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); } /** * Generates a new hardware bitmap with specified values, copying the content from the * passed in bitmap. Loading @@ -227,16 +276,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Context context = mParams.context; Bitmap image = mParams.image; boolean smartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); CompletableFuture<List<Notification.Action>> smartActionsFuture = GlobalScreenshot.getSmartActionsFuture( context, image, mSmartActionsProvider, mHandler, smartActionsEnabled, isManagedProfile(context)); Resources r = context.getResources(); try { CompletableFuture<List<Notification.Action>> smartActionsFuture = GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image, mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context)); // Save the screenshot to the MediaStore final MediaStore.PendingParams params = new MediaStore.PendingParams( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png"); Loading Loading @@ -289,6 +335,31 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 // order to do some common work like dismissing the keyguard and sending // closeSystemWindows Loading Loading @@ -326,12 +397,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, sharingChooserIntent) .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, mSmartActionsEnabled) .setAction(Intent.ACTION_SEND), PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_share, 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 that directly Loading @@ -351,12 +425,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent) .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, mSmartActionsEnabled) .setAction(Intent.ACTION_EDIT), PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_edit, r.getString(com.android.internal.R.string.screenshot_edit), editAction); mNotificationBuilder.addAction(editActionBuilder.build()); notificationBuilder.addAction(editActionBuilder.build()); if (editAction != null && mParams.onEditReady != null) { mParams.onEditReady.apply(editAction); } Loading @@ -364,43 +441,28 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, 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); Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_delete, r.getString(com.android.internal.R.string.delete), deleteAction); mNotificationBuilder.addAction(deleteActionBuilder.build()); mParams.imageUri = uri; mParams.image = null; mParams.errorMsgResId = 0; notificationBuilder.addAction(deleteActionBuilder.build()); if (smartActionsEnabled) { if (mSmartActionsEnabled) { int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS, 1000); List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions( smartActionsFuture, timeoutMs); List<Notification.Action> smartActions = buildSmartActions( GlobalScreenshot.getSmartActions(mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider), context); 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 Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java +51 −8 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -369,6 +369,10 @@ <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver" 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 --> <activity android:name=".usb.UsbConfirmActivity" android:exported="true" Loading
packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +5 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.concurrent.Executor; import dagger.Module; import dagger.Provides; Loading Loading @@ -124,7 +126,9 @@ public class SystemUIFactory { * This method is overridden in vendor specific implementation of Sys UI. */ public ScreenshotNotificationSmartActionsProvider createScreenshotNotificationSmartActionsProvider() { createScreenshotNotificationSmartActionsProvider(Context context, Executor executor, Handler uiHandler) { return new ScreenshotNotificationSmartActionsProvider(); } Loading
packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +92 −11 Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.SystemUIFactory; import com.android.systemui.dagger.qualifiers.MainResources; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.phone.StatusBar; Loading Loading @@ -126,8 +127,17 @@ public class GlobalScreenshot { } } static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; // 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 EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; Loading Loading @@ -689,9 +699,9 @@ public class GlobalScreenshot { } @VisibleForTesting static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(Context context, static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId, Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider, Handler handler, boolean smartActionsEnabled, boolean isManagedProfile) { boolean smartActionsEnabled, boolean isManagedProfile) { if (!smartActionsEnabled) { Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list."); return CompletableFuture.completedFuture(Collections.emptyList()); Loading @@ -705,6 +715,7 @@ public class GlobalScreenshot { Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile); CompletableFuture<List<Notification.Action>> smartActionsFuture; long startTimeMs = SystemClock.uptimeMillis(); try { ActivityManager.RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask(); Loading @@ -712,34 +723,74 @@ public class GlobalScreenshot { (runningTask != null && runningTask.topActivity != null) ? runningTask.topActivity : new ComponentName("", ""); smartActionsFuture = smartActionsProvider.getActions(image, context, THREAD_POOL_EXECUTOR, handler, smartActionsFuture = smartActionsProvider.getActions(screenshotId, image, componentName, isManagedProfile); } catch (Throwable e) { long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs; smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList()); 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; } @VisibleForTesting static List<Notification.Action> getSmartActions( CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs) { try { static List<Notification.Action> getSmartActions(String screenshotId, CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs, ScreenshotNotificationSmartActionsProvider smartActionsProvider) { long startTimeMs = SystemClock.uptimeMillis(); try { List<Notification.Action> actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS); long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs; 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; } 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(); } } 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 * appropriate signals to the system (ie. to dismiss the keyguard if necessary). Loading Loading @@ -783,6 +834,13 @@ public class GlobalScreenshot { } else { startActivityRunnable.run(); } if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT : ACTION_TYPE_SHARE; notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID), actionType, false); } } } Loading Loading @@ -813,6 +871,29 @@ public class GlobalScreenshot { // And delete the image from the media store final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID)); 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); } } Loading
packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +167 −105 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.media.ExifInterface; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.ParcelFileDescriptor; Loading @@ -50,6 +51,7 @@ import android.provider.MediaStore; import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.R; Loading @@ -69,9 +71,12 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; /** Loading @@ -81,6 +86,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String TAG = "SaveImageInBackgroundTask"; 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 final GlobalScreenshot.SaveImageInBackgroundData mParams; Loading @@ -91,8 +97,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final Notification.BigPictureStyle mNotificationStyle; private final int mImageWidth; private final int mImageHeight; private final Handler mHandler; private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private final String mScreenshotId; private final boolean mSmartActionsEnabled; private final Random mRandom = new Random(); SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data, NotificationManager nManager) { Loading @@ -103,11 +111,20 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageTime = System.currentTimeMillis(); String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime)); mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate); mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID()); // 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 = 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 mImageWidth = data.image.getWidth(); Loading Loading @@ -201,6 +218,38 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { return info.isManagedProfile(); } 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); } /** * Generates a new hardware bitmap with specified values, copying the content from the * passed in bitmap. Loading @@ -227,16 +276,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Context context = mParams.context; Bitmap image = mParams.image; boolean smartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); CompletableFuture<List<Notification.Action>> smartActionsFuture = GlobalScreenshot.getSmartActionsFuture( context, image, mSmartActionsProvider, mHandler, smartActionsEnabled, isManagedProfile(context)); Resources r = context.getResources(); try { CompletableFuture<List<Notification.Action>> smartActionsFuture = GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image, mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context)); // Save the screenshot to the MediaStore final MediaStore.PendingParams params = new MediaStore.PendingParams( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png"); Loading Loading @@ -289,6 +335,31 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 // order to do some common work like dismissing the keyguard and sending // closeSystemWindows Loading Loading @@ -326,12 +397,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, sharingChooserIntent) .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, mSmartActionsEnabled) .setAction(Intent.ACTION_SEND), PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_share, 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 that directly Loading @@ -351,12 +425,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent) .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, mSmartActionsEnabled) .setAction(Intent.ACTION_EDIT), PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_edit, r.getString(com.android.internal.R.string.screenshot_edit), editAction); mNotificationBuilder.addAction(editActionBuilder.build()); notificationBuilder.addAction(editActionBuilder.build()); if (editAction != null && mParams.onEditReady != null) { mParams.onEditReady.apply(editAction); } Loading @@ -364,43 +441,28 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, 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); Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_delete, r.getString(com.android.internal.R.string.delete), deleteAction); mNotificationBuilder.addAction(deleteActionBuilder.build()); mParams.imageUri = uri; mParams.image = null; mParams.errorMsgResId = 0; notificationBuilder.addAction(deleteActionBuilder.build()); if (smartActionsEnabled) { if (mSmartActionsEnabled) { int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS, 1000); List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions( smartActionsFuture, timeoutMs); List<Notification.Action> smartActions = buildSmartActions( GlobalScreenshot.getSmartActions(mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider), context); 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 Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java +51 −8 File changed.Preview size limit exceeded, changes collapsed. Show changes