Merge "Add screenshots logging" into rvc-dev am: 44cc1e274b
Change-Id: Ia8a3352ca08bfa9fdb6c67bc7450be5279f816b4
This commit is contained in:
@@ -514,33 +514,24 @@ public interface WindowManager extends ViewManager {
|
|||||||
int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3;
|
int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parcel key for the screen shot bitmap sent with messages of type
|
* Enum listing the possible sources from which a screenshot was originated. Used for logging.
|
||||||
* {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}, type {@link android.graphics.Bitmap}
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
String PARCEL_KEY_SCREENSHOT_BITMAP = "screenshot_screen_bitmap";
|
@IntDef({ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS,
|
||||||
|
ScreenshotSource.SCREENSHOT_KEY_CHORD,
|
||||||
/**
|
ScreenshotSource.SCREENSHOT_KEY_OTHER,
|
||||||
* Parcel key for the screen bounds of the image sent with messages of type
|
ScreenshotSource.SCREENSHOT_OVERVIEW,
|
||||||
* [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link Rect} in screen coordinates.
|
ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
|
||||||
* @hide
|
ScreenshotSource.SCREENSHOT_OTHER})
|
||||||
*/
|
@interface ScreenshotSource {
|
||||||
String PARCEL_KEY_SCREENSHOT_BOUNDS = "screenshot_screen_bounds";
|
int SCREENSHOT_GLOBAL_ACTIONS = 0;
|
||||||
|
int SCREENSHOT_KEY_CHORD = 1;
|
||||||
/**
|
int SCREENSHOT_KEY_OTHER = 2;
|
||||||
* Parcel key for the task id of the task that the screen shot was taken of, sent with messages
|
int SCREENSHOT_OVERVIEW = 3;
|
||||||
* of type [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type int.
|
int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4;
|
||||||
* @hide
|
int SCREENSHOT_OTHER = 5;
|
||||||
*/
|
}
|
||||||
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";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.android.internal.util;
|
package com.android.internal.util;
|
||||||
|
|
||||||
|
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -10,11 +12,12 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.Insets;
|
import android.graphics.Insets;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -23,6 +26,109 @@ import android.view.WindowManager;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class ScreenshotHelper {
|
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";
|
private static final String TAG = "ScreenshotHelper";
|
||||||
|
|
||||||
// Time until we give up on the screenshot & show an error instead.
|
// Time until we give up on the screenshot & show an error instead.
|
||||||
@@ -36,8 +142,10 @@ public class ScreenshotHelper {
|
|||||||
mContext = context;
|
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
|
* Added to support reducing unit test duration; the method variant without a timeout argument
|
||||||
* is recommended for general use.
|
* is recommended for general use.
|
||||||
@@ -47,6 +155,32 @@ public class ScreenshotHelper {
|
|||||||
* or
|
* or
|
||||||
* {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
|
* {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
|
||||||
* @param hasStatus {@code true} if the status bar is currently showing. {@code false}
|
* @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
|
* if
|
||||||
* not.
|
* not.
|
||||||
* @param hasNav {@code true} if the navigation bar is currently showing. {@code
|
* @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
|
* Added to support reducing unit test duration; the method variant without a timeout argument
|
||||||
* is recommended for general use.
|
* is recommended for general use.
|
||||||
@@ -89,9 +223,9 @@ public class ScreenshotHelper {
|
|||||||
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
|
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
|
||||||
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
|
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
|
||||||
@Nullable Consumer<Uri> completionConsumer) {
|
@Nullable Consumer<Uri> completionConsumer) {
|
||||||
takeScreenshot(screenshotType, hasStatus, hasNav, timeoutMs, handler, null,
|
ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
|
||||||
completionConsumer
|
hasNav);
|
||||||
);
|
takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,23 +240,16 @@ public class ScreenshotHelper {
|
|||||||
* screenshot was taken.
|
* screenshot was taken.
|
||||||
*/
|
*/
|
||||||
public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen,
|
public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen,
|
||||||
@NonNull Insets insets, int taskId, @NonNull Handler handler,
|
@NonNull Insets insets, int taskId, int source,
|
||||||
@Nullable Consumer<Uri> completionConsumer) {
|
@NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
|
||||||
Bundle imageBundle = new Bundle();
|
ScreenshotRequest screenshotRequest =
|
||||||
imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP, screenshot);
|
new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId);
|
||||||
imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS, boundsInScreen);
|
takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
|
||||||
imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_INSETS, insets);
|
handler, screenshotRequest, completionConsumer);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void takeScreenshot(final int screenshotType, final boolean hasStatus,
|
private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
|
||||||
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
|
ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
|
||||||
@Nullable Bundle providedImage, @Nullable Consumer<Uri> completionConsumer) {
|
|
||||||
synchronized (mScreenshotLock) {
|
synchronized (mScreenshotLock) {
|
||||||
if (mScreenshotConnection != null) {
|
if (mScreenshotConnection != null) {
|
||||||
return;
|
return;
|
||||||
@@ -157,7 +284,7 @@ public class ScreenshotHelper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Messenger messenger = new Messenger(service);
|
Messenger messenger = new Messenger(service);
|
||||||
Message msg = Message.obtain(null, screenshotType);
|
Message msg = Message.obtain(null, screenshotType, screenshotRequest);
|
||||||
final ServiceConnection myConn = this;
|
final ServiceConnection myConn = this;
|
||||||
Handler h = new Handler(handler.getLooper()) {
|
Handler h = new Handler(handler.getLooper()) {
|
||||||
@Override
|
@Override
|
||||||
@@ -175,12 +302,6 @@ public class ScreenshotHelper {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
msg.replyTo = new Messenger(h);
|
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 {
|
try {
|
||||||
messenger.send(msg);
|
messenger.send(msg);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import android.graphics.Insets;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
@@ -91,7 +92,8 @@ public final class ScreenshotHelperTest {
|
|||||||
public void testProvidedImageScreenshot() {
|
public void testProvidedImageScreenshot() {
|
||||||
mScreenshotHelper.provideScreenshot(
|
mScreenshotHelper.provideScreenshot(
|
||||||
Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(),
|
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
|
@Test
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.accessibility;
|
package com.android.systemui.accessibility;
|
||||||
|
|
||||||
|
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityService;
|
import android.accessibilityservice.AccessibilityService;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.RemoteAction;
|
import android.app.RemoteAction;
|
||||||
@@ -282,8 +284,8 @@ public class SystemActions extends SystemUI {
|
|||||||
|
|
||||||
private void handleTakeScreenshot() {
|
private void handleTakeScreenshot() {
|
||||||
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
|
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
|
||||||
screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
|
screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
|
||||||
true, true, new Handler(Looper.getMainLooper()), null);
|
SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAccessibilityMenu() {
|
private void handleAccessibilityMenu() {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ package com.android.systemui.globalactions;
|
|||||||
|
|
||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
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.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.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
|
||||||
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
|
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
|
||||||
@@ -830,7 +832,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
|||||||
mHandler.postDelayed(new Runnable() {
|
mHandler.postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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);
|
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
|
||||||
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
|
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_CANCEL;
|
||||||
import static android.view.MotionEvent.ACTION_DOWN;
|
import static android.view.MotionEvent.ACTION_DOWN;
|
||||||
import static android.view.MotionEvent.ACTION_UP;
|
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 android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
|
||||||
|
|
||||||
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
|
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,
|
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
|
||||||
Insets visibleInsets, int taskId) {
|
Insets visibleInsets, int taskId) {
|
||||||
mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
|
mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
|
||||||
taskId, mHandler, null);
|
taskId, SCREENSHOT_OVERVIEW, mHandler, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||||
@@ -104,7 +105,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
*/
|
*/
|
||||||
static class SaveImageInBackgroundData {
|
static class SaveImageInBackgroundData {
|
||||||
public Bitmap image;
|
public Bitmap image;
|
||||||
public Uri imageUri;
|
|
||||||
public Consumer<Uri> finisher;
|
public Consumer<Uri> finisher;
|
||||||
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
|
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
|
||||||
public int errorMsgResId;
|
public int errorMsgResId;
|
||||||
@@ -112,13 +112,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
|
|
||||||
void clearImage() {
|
void clearImage() {
|
||||||
image = null;
|
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 static class ActionsReadyListener {
|
||||||
abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions,
|
abstract void onActionsReady(SavedImageData imageData);
|
||||||
List<Notification.Action> actions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These strings are used for communicating the action invoked to
|
// 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 static final int MESSAGE_CORNER_TIMEOUT = 2;
|
||||||
|
|
||||||
private final ScreenshotNotificationsController mNotificationsController;
|
private final ScreenshotNotificationsController mNotificationsController;
|
||||||
|
private final UiEventLogger mUiEventLogger;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final WindowManager mWindowManager;
|
private final WindowManager mWindowManager;
|
||||||
@@ -185,6 +206,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MESSAGE_CORNER_TIMEOUT:
|
case MESSAGE_CORNER_TIMEOUT:
|
||||||
|
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
|
||||||
GlobalScreenshot.this.clearScreenshot("timeout");
|
GlobalScreenshot.this.clearScreenshot("timeout");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -199,9 +221,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
@Inject
|
@Inject
|
||||||
public GlobalScreenshot(
|
public GlobalScreenshot(
|
||||||
Context context, @Main Resources resources, LayoutInflater layoutInflater,
|
Context context, @Main Resources resources, LayoutInflater layoutInflater,
|
||||||
ScreenshotNotificationsController screenshotNotificationsController) {
|
ScreenshotNotificationsController screenshotNotificationsController,
|
||||||
|
UiEventLogger uiEventLogger) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mNotificationsController = screenshotNotificationsController;
|
mNotificationsController = screenshotNotificationsController;
|
||||||
|
mUiEventLogger = uiEventLogger;
|
||||||
|
|
||||||
// Inflate the screenshot layout
|
// Inflate the screenshot layout
|
||||||
mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
|
mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
|
||||||
@@ -222,7 +246,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
mBackgroundProtection = mScreenshotLayout.findViewById(
|
mBackgroundProtection = mScreenshotLayout.findViewById(
|
||||||
R.id.global_screenshot_actions_background);
|
R.id.global_screenshot_actions_background);
|
||||||
mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
|
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);
|
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
|
||||||
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
|
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
|
||||||
@@ -445,12 +472,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
|
|
||||||
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
|
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
|
||||||
@Override
|
@Override
|
||||||
void onActionsReady(Uri uri, List<Notification.Action> smartActions,
|
void onActionsReady(SavedImageData imageData) {
|
||||||
List<Notification.Action> actions) {
|
finisher.accept(imageData.uri);
|
||||||
if (uri == null) {
|
if (imageData.uri == null) {
|
||||||
|
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
|
||||||
mNotificationsController.notifyScreenshotError(
|
mNotificationsController.notifyScreenshotError(
|
||||||
R.string.screenshot_failed_to_capture_text);
|
R.string.screenshot_failed_to_capture_text);
|
||||||
} else {
|
} else {
|
||||||
|
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
|
||||||
mScreenshotHandler.post(() -> {
|
mScreenshotHandler.post(() -> {
|
||||||
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
|
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
|
||||||
mScreenshotAnimation.addListener(
|
mScreenshotAnimation.addListener(
|
||||||
@@ -458,13 +487,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
super.onAnimationEnd(animation);
|
super.onAnimationEnd(animation);
|
||||||
createScreenshotActionsShadeAnimation(
|
createScreenshotActionsShadeAnimation(imageData)
|
||||||
smartActions, actions).start();
|
.start();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createScreenshotActionsShadeAnimation(smartActions,
|
createScreenshotActionsShadeAnimation(imageData).start();
|
||||||
actions).start();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -567,8 +595,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
return dropInAnimation;
|
return dropInAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueAnimator createScreenshotActionsShadeAnimation(
|
private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
|
||||||
List<Notification.Action> smartActions, List<Notification.Action> actions) {
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||||
mActionsView.removeAllViews();
|
mActionsView.removeAllViews();
|
||||||
mActionsContainer.setScrollX(0);
|
mActionsContainer.setScrollX(0);
|
||||||
@@ -583,45 +610,63 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
|
|||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Notification.Action smartAction : smartActions) {
|
for (Notification.Action smartAction : imageData.smartActions) {
|
||||||
ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
|
ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
|
||||||
R.layout.global_screenshot_action_chip, mActionsView, false);
|
R.layout.global_screenshot_action_chip, mActionsView, false);
|
||||||
actionChip.setText(smartAction.title);
|
actionChip.setText(smartAction.title);
|
||||||
actionChip.setIcon(smartAction.getIcon(), false);
|
actionChip.setIcon(smartAction.getIcon(), false);
|
||||||
actionChip.setPendingIntent(smartAction.actionIntent,
|
actionChip.setPendingIntent(smartAction.actionIntent,
|
||||||
() -> clearScreenshot("chip tapped"));
|
() -> {
|
||||||
|
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
|
||||||
|
clearScreenshot("chip tapped");
|
||||||
|
});
|
||||||
mActionsView.addView(actionChip);
|
mActionsView.addView(actionChip);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Notification.Action action : actions) {
|
ScreenshotActionChip shareChip = (ScreenshotActionChip) inflater.inflate(
|
||||||
ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
|
R.layout.global_screenshot_action_chip, mActionsView, false);
|
||||||
R.layout.global_screenshot_action_chip, mActionsView, false);
|
shareChip.setText(imageData.shareAction.title);
|
||||||
actionChip.setText(action.title);
|
shareChip.setIcon(imageData.shareAction.getIcon(), true);
|
||||||
actionChip.setIcon(action.getIcon(), true);
|
shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> {
|
||||||
actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped"));
|
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
|
||||||
if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) {
|
clearScreenshot("chip tapped");
|
||||||
mScreenshotView.setOnClickListener(v -> {
|
});
|
||||||
try {
|
mActionsView.addView(shareChip);
|
||||||
action.actionIntent.send();
|
|
||||||
clearScreenshot("screenshot preview tapped");
|
ScreenshotActionChip editChip = (ScreenshotActionChip) inflater.inflate(
|
||||||
} catch (PendingIntent.CanceledException e) {
|
R.layout.global_screenshot_action_chip, mActionsView, false);
|
||||||
Log.e(TAG, "Intent cancelled", e);
|
editChip.setText(imageData.editAction.title);
|
||||||
}
|
editChip.setIcon(imageData.editAction.getIcon(), true);
|
||||||
});
|
editChip.setPendingIntent(imageData.editAction.actionIntent, () -> {
|
||||||
mScreenshotView.setContentDescription(action.title);
|
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)) {
|
if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
|
||||||
ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
|
ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
|
||||||
R.layout.global_screenshot_action_chip, mActionsView, false);
|
R.layout.global_screenshot_action_chip, mActionsView, false);
|
||||||
Toast scrollNotImplemented = Toast.makeText(
|
Toast scrollNotImplemented = Toast.makeText(
|
||||||
mContext, "Not implemented", Toast.LENGTH_SHORT);
|
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(
|
scrollChip.setIcon(
|
||||||
Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true);
|
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);
|
mActionsView.addView(scrollChip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import android.animation.AnimatorSet;
|
|||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.Notification;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@@ -53,7 +52,6 @@ import android.widget.Toast;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -347,14 +345,13 @@ public class GlobalScreenshotLegacy {
|
|||||||
// Save the screenshot once we have a bit of time now
|
// Save the screenshot once we have a bit of time now
|
||||||
saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
|
saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
|
||||||
@Override
|
@Override
|
||||||
void onActionsReady(Uri uri, List<Notification.Action> smartActions,
|
void onActionsReady(GlobalScreenshot.SavedImageData actionData) {
|
||||||
List<Notification.Action> actions) {
|
if (actionData.uri == null) {
|
||||||
if (uri == null) {
|
|
||||||
mNotificationsController.notifyScreenshotError(
|
mNotificationsController.notifyScreenshotError(
|
||||||
R.string.screenshot_failed_to_capture_text);
|
R.string.screenshot_failed_to_capture_text);
|
||||||
} else {
|
} else {
|
||||||
mNotificationsController
|
mNotificationsController
|
||||||
.showScreenshotActionsNotification(uri, smartActions, actions);
|
.showScreenshotActionsNotification(actionData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final GlobalScreenshot.SaveImageInBackgroundData mParams;
|
private final GlobalScreenshot.SaveImageInBackgroundData mParams;
|
||||||
|
private final GlobalScreenshot.SavedImageData mImageData;
|
||||||
private final String mImageFileName;
|
private final String mImageFileName;
|
||||||
private final long mImageTime;
|
private final long mImageTime;
|
||||||
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
|
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
|
||||||
@@ -93,6 +94,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
|
|
||||||
SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
|
SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mImageData = new GlobalScreenshot.SavedImageData();
|
||||||
|
|
||||||
// Prepare all the output metadata
|
// Prepare all the output metadata
|
||||||
mParams = data;
|
mParams = data;
|
||||||
@@ -145,6 +147,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
values.put(MediaColumns.IS_PENDING, 1);
|
values.put(MediaColumns.IS_PENDING, 1);
|
||||||
|
|
||||||
final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First, write the actual data for our screenshot
|
// First, write the actual data for our screenshot
|
||||||
try (OutputStream out = resolver.openOutputStream(uri)) {
|
try (OutputStream out = resolver.openOutputStream(uri)) {
|
||||||
@@ -192,8 +195,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Notification.Action> actions =
|
|
||||||
populateNotificationActions(mContext, r, uri);
|
|
||||||
List<Notification.Action> smartActions = new ArrayList<>();
|
List<Notification.Action> smartActions = new ArrayList<>();
|
||||||
if (mSmartActionsEnabled) {
|
if (mSmartActionsEnabled) {
|
||||||
int timeoutMs = DeviceConfig.getInt(
|
int timeoutMs = DeviceConfig.getInt(
|
||||||
@@ -206,8 +207,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
mSmartActionsProvider),
|
mSmartActionsProvider),
|
||||||
mContext));
|
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.image = null;
|
||||||
mParams.errorMsgResId = 0;
|
mParams.errorMsgResId = 0;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -216,29 +223,26 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
Slog.e(TAG, "unable to save screenshot", e);
|
Slog.e(TAG, "unable to save screenshot", e);
|
||||||
mParams.clearImage();
|
mParams.clearImage();
|
||||||
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
|
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
|
||||||
mParams.mActionsReadyListener.onActionsReady(null, null, null);
|
mImageData.reset();
|
||||||
|
mParams.mActionsReadyListener.onActionsReady(mImageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void params) {
|
|
||||||
mParams.finisher.accept(mParams.imageUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCancelled(Void params) {
|
protected void onCancelled(Void params) {
|
||||||
// If we are cancelled while the task is running in the background, we may get null
|
// 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. The finisher is expected to always be called back, so just use the baked-in
|
||||||
// params from the ctor in any case.
|
// params from the ctor in any case.
|
||||||
mParams.mActionsReadyListener.onActionsReady(null, null, null);
|
mImageData.reset();
|
||||||
|
mParams.mActionsReadyListener.onActionsReady(mImageData);
|
||||||
mParams.finisher.accept(null);
|
mParams.finisher.accept(null);
|
||||||
mParams.clearImage();
|
mParams.clearImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@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
|
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
|
||||||
// order to do some common work like dismissing the keyguard and sending
|
// order to do some common work like dismissing the keyguard and sending
|
||||||
// closeSystemWindows
|
// closeSystemWindows
|
||||||
@@ -263,8 +267,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
// by setting the (otherwise unused) request code to the current user id.
|
// by setting the (otherwise unused) request code to the current user id.
|
||||||
int requestCode = context.getUserId();
|
int requestCode = context.getUserId();
|
||||||
|
|
||||||
ArrayList<Notification.Action> actions = new ArrayList<>();
|
|
||||||
|
|
||||||
PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
|
PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
|
||||||
new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
|
new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
|
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(
|
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
|
||||||
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
|
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
|
||||||
r.getString(com.android.internal.R.string.share), shareAction);
|
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
|
// Create an edit intent, if a specific package is provided as the editor, then
|
||||||
// launch that directly
|
// 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_READ_URI_PERMISSION);
|
||||||
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_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
|
// Create a edit action
|
||||||
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
|
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
|
||||||
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
|
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(
|
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
|
||||||
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
|
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
|
||||||
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
|
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
|
||||||
actions.add(editActionBuilder.build());
|
|
||||||
|
|
||||||
if (mCreateDeleteAction) {
|
return editActionBuilder.build();
|
||||||
// Create a delete action for the notification
|
}
|
||||||
PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
|
|
||||||
new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
|
@VisibleForTesting
|
||||||
.putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
|
Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
|
||||||
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
|
// Make sure pending intents for the system user are still unique across users
|
||||||
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
|
// by setting the (otherwise unused) request code to the current user id.
|
||||||
mSmartActionsEnabled)
|
int requestCode = mContext.getUserId();
|
||||||
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
|
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
|
// Create a delete action for the notification
|
||||||
Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
|
PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
|
||||||
Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
|
new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
|
||||||
r.getString(com.android.internal.R.string.delete), deleteAction);
|
.putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
|
||||||
actions.add(deleteActionBuilder.build());
|
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
|
||||||
}
|
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
|
||||||
return actions;
|
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) {
|
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.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Picture;
|
import android.graphics.Picture;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
@@ -42,8 +41,6 @@ import com.android.systemui.R;
|
|||||||
import com.android.systemui.SystemUI;
|
import com.android.systemui.SystemUI;
|
||||||
import com.android.systemui.util.NotificationChannels;
|
import com.android.systemui.util.NotificationChannels;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
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.
|
* Shows a notification with the saved screenshot and actions that can be taken with it.
|
||||||
*
|
*
|
||||||
* @param imageUri URI for the saved image
|
* @param actionData SavedImageData struct with image URI and actions
|
||||||
* @param actions a list of notification actions which can be taken
|
|
||||||
*/
|
*/
|
||||||
public void showScreenshotActionsNotification(
|
public void showScreenshotActionsNotification(
|
||||||
Uri imageUri,
|
GlobalScreenshot.SavedImageData actionData) {
|
||||||
List<Notification.Action> smartActions,
|
mNotificationBuilder.addAction(actionData.shareAction);
|
||||||
List<Notification.Action> actions) {
|
mNotificationBuilder.addAction(actionData.editAction);
|
||||||
for (Notification.Action action : actions) {
|
mNotificationBuilder.addAction(actionData.deleteAction);
|
||||||
mNotificationBuilder.addAction(action);
|
for (Notification.Action smartAction : actionData.smartActions) {
|
||||||
}
|
|
||||||
for (Notification.Action smartAction : smartActions) {
|
|
||||||
mNotificationBuilder.addAction(smartAction);
|
mNotificationBuilder.addAction(smartAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the intent to show the screenshot in gallery
|
// Create the intent to show the screenshot in gallery
|
||||||
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
|
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
|
||||||
launchIntent.setDataAndType(imageUri, "image/png");
|
launchIntent.setDataAndType(actionData.uri, "image/png");
|
||||||
launchIntent.setFlags(
|
launchIntent.setFlags(
|
||||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
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.util.Log;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import com.android.internal.logging.UiEventLogger;
|
||||||
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -42,6 +45,7 @@ public class TakeScreenshotService extends Service {
|
|||||||
private final GlobalScreenshot mScreenshot;
|
private final GlobalScreenshot mScreenshot;
|
||||||
private final GlobalScreenshotLegacy mScreenshotLegacy;
|
private final GlobalScreenshotLegacy mScreenshotLegacy;
|
||||||
private final UserManager mUserManager;
|
private final UserManager mUserManager;
|
||||||
|
private final UiEventLogger mUiEventLogger;
|
||||||
|
|
||||||
private Handler mHandler = new Handler(Looper.myLooper()) {
|
private Handler mHandler = new Handler(Looper.myLooper()) {
|
||||||
@Override
|
@Override
|
||||||
@@ -64,14 +68,22 @@ public class TakeScreenshotService extends Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (mkephart): clean up once notifications flow is fully deprecated
|
// TODO: clean up once notifications flow is fully deprecated
|
||||||
boolean useCornerFlow = true;
|
boolean useCornerFlow = true;
|
||||||
|
|
||||||
|
ScreenshotHelper.ScreenshotRequest screenshotRequest =
|
||||||
|
(ScreenshotHelper.ScreenshotRequest) msg.obj;
|
||||||
|
|
||||||
|
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));
|
||||||
|
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
|
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
|
||||||
if (useCornerFlow) {
|
if (useCornerFlow) {
|
||||||
mScreenshot.takeScreenshot(finisher);
|
mScreenshot.takeScreenshot(finisher);
|
||||||
} else {
|
} else {
|
||||||
mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
|
mScreenshotLegacy.takeScreenshot(
|
||||||
|
finisher, screenshotRequest.getHasStatusBar(),
|
||||||
|
screenshotRequest.getHasNavBar());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
|
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
|
||||||
@@ -79,17 +91,15 @@ public class TakeScreenshotService extends Service {
|
|||||||
mScreenshot.takeScreenshotPartial(finisher);
|
mScreenshot.takeScreenshotPartial(finisher);
|
||||||
} else {
|
} else {
|
||||||
mScreenshotLegacy.takeScreenshotPartial(
|
mScreenshotLegacy.takeScreenshotPartial(
|
||||||
finisher, msg.arg1 > 0, msg.arg2 > 0);
|
finisher, screenshotRequest.getHasStatusBar(),
|
||||||
|
screenshotRequest.getHasNavBar());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
|
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
|
||||||
Bitmap screenshot = msg.getData().getParcelable(
|
Bitmap screenshot = screenshotRequest.getBitmap();
|
||||||
WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP);
|
Rect screenBounds = screenshotRequest.getBoundsInScreen();
|
||||||
Rect screenBounds = msg.getData().getParcelable(
|
Insets insets = screenshotRequest.getInsets();
|
||||||
WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS);
|
int taskId = screenshotRequest.getTaskId();
|
||||||
Insets insets = msg.getData().getParcelable(
|
|
||||||
WindowManager.PARCEL_KEY_SCREENSHOT_INSETS);
|
|
||||||
int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID);
|
|
||||||
if (useCornerFlow) {
|
if (useCornerFlow) {
|
||||||
mScreenshot.handleImageAsScreenshot(
|
mScreenshot.handleImageAsScreenshot(
|
||||||
screenshot, screenBounds, insets, taskId, finisher);
|
screenshot, screenBounds, insets, taskId, finisher);
|
||||||
@@ -106,10 +116,12 @@ public class TakeScreenshotService extends Service {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TakeScreenshotService(GlobalScreenshot globalScreenshot,
|
public TakeScreenshotService(GlobalScreenshot globalScreenshot,
|
||||||
GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
|
GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager,
|
||||||
|
UiEventLogger uiEventLogger) {
|
||||||
mScreenshot = globalScreenshot;
|
mScreenshot = globalScreenshot;
|
||||||
mScreenshotLegacy = globalScreenshotLegacy;
|
mScreenshotLegacy = globalScreenshotLegacy;
|
||||||
mUserManager = userManager;
|
mUserManager = userManager;
|
||||||
|
mUiEventLogger = uiEventLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
package com.android.systemui.screenshot;
|
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.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
@@ -40,7 +44,6 @@ import androidx.test.filters.SmallTest;
|
|||||||
import com.android.systemui.SystemUIFactory;
|
import com.android.systemui.SystemUIFactory;
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -82,9 +85,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||||
ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
|
ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
|
||||||
smartActionsProvider, true, false);
|
smartActionsProvider, true, false);
|
||||||
Assert.assertNotNull(smartActionsFuture);
|
assertNotNull(smartActionsFuture);
|
||||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||||
Assert.assertEquals(Collections.emptyList(), smartActions);
|
assertEquals(Collections.emptyList(), smartActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests any exception thrown in waiting for smart actions future to complete does
|
// Tests any exception thrown in waiting for smart actions future to complete does
|
||||||
@@ -99,7 +102,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
RuntimeException.class);
|
RuntimeException.class);
|
||||||
List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
|
List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
|
||||||
"", "", smartActionsFuture, timeoutMs, mSmartActionsProvider);
|
"", "", 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.
|
// 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);
|
mSmartActionsProvider, true, true);
|
||||||
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(),
|
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(),
|
||||||
eq(false));
|
eq(false));
|
||||||
Assert.assertNotNull(smartActionsFuture);
|
assertNotNull(smartActionsFuture);
|
||||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||||
Assert.assertEquals(Collections.emptyList(), smartActions);
|
assertEquals(Collections.emptyList(), smartActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once.
|
// Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once.
|
||||||
@@ -152,14 +155,14 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
|
ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
|
||||||
actionsProvider,
|
actionsProvider,
|
||||||
true, true);
|
true, true);
|
||||||
Assert.assertNotNull(smartActionsFuture);
|
assertNotNull(smartActionsFuture);
|
||||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||||
Assert.assertEquals(smartActions.size(), 0);
|
assertEquals(smartActions.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests for notification action extras.
|
// Tests for share action extras
|
||||||
@Test
|
@Test
|
||||||
public void testNotificationActionExtras() {
|
public void testShareActionExtras() {
|
||||||
if (Looper.myLooper() == null) {
|
if (Looper.myLooper() == null) {
|
||||||
Looper.prepare();
|
Looper.prepare();
|
||||||
}
|
}
|
||||||
@@ -169,35 +172,70 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
|
data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
|
||||||
data.finisher = null;
|
data.finisher = null;
|
||||||
data.mActionsReadyListener = null;
|
data.mActionsReadyListener = null;
|
||||||
data.createDeleteAction = true;
|
|
||||||
SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
|
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"));
|
Uri.parse("Screenshot_123.png"));
|
||||||
|
|
||||||
Assert.assertEquals(actions.size(), 3);
|
Intent intent = shareAction.actionIntent.getIntent();
|
||||||
boolean isShareFound = false;
|
assertNotNull(intent);
|
||||||
boolean isEditFound = false;
|
Bundle bundle = intent.getExtras();
|
||||||
boolean isDeleteFound = false;
|
assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
|
||||||
for (Notification.Action action : actions) {
|
assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
|
||||||
Intent intent = action.actionIntent.getIntent();
|
assertEquals(GlobalScreenshot.ACTION_TYPE_SHARE, shareAction.title);
|
||||||
Assert.assertNotNull(intent);
|
assertEquals(Intent.ACTION_SEND, intent.getAction());
|
||||||
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)) {
|
// Tests for edit action extras
|
||||||
isDeleteFound = intent.getAction() == null;
|
@Test
|
||||||
} else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_EDIT)) {
|
public void testEditActionExtras() {
|
||||||
isEditFound = Intent.ACTION_EDIT.equals(intent.getAction());
|
if (Looper.myLooper() == null) {
|
||||||
} else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_SHARE)) {
|
Looper.prepare();
|
||||||
isShareFound = Intent.ACTION_SEND.equals(intent.getAction());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertTrue(isEditFound);
|
GlobalScreenshot.SaveImageInBackgroundData
|
||||||
Assert.assertTrue(isDeleteFound);
|
data = new GlobalScreenshot.SaveImageInBackgroundData();
|
||||||
Assert.assertTrue(isShareFound);
|
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;
|
package com.android.server.accessibility;
|
||||||
|
|
||||||
|
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityService;
|
import android.accessibilityservice.AccessibilityService;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.RemoteAction;
|
import android.app.RemoteAction;
|
||||||
@@ -399,7 +401,8 @@ public class SystemActionPerformer {
|
|||||||
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
|
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
|
||||||
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
|
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
|
||||||
screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
|
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;
|
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_VOICE_INTERACTION;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
||||||
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
|
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_FULLSCREEN;
|
||||||
import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
|
import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
|
||||||
import static android.view.WindowManagerGlobal.ADD_OKAY;
|
import static android.view.WindowManagerGlobal.ADD_OKAY;
|
||||||
@@ -1328,6 +1330,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
mScreenshotChordVolumeDownKeyConsumed = true;
|
mScreenshotChordVolumeDownKeyConsumed = true;
|
||||||
cancelPendingPowerKeyAction();
|
cancelPendingPowerKeyAction();
|
||||||
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
|
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
|
||||||
|
mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);
|
||||||
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
|
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1411,14 +1414,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
|
|
||||||
private class ScreenshotRunnable implements Runnable {
|
private class ScreenshotRunnable implements Runnable {
|
||||||
private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
|
private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
|
||||||
|
private int mScreenshotSource = SCREENSHOT_KEY_OTHER;
|
||||||
|
|
||||||
public void setScreenshotType(int screenshotType) {
|
public void setScreenshotType(int screenshotType) {
|
||||||
mScreenshotType = screenshotType;
|
mScreenshotType = screenshotType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setScreenshotSource(int screenshotSource) {
|
||||||
|
mScreenshotSource = screenshotSource;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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
|
int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
|
||||||
: TAKE_SCREENSHOT_FULLSCREEN;
|
: TAKE_SCREENSHOT_FULLSCREEN;
|
||||||
mScreenshotRunnable.setScreenshotType(type);
|
mScreenshotRunnable.setScreenshotType(type);
|
||||||
|
mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
|
||||||
mHandler.post(mScreenshotRunnable);
|
mHandler.post(mScreenshotRunnable);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -2709,6 +2718,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
} else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
|
} else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
|
||||||
if (down && repeatCount == 0) {
|
if (down && repeatCount == 0) {
|
||||||
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
|
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
|
||||||
|
mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
|
||||||
mHandler.post(mScreenshotRunnable);
|
mHandler.post(mScreenshotRunnable);
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -3783,13 +3783,14 @@ public class DisplayPolicy {
|
|||||||
* @param screenshotType The type of screenshot, for example either
|
* @param screenshotType The type of screenshot, for example either
|
||||||
* {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or
|
* {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or
|
||||||
* {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
|
* {@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) {
|
if (mScreenshotHelper != null) {
|
||||||
mScreenshotHelper.takeScreenshot(screenshotType,
|
mScreenshotHelper.takeScreenshot(screenshotType,
|
||||||
mStatusBar != null && mStatusBar.isVisibleLw(),
|
mStatusBar != null && mStatusBar.isVisibleLw(),
|
||||||
mNavigationBar != null && mNavigationBar.isVisibleLw(),
|
mNavigationBar != null && mNavigationBar.isVisibleLw(),
|
||||||
mHandler, null /* completionConsumer */);
|
source, mHandler, null /* completionConsumer */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.server.accessibility;
|
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.hasItem;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
@@ -308,7 +310,7 @@ public class SystemActionPerformerTest {
|
|||||||
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
|
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
|
||||||
verify(mMockScreenshotHelper).takeScreenshot(
|
verify(mMockScreenshotHelper).takeScreenshot(
|
||||||
eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
|
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
|
// PendingIntent is a final class and cannot be mocked. So we are using this
|
||||||
|
|||||||
Reference in New Issue
Block a user