From 7b2c313da77cb5c0faef0a08a7e1cfc09aed9239 Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Fri, 27 Mar 2020 09:54:14 -0400 Subject: Add screenshots logging Bug: 150710005 Test: manual Change-Id: I54a37eb0a62234c6c53fc0f3c80e18e9ee269f12 --- core/java/android/view/WindowManager.java | 41 ++--- .../android/internal/util/ScreenshotHelper.java | 177 +++++++++++++++++---- .../internal/util/ScreenshotHelperTest.java | 4 +- .../systemui/accessibility/SystemActions.java | 6 +- .../globalactions/GlobalActionsDialog.java | 7 +- .../systemui/recents/OverviewProxyService.java | 3 +- .../systemui/screenshot/GlobalScreenshot.java | 119 +++++++++----- .../screenshot/GlobalScreenshotLegacy.java | 9 +- .../screenshot/SaveImageInBackgroundTask.java | 86 ++++++---- .../systemui/screenshot/ScreenshotEvent.java | 89 +++++++++++ .../ScreenshotNotificationsController.java | 20 +-- .../systemui/screenshot/TakeScreenshotService.java | 34 ++-- .../ScreenshotNotificationSmartActionsTest.java | 108 +++++++++---- .../accessibility/SystemActionPerformer.java | 5 +- .../android/server/policy/PhoneWindowManager.java | 12 +- .../java/com/android/server/wm/DisplayPolicy.java | 5 +- .../accessibility/SystemActionPerformerTest.java | 4 +- 17 files changed, 530 insertions(+), 199 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cc380f32297e..e4dbd63765b6 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -514,33 +514,24 @@ public interface WindowManager extends ViewManager { int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3; /** - * Parcel key for the screen shot bitmap sent with messages of type - * {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}, type {@link android.graphics.Bitmap} - * @hide - */ - String PARCEL_KEY_SCREENSHOT_BITMAP = "screenshot_screen_bitmap"; - - /** - * Parcel key for the screen bounds of the image sent with messages of type - * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link Rect} in screen coordinates. - * @hide - */ - String PARCEL_KEY_SCREENSHOT_BOUNDS = "screenshot_screen_bounds"; - - /** - * Parcel key for the task id of the task that the screen shot was taken of, sent with messages - * of type [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type int. - * @hide - */ - String PARCEL_KEY_SCREENSHOT_TASK_ID = "screenshot_task_id"; - - /** - * Parcel key for the visible insets of the image sent with messages of type - * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link android.graphics.Insets} in - * screen coordinates. + * Enum listing the possible sources from which a screenshot was originated. Used for logging. + * * @hide */ - String PARCEL_KEY_SCREENSHOT_INSETS = "screenshot_insets"; + @IntDef({ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS, + ScreenshotSource.SCREENSHOT_KEY_CHORD, + ScreenshotSource.SCREENSHOT_KEY_OTHER, + ScreenshotSource.SCREENSHOT_OVERVIEW, + ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS, + ScreenshotSource.SCREENSHOT_OTHER}) + @interface ScreenshotSource { + int SCREENSHOT_GLOBAL_ACTIONS = 0; + int SCREENSHOT_KEY_CHORD = 1; + int SCREENSHOT_KEY_OTHER = 2; + int SCREENSHOT_OVERVIEW = 3; + int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4; + int SCREENSHOT_OTHER = 5; + } /** * @hide diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index 7cff90bbf437..adadc5e20549 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -1,5 +1,7 @@ package com.android.internal.util; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -10,11 +12,12 @@ import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.net.Uri; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; @@ -23,6 +26,109 @@ import android.view.WindowManager; import java.util.function.Consumer; public class ScreenshotHelper { + + /** + * Describes a screenshot request (to make it easier to pass data through to the handler). + */ + public static class ScreenshotRequest implements Parcelable { + private int mSource; + private boolean mHasStatusBar; + private boolean mHasNavBar; + private Bitmap mBitmap; + private Rect mBoundsInScreen; + private Insets mInsets; + private int mTaskId; + + ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) { + mSource = source; + mHasStatusBar = hasStatus; + mHasNavBar = hasNav; + } + + ScreenshotRequest( + int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) { + mSource = source; + mBitmap = bitmap; + mBoundsInScreen = boundsInScreen; + mInsets = insets; + mTaskId = taskId; + } + + ScreenshotRequest(Parcel in) { + mSource = in.readInt(); + mHasStatusBar = in.readBoolean(); + mHasNavBar = in.readBoolean(); + if (in.readInt() == 1) { + mBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader()); + mInsets = in.readParcelable(Insets.class.getClassLoader()); + mTaskId = in.readInt(); + } + } + + public int getSource() { + return mSource; + } + + public boolean getHasStatusBar() { + return mHasStatusBar; + } + + public boolean getHasNavBar() { + return mHasNavBar; + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public Rect getBoundsInScreen() { + return mBoundsInScreen; + } + + public Insets getInsets() { + return mInsets; + } + + public int getTaskId() { + return mTaskId; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSource); + dest.writeBoolean(mHasStatusBar); + dest.writeBoolean(mHasNavBar); + if (mBitmap == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeParcelable(mBitmap, 0); + dest.writeParcelable(mBoundsInScreen, 0); + dest.writeParcelable(mInsets, 0); + dest.writeInt(mTaskId); + } + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public ScreenshotRequest createFromParcel(Parcel source) { + return new ScreenshotRequest(source); + } + + @Override + public ScreenshotRequest[] newArray(int size) { + return new ScreenshotRequest[size]; + } + }; + } private static final String TAG = "ScreenshotHelper"; // Time until we give up on the screenshot & show an error instead. @@ -36,8 +142,10 @@ public class ScreenshotHelper { mContext = context; } + + /** - * Request a screenshot be taken with a specific timeout. + * Request a screenshot be taken. * * Added to support reducing unit test duration; the method variant without a timeout argument * is recommended for general use. @@ -47,6 +155,32 @@ public class ScreenshotHelper { * or * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} * @param hasStatus {@code true} if the status bar is currently showing. {@code false} + * if not. + * @param hasNav {@code true} if the navigation bar is currently showing. {@code + * false} if not. + * @param source The source of the screenshot request. One of + * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD, + * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER} + * @param handler A handler used in case the screenshot times out + * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the + * screenshot was taken. + */ + public void takeScreenshot(final int screenshotType, final boolean hasStatus, + final boolean hasNav, int source, @NonNull Handler handler, + @Nullable Consumer completionConsumer) { + ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav); + takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest, + completionConsumer); + } + + /** + * Request a screenshot be taken, with provided reason. + * + * @param screenshotType The type of screenshot, for example either + * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN} + * or + * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} + * @param hasStatus {@code true} if the status bar is currently showing. {@code false} * if * not. * @param hasNav {@code true} if the navigation bar is currently showing. {@code @@ -64,7 +198,7 @@ public class ScreenshotHelper { } /** - * Request a screenshot be taken. + * Request a screenshot be taken with a specific timeout. * * Added to support reducing unit test duration; the method variant without a timeout argument * is recommended for general use. @@ -89,9 +223,9 @@ public class ScreenshotHelper { public void takeScreenshot(final int screenshotType, final boolean hasStatus, final boolean hasNav, long timeoutMs, @NonNull Handler handler, @Nullable Consumer completionConsumer) { - takeScreenshot(screenshotType, hasStatus, hasNav, timeoutMs, handler, null, - completionConsumer - ); + ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus, + hasNav); + takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer); } /** @@ -106,23 +240,16 @@ public class ScreenshotHelper { * screenshot was taken. */ public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen, - @NonNull Insets insets, int taskId, @NonNull Handler handler, - @Nullable Consumer completionConsumer) { - Bundle imageBundle = new Bundle(); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP, screenshot); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS, boundsInScreen); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_INSETS, insets); - imageBundle.putInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID, taskId); - - takeScreenshot( - WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, - false, false, // ignored when image bundle is set - SCREENSHOT_TIMEOUT_MS, handler, imageBundle, completionConsumer); + @NonNull Insets insets, int taskId, int source, + @NonNull Handler handler, @Nullable Consumer completionConsumer) { + ScreenshotRequest screenshotRequest = + new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId); + takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS, + handler, screenshotRequest, completionConsumer); } - private void takeScreenshot(final int screenshotType, final boolean hasStatus, - final boolean hasNav, long timeoutMs, @NonNull Handler handler, - @Nullable Bundle providedImage, @Nullable Consumer completionConsumer) { + private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler, + ScreenshotRequest screenshotRequest, @Nullable Consumer completionConsumer) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; @@ -157,7 +284,7 @@ public class ScreenshotHelper { return; } Messenger messenger = new Messenger(service); - Message msg = Message.obtain(null, screenshotType); + Message msg = Message.obtain(null, screenshotType, screenshotRequest); final ServiceConnection myConn = this; Handler h = new Handler(handler.getLooper()) { @Override @@ -175,12 +302,6 @@ public class ScreenshotHelper { } }; msg.replyTo = new Messenger(h); - msg.arg1 = hasStatus ? 1 : 0; - msg.arg2 = hasNav ? 1 : 0; - - if (screenshotType == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { - msg.setData(providedImage); - } try { messenger.send(msg); diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java index cd6b3af5fa6d..fe33cd80f735 100644 --- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java +++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java @@ -36,6 +36,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; +import android.view.WindowManager; import androidx.test.runner.AndroidJUnit4; @@ -91,7 +92,8 @@ public final class ScreenshotHelperTest { public void testProvidedImageScreenshot() { mScreenshotHelper.provideScreenshot( Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(), - Insets.of(0, 0, 0, 0), 1, mHandler, null); + Insets.of(0, 0, 0, 0), 1, + WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null); } @Test diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index 7262f8caac89..1f27ae238533 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; + import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.app.RemoteAction; @@ -282,8 +284,8 @@ public class SystemActions extends SystemUI { private void handleTakeScreenshot() { ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext); - screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true, true, new Handler(Looper.getMainLooper()), null); + screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true, + SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null); } private void handleAccessibilityMenu() { diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 2c1bd2186dea..423906d625d4 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -16,6 +16,8 @@ package com.android.systemui.globalactions; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; +import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; @@ -828,7 +830,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mHandler.postDelayed(new Runnable() { @Override public void run() { - mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null); + mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true, + SCREENSHOT_GLOBAL_ACTIONS, mHandler, null); mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS); } @@ -2331,4 +2334,4 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // always use new controls layout return true; } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 66bc177da81d..fecb7b602012 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; @@ -380,7 +381,7 @@ public class OverviewProxyService implements CallbackController finisher; public GlobalScreenshot.ActionsReadyListener mActionsReadyListener; public int errorMsgResId; @@ -112,13 +112,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset void clearImage() { image = null; - imageUri = null; + } + } + + /** + * Structure returned by the SaveImageInBackgroundTask + */ + static class SavedImageData { + public Uri uri; + public Notification.Action shareAction; + public Notification.Action editAction; + public Notification.Action deleteAction; + public List smartActions; + + /** + * Used to reset the return data on error + */ + public void reset() { + uri = null; + shareAction = null; + editAction = null; + deleteAction = null; + smartActions = null; } } abstract static class ActionsReadyListener { - abstract void onActionsReady(Uri imageUri, List smartActions, - List actions); + abstract void onActionsReady(SavedImageData imageData); } // These strings are used for communicating the action invoked to @@ -147,6 +167,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; + private final UiEventLogger mUiEventLogger; private final Context mContext; private final WindowManager mWindowManager; @@ -185,6 +206,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); GlobalScreenshot.this.clearScreenshot("timeout"); break; default: @@ -199,9 +221,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Inject public GlobalScreenshot( Context context, @Main Resources resources, LayoutInflater layoutInflater, - ScreenshotNotificationsController screenshotNotificationsController) { + ScreenshotNotificationsController screenshotNotificationsController, + UiEventLogger uiEventLogger) { mContext = context; mNotificationsController = screenshotNotificationsController; + mUiEventLogger = uiEventLogger; // Inflate the screenshot layout mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null); @@ -222,7 +246,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mBackgroundProtection = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_background); mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); - mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button")); + mDismissButton.setOnClickListener(view -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL); + clearScreenshot("dismiss_button"); + }); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); @@ -445,12 +472,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List smartActions, - List actions) { - if (uri == null) { + void onActionsReady(SavedImageData imageData) { + finisher.accept(imageData.uri); + if (imageData.uri == null) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); mScreenshotHandler.post(() -> { if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.addListener( @@ -458,13 +487,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - createScreenshotActionsShadeAnimation( - smartActions, actions).start(); + createScreenshotActionsShadeAnimation(imageData) + .start(); } }); } else { - createScreenshotActionsShadeAnimation(smartActions, - actions).start(); + createScreenshotActionsShadeAnimation(imageData).start(); } }); } @@ -567,8 +595,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return dropInAnimation; } - private ValueAnimator createScreenshotActionsShadeAnimation( - List smartActions, List actions) { + private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) { LayoutInflater inflater = LayoutInflater.from(mContext); mActionsView.removeAllViews(); mActionsContainer.setScrollX(0); @@ -583,45 +610,63 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } catch (RemoteException e) { } - for (Notification.Action smartAction : smartActions) { + for (Notification.Action smartAction : imageData.smartActions) { ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); actionChip.setText(smartAction.title); actionChip.setIcon(smartAction.getIcon(), false); actionChip.setPendingIntent(smartAction.actionIntent, - () -> clearScreenshot("chip tapped")); + () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED); + clearScreenshot("chip tapped"); + }); mActionsView.addView(actionChip); } - for (Notification.Action action : actions) { - ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( - R.layout.global_screenshot_action_chip, mActionsView, false); - actionChip.setText(action.title); - actionChip.setIcon(action.getIcon(), true); - actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped")); - if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) { - mScreenshotView.setOnClickListener(v -> { - try { - action.actionIntent.send(); - clearScreenshot("screenshot preview tapped"); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Intent cancelled", e); - } - }); - mScreenshotView.setContentDescription(action.title); + ScreenshotActionChip shareChip = (ScreenshotActionChip) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + shareChip.setText(imageData.shareAction.title); + shareChip.setIcon(imageData.shareAction.getIcon(), true); + shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED); + clearScreenshot("chip tapped"); + }); + mActionsView.addView(shareChip); + + ScreenshotActionChip editChip = (ScreenshotActionChip) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + editChip.setText(imageData.editAction.title); + editChip.setIcon(imageData.editAction.getIcon(), true); + editChip.setPendingIntent(imageData.editAction.actionIntent, () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED); + clearScreenshot("chip tapped"); + }); + mActionsView.addView(editChip); + + mScreenshotView.setOnClickListener(v -> { + try { + imageData.editAction.actionIntent.send(); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED); + clearScreenshot("screenshot preview tapped"); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Intent cancelled", e); } - mActionsView.addView(actionChip); - } + }); + mScreenshotView.setContentDescription(imageData.editAction.title); + if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) { ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); Toast scrollNotImplemented = Toast.makeText( mContext, "Not implemented", Toast.LENGTH_SHORT); - scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate + scrollChip.setText("Extend"); // TODO : add resource and translate scrollChip.setIcon( Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true); - scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); + scrollChip.setOnClickListener(v -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED); + scrollNotImplemented.show(); + }); mActionsView.addView(scrollChip); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java index f3614ffbdb1b..095c32f4a2ce 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -24,7 +24,6 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; -import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -53,7 +52,6 @@ import android.widget.Toast; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; -import java.util.List; import java.util.function.Consumer; import javax.inject.Inject; @@ -347,14 +345,13 @@ public class GlobalScreenshotLegacy { // Save the screenshot once we have a bit of time now saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List smartActions, - List actions) { - if (uri == null) { + void onActionsReady(GlobalScreenshot.SavedImageData actionData) { + if (actionData.uri == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { mNotificationsController - .showScreenshotActionsNotification(uri, smartActions, actions); + .showScreenshotActionsNotification(actionData); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index c828c4cccce5..170174deaeb3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -83,6 +83,7 @@ class SaveImageInBackgroundTask extends AsyncTask { private final Context mContext; private final GlobalScreenshot.SaveImageInBackgroundData mParams; + private final GlobalScreenshot.SavedImageData mImageData; private final String mImageFileName; private final long mImageTime; private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; @@ -93,6 +94,7 @@ class SaveImageInBackgroundTask extends AsyncTask { SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { mContext = context; + mImageData = new GlobalScreenshot.SavedImageData(); // Prepare all the output metadata mParams = data; @@ -145,6 +147,7 @@ class SaveImageInBackgroundTask extends AsyncTask { values.put(MediaColumns.IS_PENDING, 1); final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + try { // First, write the actual data for our screenshot try (OutputStream out = resolver.openOutputStream(uri)) { @@ -192,8 +195,6 @@ class SaveImageInBackgroundTask extends AsyncTask { throw e; } - List actions = - populateNotificationActions(mContext, r, uri); List smartActions = new ArrayList<>(); if (mSmartActionsEnabled) { int timeoutMs = DeviceConfig.getInt( @@ -206,8 +207,14 @@ class SaveImageInBackgroundTask extends AsyncTask { mSmartActionsProvider), mContext)); } - mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions); - mParams.imageUri = uri; + + mImageData.uri = uri; + mImageData.smartActions = smartActions; + mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri); + mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri); + mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); + + mParams.mActionsReadyListener.onActionsReady(mImageData); mParams.image = null; mParams.errorMsgResId = 0; } catch (Exception e) { @@ -216,29 +223,26 @@ class SaveImageInBackgroundTask extends AsyncTask { Slog.e(TAG, "unable to save screenshot", e); mParams.clearImage(); mParams.errorMsgResId = R.string.screenshot_failed_to_save_text; - mParams.mActionsReadyListener.onActionsReady(null, null, null); + mImageData.reset(); + mParams.mActionsReadyListener.onActionsReady(mImageData); } return null; } - @Override - protected void onPostExecute(Void params) { - mParams.finisher.accept(mParams.imageUri); - } - @Override protected void onCancelled(Void params) { // If we are cancelled while the task is running in the background, we may get null // params. The finisher is expected to always be called back, so just use the baked-in // params from the ctor in any case. - mParams.mActionsReadyListener.onActionsReady(null, null, null); + mImageData.reset(); + mParams.mActionsReadyListener.onActionsReady(mImageData); mParams.finisher.accept(null); mParams.clearImage(); } @VisibleForTesting - List populateNotificationActions(Context context, Resources r, Uri uri) { + Notification.Action createShareAction(Context context, Resources r, Uri uri) { // 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 @@ -263,8 +267,6 @@ class SaveImageInBackgroundTask extends AsyncTask { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); - ArrayList actions = new ArrayList<>(); - PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode, new Intent(context, GlobalScreenshot.TargetChosenReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); @@ -288,7 +290,15 @@ class SaveImageInBackgroundTask extends AsyncTask { Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); - actions.add(shareActionBuilder.build()); + + return shareActionBuilder.build(); + } + + @VisibleForTesting + Notification.Action createEditAction(Context context, Resources r, Uri uri) { + // 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 // Create an edit intent, if a specific package is provided as the editor, then // launch that directly @@ -302,6 +312,10 @@ class SaveImageInBackgroundTask extends AsyncTask { editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + // Make sure pending intents for the system user are still unique across users + // by setting the (otherwise unused) request code to the current user id. + int requestCode = mContext.getUserId(); + // Create a edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) @@ -317,24 +331,30 @@ class SaveImageInBackgroundTask extends AsyncTask { Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); - actions.add(editActionBuilder.build()); - - if (mCreateDeleteAction) { - // 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.EXTRA_ID, mScreenshotId) - .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, - mSmartActionsEnabled) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); - Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( - Icon.createWithResource(r, R.drawable.ic_screenshot_delete), - r.getString(com.android.internal.R.string.delete), deleteAction); - actions.add(deleteActionBuilder.build()); - } - return actions; + + return editActionBuilder.build(); + } + + @VisibleForTesting + Notification.Action createDeleteAction(Context context, Resources r, Uri uri) { + // Make sure pending intents for the system user are still unique across users + // by setting the (otherwise unused) request code to the current user id. + int requestCode = mContext.getUserId(); + + // 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.EXTRA_ID, mScreenshotId) + .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, + mSmartActionsEnabled) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); + Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( + Icon.createWithResource(r, R.drawable.ic_screenshot_delete), + r.getString(com.android.internal.R.string.delete), deleteAction); + + return deleteActionBuilder.build(); } private int getUserHandleOfForegroundApplication(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java new file mode 100644 index 000000000000..20fa991dcc1f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; + +public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "screenshot requested from global actions") + SCREENSHOT_REQUESTED_GLOBAL_ACTIONS(302), + @UiEvent(doc = "screenshot requested from key chord") + SCREENSHOT_REQUESTED_KEY_CHORD(303), + @UiEvent(doc = "screenshot requested from other key press (e.g. ctrl-s)") + SCREENSHOT_REQUESTED_KEY_OTHER(384), + @UiEvent(doc = "screenshot requested from overview") + SCREENSHOT_REQUESTED_OVERVIEW(304), + @UiEvent(doc = "screenshot requested from accessibility actions") + SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS(382), + @UiEvent(doc = "screenshot requested (other)") + SCREENSHOT_REQUESTED_OTHER(305), + @UiEvent(doc = "screenshot was saved") + SCREENSHOT_SAVED(306), + @UiEvent(doc = "screenshot failed to save") + SCREENSHOT_NOT_SAVED(336), + @UiEvent(doc = "screenshot preview tapped") + SCREENSHOT_PREVIEW_TAPPED(307), + @UiEvent(doc = "screenshot edit button tapped") + SCREENSHOT_EDIT_TAPPED(308), + @UiEvent(doc = "screenshot share button tapped") + SCREENSHOT_SHARE_TAPPED(309), + @UiEvent(doc = "screenshot smart action chip tapped") + SCREENSHOT_SMART_ACTION_TAPPED(374), + @UiEvent(doc = "screenshot scroll tapped") + SCREENSHOT_SCROLL_TAPPED(373), + @UiEvent(doc = "screenshot interaction timed out") + SCREENSHOT_INTERACTION_TIMEOUT(310), + @UiEvent(doc = "screenshot explicitly dismissed") + SCREENSHOT_EXPLICIT_DISMISSAL(311); + + private final int mId; + + ScreenshotEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + + static ScreenshotEvent getScreenshotSource(int source) { + switch (source) { + case SCREENSHOT_GLOBAL_ACTIONS: + return ScreenshotEvent.SCREENSHOT_REQUESTED_GLOBAL_ACTIONS; + case SCREENSHOT_KEY_CHORD: + return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD; + case SCREENSHOT_KEY_OTHER: + return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER; + case SCREENSHOT_OVERVIEW: + return ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW; + case SCREENSHOT_ACCESSIBILITY_ACTIONS: + return ScreenshotEvent.SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS; + case SCREENSHOT_OTHER: + default: + return ScreenshotEvent.SCREENSHOT_REQUESTED_OTHER; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 811a8d936b77..fbcd6ba0ff47 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -32,7 +32,6 @@ import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; -import android.net.Uri; import android.os.UserHandle; import android.util.DisplayMetrics; import android.view.WindowManager; @@ -42,8 +41,6 @@ import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.util.NotificationChannels; -import java.util.List; - import javax.inject.Inject; /** @@ -185,23 +182,20 @@ public class ScreenshotNotificationsController { /** * Shows a notification with the saved screenshot and actions that can be taken with it. * - * @param imageUri URI for the saved image - * @param actions a list of notification actions which can be taken + * @param actionData SavedImageData struct with image URI and actions */ public void showScreenshotActionsNotification( - Uri imageUri, - List smartActions, - List actions) { - for (Notification.Action action : actions) { - mNotificationBuilder.addAction(action); - } - for (Notification.Action smartAction : smartActions) { + GlobalScreenshot.SavedImageData actionData) { + mNotificationBuilder.addAction(actionData.shareAction); + mNotificationBuilder.addAction(actionData.editAction); + mNotificationBuilder.addAction(actionData.deleteAction); + for (Notification.Action smartAction : actionData.smartActions) { mNotificationBuilder.addAction(smartAction); } // Create the intent to show the screenshot in gallery Intent launchIntent = new Intent(Intent.ACTION_VIEW); - launchIntent.setDataAndType(imageUri, "image/png"); + launchIntent.setDataAndType(actionData.uri, "image/png"); launchIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 8b8b6f8071e1..a6e083b04ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -32,6 +32,9 @@ import android.os.UserManager; import android.util.Log; import android.view.WindowManager; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.util.ScreenshotHelper; + import java.util.function.Consumer; import javax.inject.Inject; @@ -42,6 +45,7 @@ public class TakeScreenshotService extends Service { private final GlobalScreenshot mScreenshot; private final GlobalScreenshotLegacy mScreenshotLegacy; private final UserManager mUserManager; + private final UiEventLogger mUiEventLogger; private Handler mHandler = new Handler(Looper.myLooper()) { @Override @@ -64,14 +68,22 @@ public class TakeScreenshotService extends Service { return; } - // TODO (mkephart): clean up once notifications flow is fully deprecated + // TODO: clean up once notifications flow is fully deprecated boolean useCornerFlow = true; + + ScreenshotHelper.ScreenshotRequest screenshotRequest = + (ScreenshotHelper.ScreenshotRequest) msg.obj; + + mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource())); + switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: if (useCornerFlow) { mScreenshot.takeScreenshot(finisher); } else { - mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + mScreenshotLegacy.takeScreenshot( + finisher, screenshotRequest.getHasStatusBar(), + screenshotRequest.getHasNavBar()); } break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: @@ -79,17 +91,15 @@ public class TakeScreenshotService extends Service { mScreenshot.takeScreenshotPartial(finisher); } else { mScreenshotLegacy.takeScreenshotPartial( - finisher, msg.arg1 > 0, msg.arg2 > 0); + finisher, screenshotRequest.getHasStatusBar(), + screenshotRequest.getHasNavBar()); } break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: - Bitmap screenshot = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP); - Rect screenBounds = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS); - Insets insets = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_INSETS); - int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID); + Bitmap screenshot = screenshotRequest.getBitmap(); + Rect screenBounds = screenshotRequest.getBoundsInScreen(); + Insets insets = screenshotRequest.getInsets(); + int taskId = screenshotRequest.getTaskId(); if (useCornerFlow) { mScreenshot.handleImageAsScreenshot( screenshot, screenBounds, insets, taskId, finisher); @@ -106,10 +116,12 @@ public class TakeScreenshotService extends Service { @Inject public TakeScreenshotService(GlobalScreenshot globalScreenshot, - GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) { + GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager, + UiEventLogger uiEventLogger) { mScreenshot = globalScreenshot; mScreenshotLegacy = globalScreenshotLegacy; mUserManager = userManager; + mUiEventLogger = uiEventLogger; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 11649ca34709..ffe3cd5bd504 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -16,6 +16,10 @@ package com.android.systemui.screenshot; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -40,7 +44,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,9 +85,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { CompletableFuture> smartActionsFuture = ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap, smartActionsProvider, true, false); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(Collections.emptyList(), smartActions); + assertEquals(Collections.emptyList(), smartActions); } // Tests any exception thrown in waiting for smart actions future to complete does @@ -99,7 +102,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { RuntimeException.class); List actions = ScreenshotSmartActions.getSmartActions( "", "", smartActionsFuture, timeoutMs, mSmartActionsProvider); - Assert.assertEquals(Collections.emptyList(), actions); + assertEquals(Collections.emptyList(), actions); } // Tests any exception thrown in notifying feedback does not affect regular screenshot flow. @@ -123,9 +126,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { mSmartActionsProvider, true, true); verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), eq(false)); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(Collections.emptyList(), smartActions); + assertEquals(Collections.emptyList(), smartActions); } // Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once. @@ -152,14 +155,14 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap, actionsProvider, true, true); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(smartActions.size(), 0); + assertEquals(smartActions.size(), 0); } - // Tests for notification action extras. + // Tests for share action extras @Test - public void testNotificationActionExtras() { + public void testShareActionExtras() { if (Looper.myLooper() == null) { Looper.prepare(); } @@ -169,35 +172,70 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - data.createDeleteAction = true; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); - List actions = task.populateNotificationActions( - mContext, mContext.getResources(), + + Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); - Assert.assertEquals(actions.size(), 3); - boolean isShareFound = false; - boolean isEditFound = false; - boolean isDeleteFound = false; - for (Notification.Action action : actions) { - Intent intent = action.actionIntent.getIntent(); - Assert.assertNotNull(intent); - Bundle bundle = intent.getExtras(); - Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); - Assert.assertTrue( - bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); - - if (action.title.equals(GlobalScreenshot.ACTION_TYPE_DELETE)) { - isDeleteFound = intent.getAction() == null; - } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_EDIT)) { - isEditFound = Intent.ACTION_EDIT.equals(intent.getAction()); - } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_SHARE)) { - isShareFound = Intent.ACTION_SEND.equals(intent.getAction()); - } + Intent intent = shareAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(GlobalScreenshot.ACTION_TYPE_SHARE, shareAction.title); + assertEquals(Intent.ACTION_SEND, intent.getAction()); + } + + // Tests for edit action extras + @Test + public void testEditActionExtras() { + if (Looper.myLooper() == null) { + Looper.prepare(); } - Assert.assertTrue(isEditFound); - Assert.assertTrue(isDeleteFound); - Assert.assertTrue(isShareFound); + GlobalScreenshot.SaveImageInBackgroundData + data = new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + data.finisher = null; + data.mActionsReadyListener = null; + SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + + Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), + Uri.parse("Screenshot_123.png")); + + Intent intent = editAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(GlobalScreenshot.ACTION_TYPE_EDIT, editAction.title); + assertEquals(Intent.ACTION_EDIT, intent.getAction()); + } + + // Tests for share action extras + @Test + public void testDeleteActionExtras() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + GlobalScreenshot.SaveImageInBackgroundData + data = new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + data.finisher = null; + data.mActionsReadyListener = null; + SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + + Notification.Action deleteAction = task.createDeleteAction(mContext, + mContext.getResources(), + Uri.parse("Screenshot_123.png")); + + Intent intent = deleteAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(deleteAction.title, GlobalScreenshot.ACTION_TYPE_DELETE); + assertNull(intent.getAction()); } } diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index ef8d524bee25..2d66b2399ee6 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; + import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.app.RemoteAction; @@ -395,7 +397,8 @@ public class SystemActionPerformer { ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null) ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext); screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true, true, new Handler(Looper.getMainLooper()), null); + true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS, + new Handler(Looper.getMainLooper()), null); return true; } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4624e9ea0209..2f84a99774f4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -62,6 +62,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION; import static android.view.WindowManagerGlobal.ADD_OKAY; @@ -1328,6 +1330,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenshotChordVolumeDownKeyConsumed = true; cancelPendingPowerKeyAction(); mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD); mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); } } @@ -1411,14 +1414,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { private class ScreenshotRunnable implements Runnable { private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; + private int mScreenshotSource = SCREENSHOT_KEY_OTHER; public void setScreenshotType(int screenshotType) { mScreenshotType = screenshotType; } + public void setScreenshotSource(int screenshotSource) { + mScreenshotSource = screenshotSource; + } + @Override public void run() { - mDefaultDisplayPolicy.takeScreenshot(mScreenshotType); + mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource); } } @@ -2693,6 +2701,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION : TAKE_SCREENSHOT_FULLSCREEN; mScreenshotRunnable.setScreenshotType(type); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); mHandler.post(mScreenshotRunnable); return -1; } @@ -2709,6 +2718,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { if (down && repeatCount == 0) { mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); mHandler.post(mScreenshotRunnable); } return -1; diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e9d3d56ee283..601f8df6af15 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -3783,13 +3783,14 @@ public class DisplayPolicy { * @param screenshotType The type of screenshot, for example either * {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or * {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} + * @param source Where the screenshot originated from (see WindowManager.ScreenshotSource) */ - public void takeScreenshot(int screenshotType) { + public void takeScreenshot(int screenshotType, int source) { if (mScreenshotHelper != null) { mScreenshotHelper.takeScreenshot(screenshotType, mStatusBar != null && mStatusBar.isVisibleLw(), mNavigationBar != null && mNavigationBar.isVisibleLw(), - mHandler, null /* completionConsumer */); + source, mHandler, null /* completionConsumer */); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index 335217719cc9..064e3486823a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; + import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -308,7 +310,7 @@ public class SystemActionPerformerTest { AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT); verify(mMockScreenshotHelper).takeScreenshot( eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(), - anyBoolean(), any(Handler.class), any()); + anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any()); } // PendingIntent is a final class and cannot be mocked. So we are using this -- cgit v1.2.3-59-g8ed1b