Add screenshots logging
Bug: 150710005 Test: manual Change-Id: I54a37eb0a62234c6c53fc0f3c80e18e9ee269f12
This commit is contained in:
@@ -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}
|
||||
* Enum listing the possible sources from which a screenshot was originated. Used for logging.
|
||||
*
|
||||
* @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.
|
||||
* @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
|
||||
|
||||
@@ -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<ScreenshotRequest> CREATOR =
|
||||
new Parcelable.Creator<ScreenshotRequest>() {
|
||||
|
||||
@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<Uri> 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<Uri> 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<Uri> 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<Uri> 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<Uri> completionConsumer) {
|
||||
private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
|
||||
ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<OverviewProxyLis
|
||||
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
|
||||
Insets visibleInsets, int taskId) {
|
||||
mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
|
||||
taskId, mHandler, null);
|
||||
taskId, SCREENSHOT_OVERVIEW, mHandler, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -76,6 +76,7 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@@ -104,7 +105,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
||||
*/
|
||||
static class SaveImageInBackgroundData {
|
||||
public Bitmap image;
|
||||
public Uri imageUri;
|
||||
public Consumer<Uri> 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<Notification.Action> 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<Notification.Action> smartActions,
|
||||
List<Notification.Action> 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<Notification.Action> smartActions,
|
||||
List<Notification.Action> 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<Notification.Action> smartActions, List<Notification.Action> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Notification.Action> smartActions,
|
||||
List<Notification.Action> 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -83,6 +83,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
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<Void, Void, Void> {
|
||||
|
||||
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<Void, Void, Void> {
|
||||
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<Void, Void, Void> {
|
||||
throw e;
|
||||
}
|
||||
|
||||
List<Notification.Action> actions =
|
||||
populateNotificationActions(mContext, r, uri);
|
||||
List<Notification.Action> smartActions = new ArrayList<>();
|
||||
if (mSmartActionsEnabled) {
|
||||
int timeoutMs = DeviceConfig.getInt(
|
||||
@@ -206,8 +207,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
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<Void, Void, Void> {
|
||||
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<Notification.Action> 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<Void, Void, Void> {
|
||||
// by setting the (otherwise unused) request code to the current user id.
|
||||
int requestCode = context.getUserId();
|
||||
|
||||
ArrayList<Notification.Action> 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<Void, Void, Void> {
|
||||
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<Void, Void, Void> {
|
||||
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<Void, Void, Void> {
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Notification.Action> smartActions,
|
||||
List<Notification.Action> 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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<List<Notification.Action>> smartActionsFuture =
|
||||
ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
|
||||
smartActionsProvider, true, false);
|
||||
Assert.assertNotNull(smartActionsFuture);
|
||||
assertNotNull(smartActionsFuture);
|
||||
List<Notification.Action> 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<Notification.Action> 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<Notification.Action> 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<Notification.Action> 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<Notification.Action> 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));
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user