Merge "Make ScreenshotRequest handle hardware bitmap conversion" into tm-qpr-dev am: a5e45d3124
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20894854 Change-Id: I59821a3f0851624e07b169d83bc190bc9345556b Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
package com.android.internal.util;
|
package com.android.internal.util;
|
||||||
|
|
||||||
import static android.content.Intent.ACTION_USER_SWITCHED;
|
import static android.content.Intent.ACTION_USER_SWITCHED;
|
||||||
import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
|
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
@@ -11,29 +11,18 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.ColorSpace;
|
|
||||||
import android.graphics.Insets;
|
|
||||||
import android.graphics.ParcelableColorSpace;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.hardware.HardwareBuffer;
|
|
||||||
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;
|
||||||
import android.view.WindowManager.ScreenshotSource;
|
import android.view.WindowManager.ScreenshotSource;
|
||||||
import android.view.WindowManager.ScreenshotType;
|
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class ScreenshotHelper {
|
public class ScreenshotHelper {
|
||||||
@@ -41,212 +30,6 @@ public class ScreenshotHelper {
|
|||||||
public static final int SCREENSHOT_MSG_URI = 1;
|
public static final int SCREENSHOT_MSG_URI = 1;
|
||||||
public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
|
public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes a screenshot request.
|
|
||||||
*/
|
|
||||||
public static class ScreenshotRequest implements Parcelable {
|
|
||||||
@ScreenshotType
|
|
||||||
private final int mType;
|
|
||||||
|
|
||||||
@ScreenshotSource
|
|
||||||
private final int mSource;
|
|
||||||
|
|
||||||
private final Bundle mBitmapBundle;
|
|
||||||
private final Rect mBoundsInScreen;
|
|
||||||
private final Insets mInsets;
|
|
||||||
private final int mTaskId;
|
|
||||||
private final int mUserId;
|
|
||||||
private final ComponentName mTopComponent;
|
|
||||||
|
|
||||||
|
|
||||||
public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
|
|
||||||
this(type, source, /* topComponent */ null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
|
|
||||||
ComponentName topComponent) {
|
|
||||||
this(type,
|
|
||||||
source,
|
|
||||||
/* bitmapBundle*/ null,
|
|
||||||
/* boundsInScreen */ null,
|
|
||||||
/* insets */ null,
|
|
||||||
/* taskId */ -1,
|
|
||||||
/* userId */ -1,
|
|
||||||
topComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
|
|
||||||
Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
|
|
||||||
ComponentName topComponent) {
|
|
||||||
mType = type;
|
|
||||||
mSource = source;
|
|
||||||
mBitmapBundle = bitmapBundle;
|
|
||||||
mBoundsInScreen = boundsInScreen;
|
|
||||||
mInsets = insets;
|
|
||||||
mTaskId = taskId;
|
|
||||||
mUserId = userId;
|
|
||||||
mTopComponent = topComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreenshotRequest(Parcel in) {
|
|
||||||
mType = in.readInt();
|
|
||||||
mSource = in.readInt();
|
|
||||||
if (in.readInt() == 1) {
|
|
||||||
mBitmapBundle = in.readBundle(getClass().getClassLoader());
|
|
||||||
mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
|
|
||||||
mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
|
|
||||||
mTaskId = in.readInt();
|
|
||||||
mUserId = in.readInt();
|
|
||||||
mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
|
|
||||||
ComponentName.class);
|
|
||||||
} else {
|
|
||||||
mBitmapBundle = null;
|
|
||||||
mBoundsInScreen = null;
|
|
||||||
mInsets = null;
|
|
||||||
mTaskId = -1;
|
|
||||||
mUserId = -1;
|
|
||||||
mTopComponent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ScreenshotType
|
|
||||||
public int getType() {
|
|
||||||
return mType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ScreenshotSource
|
|
||||||
public int getSource() {
|
|
||||||
return mSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bundle getBitmapBundle() {
|
|
||||||
return mBitmapBundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Rect getBoundsInScreen() {
|
|
||||||
return mBoundsInScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Insets getInsets() {
|
|
||||||
return mInsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTaskId() {
|
|
||||||
return mTaskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getUserId() {
|
|
||||||
return mUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentName getTopComponent() {
|
|
||||||
return mTopComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeInt(mType);
|
|
||||||
dest.writeInt(mSource);
|
|
||||||
if (mBitmapBundle == null) {
|
|
||||||
dest.writeInt(0);
|
|
||||||
} else {
|
|
||||||
dest.writeInt(1);
|
|
||||||
dest.writeBundle(mBitmapBundle);
|
|
||||||
dest.writeParcelable(mBoundsInScreen, 0);
|
|
||||||
dest.writeParcelable(mInsets, 0);
|
|
||||||
dest.writeInt(mTaskId);
|
|
||||||
dest.writeInt(mUserId);
|
|
||||||
dest.writeParcelable(mTopComponent, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static final 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];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bundler used to convert between a hardware bitmap and a bundle without copying the internal
|
|
||||||
* content. This is expected to be used together with {@link #provideScreenshot} to handle a
|
|
||||||
* hardware bitmap as a screenshot.
|
|
||||||
*/
|
|
||||||
public static final class HardwareBitmapBundler {
|
|
||||||
private static final String KEY_BUFFER = "bitmap_util_buffer";
|
|
||||||
private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
|
|
||||||
|
|
||||||
private HardwareBitmapBundler() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Bundle that represents the given Bitmap.
|
|
||||||
* <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
|
|
||||||
* copies when passing across processes, only pass to processes you trust.
|
|
||||||
*
|
|
||||||
* <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
|
|
||||||
* returned Bundle should be treated as a standalone object.
|
|
||||||
*
|
|
||||||
* @param bitmap to convert to bundle
|
|
||||||
* @return a Bundle representing the bitmap, should only be parsed by
|
|
||||||
* {@link #bundleToHardwareBitmap(Bundle)}
|
|
||||||
*/
|
|
||||||
public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
|
|
||||||
if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Passed bitmap must have hardware config, found: " + bitmap.getConfig());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bitmap assumes SRGB for null color space
|
|
||||||
ParcelableColorSpace colorSpace =
|
|
||||||
bitmap.getColorSpace() == null
|
|
||||||
? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
|
|
||||||
: new ParcelableColorSpace(bitmap.getColorSpace());
|
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
|
|
||||||
bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
|
|
||||||
|
|
||||||
return bundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
|
|
||||||
*
|
|
||||||
* <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
|
|
||||||
* this Bitmap on to any other source.
|
|
||||||
*
|
|
||||||
* @param bundle containing the bitmap
|
|
||||||
* @return a hardware Bitmap
|
|
||||||
*/
|
|
||||||
public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
|
|
||||||
if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
|
|
||||||
throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
|
|
||||||
}
|
|
||||||
|
|
||||||
HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
|
|
||||||
ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
|
|
||||||
ParcelableColorSpace.class);
|
|
||||||
|
|
||||||
return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
|
|
||||||
colorSpace.getColorSpace());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
||||||
@@ -277,20 +60,35 @@ public class ScreenshotHelper {
|
|||||||
/**
|
/**
|
||||||
* Request a screenshot be taken.
|
* Request a screenshot be taken.
|
||||||
* <p>
|
* <p>
|
||||||
* Added to support reducing unit test duration; the method variant without a timeout argument
|
* Convenience method for taking a full screenshot with provided source.
|
||||||
* is recommended for general use.
|
|
||||||
*
|
*
|
||||||
* @param type The type of screenshot, defined by {@link ScreenshotType}
|
* @param source source of the screenshot request, defined by {@link
|
||||||
* @param source The source of the screenshot request, defined by {@link ScreenshotSource}
|
* ScreenshotSource}
|
||||||
* @param handler used to process messages received from the screenshot service
|
* @param handler used to process messages received from the screenshot service
|
||||||
* @param completionConsumer receives the URI of the captured screenshot, once saved or
|
* @param completionConsumer receives the URI of the captured screenshot, once saved or
|
||||||
* null if no screenshot was saved
|
* null if no screenshot was saved
|
||||||
*/
|
*/
|
||||||
public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
|
public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
|
||||||
@NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
|
@Nullable Consumer<Uri> completionConsumer) {
|
||||||
ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
|
ScreenshotRequest request =
|
||||||
takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
|
new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
|
||||||
completionConsumer);
|
takeScreenshot(request, handler, completionConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a screenshot be taken.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @param request description of the screenshot request, either for taking a
|
||||||
|
* screenshot or
|
||||||
|
* providing a bitmap
|
||||||
|
* @param handler used to process messages received from the screenshot service
|
||||||
|
* @param completionConsumer receives the URI of the captured screenshot, once saved or
|
||||||
|
* null if no screenshot was saved
|
||||||
|
*/
|
||||||
|
public void takeScreenshot(ScreenshotRequest request, @NonNull Handler handler,
|
||||||
|
@Nullable Consumer<Uri> completionConsumer) {
|
||||||
|
takeScreenshotInternal(request, handler, completionConsumer, SCREENSHOT_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -299,46 +97,16 @@ public class ScreenshotHelper {
|
|||||||
* 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.
|
||||||
*
|
*
|
||||||
* @param type The type of screenshot, defined by {@link ScreenshotType}
|
* @param request description of the screenshot request, either for taking a
|
||||||
* @param source The source of the screenshot request, defined by {@link ScreenshotSource}
|
* screenshot or providing a bitmap
|
||||||
* @param handler used to process messages received from the screenshot service
|
* @param handler used to process messages received from the screenshot service
|
||||||
* @param timeoutMs time limit for processing, intended only for testing
|
* @param timeoutMs time limit for processing, intended only for testing
|
||||||
* @param completionConsumer receives the URI of the captured screenshot, once saved or
|
* @param completionConsumer receives the URI of the captured screenshot, once saved or
|
||||||
* null if no screenshot was saved
|
* null if no screenshot was saved
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
|
public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
|
||||||
@NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
|
@Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
|
||||||
ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
|
|
||||||
takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request that provided image be handled as if it was a screenshot.
|
|
||||||
*
|
|
||||||
* @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
|
|
||||||
* @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
|
|
||||||
* @param insets The insets that the image was shown with, inside the screen bounds.
|
|
||||||
* @param taskId The taskId of the task that the screen shot was taken of.
|
|
||||||
* @param userId The userId of user running the task provided in taskId.
|
|
||||||
* @param topComponent The component name of the top component running in the task.
|
|
||||||
* @param source The source of the screenshot request, defined by {@link ScreenshotSource}
|
|
||||||
* @param handler A handler used in case the screenshot times out
|
|
||||||
* @param completionConsumer receives the URI of the captured screenshot, once saved or
|
|
||||||
* null if no screenshot was saved
|
|
||||||
*/
|
|
||||||
public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
|
|
||||||
@NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
|
|
||||||
@ScreenshotSource int source, @NonNull Handler handler,
|
|
||||||
@Nullable Consumer<Uri> completionConsumer) {
|
|
||||||
ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
|
|
||||||
source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
|
|
||||||
takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void takeScreenshot(@NonNull Handler handler,
|
|
||||||
ScreenshotRequest screenshotRequest, long timeoutMs,
|
|
||||||
@Nullable Consumer<Uri> completionConsumer) {
|
|
||||||
synchronized (mScreenshotLock) {
|
synchronized (mScreenshotLock) {
|
||||||
|
|
||||||
final Runnable mScreenshotTimeout = () -> {
|
final Runnable mScreenshotTimeout = () -> {
|
||||||
@@ -354,7 +122,7 @@ public class ScreenshotHelper {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Message msg = Message.obtain(null, 0, screenshotRequest);
|
Message msg = Message.obtain(null, 0, request);
|
||||||
|
|
||||||
Handler h = new Handler(handler.getLooper()) {
|
Handler h = new Handler(handler.getLooper()) {
|
||||||
@Override
|
@Override
|
||||||
@@ -471,5 +239,4 @@ public class ScreenshotHelper {
|
|||||||
Intent.FLAG_RECEIVER_FOREGROUND);
|
Intent.FLAG_RECEIVER_FOREGROUND);
|
||||||
mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
|
mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
core/java/com/android/internal/util/ScreenshotRequest.aidl
Normal file
19
core/java/com/android/internal/util/ScreenshotRequest.aidl
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.internal.util;
|
||||||
|
|
||||||
|
parcelable ScreenshotRequest;
|
||||||
332
core/java/com/android/internal/util/ScreenshotRequest.java
Normal file
332
core/java/com/android/internal/util/ScreenshotRequest.java
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.internal.util;
|
||||||
|
|
||||||
|
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||||
|
import static android.os.UserHandle.USER_NULL;
|
||||||
|
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
||||||
|
import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.ColorSpace;
|
||||||
|
import android.graphics.Insets;
|
||||||
|
import android.graphics.ParcelableColorSpace;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.hardware.HardwareBuffer;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a screenshot request.
|
||||||
|
*/
|
||||||
|
public class ScreenshotRequest implements Parcelable {
|
||||||
|
private static final String TAG = "ScreenshotRequest";
|
||||||
|
|
||||||
|
@WindowManager.ScreenshotType
|
||||||
|
private final int mType;
|
||||||
|
@WindowManager.ScreenshotSource
|
||||||
|
private final int mSource;
|
||||||
|
private final ComponentName mTopComponent;
|
||||||
|
private final int mTaskId;
|
||||||
|
private final int mUserId;
|
||||||
|
private final Bitmap mBitmap;
|
||||||
|
private final Rect mBoundsInScreen;
|
||||||
|
private final Insets mInsets;
|
||||||
|
|
||||||
|
private ScreenshotRequest(
|
||||||
|
@WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
|
||||||
|
ComponentName topComponent, int taskId, int userId,
|
||||||
|
Bitmap bitmap, Rect boundsInScreen, Insets insets) {
|
||||||
|
mType = type;
|
||||||
|
mSource = source;
|
||||||
|
mTopComponent = topComponent;
|
||||||
|
mTaskId = taskId;
|
||||||
|
mUserId = userId;
|
||||||
|
mBitmap = bitmap;
|
||||||
|
mBoundsInScreen = boundsInScreen;
|
||||||
|
mInsets = insets;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenshotRequest(Parcel in) {
|
||||||
|
mType = in.readInt();
|
||||||
|
mSource = in.readInt();
|
||||||
|
mTopComponent = in.readTypedObject(ComponentName.CREATOR);
|
||||||
|
mTaskId = in.readInt();
|
||||||
|
mUserId = in.readInt();
|
||||||
|
mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
|
||||||
|
mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
|
||||||
|
mInsets = in.readTypedObject(Insets.CREATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WindowManager.ScreenshotType
|
||||||
|
public int getType() {
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WindowManager.ScreenshotSource
|
||||||
|
public int getSource() {
|
||||||
|
return mSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getBitmap() {
|
||||||
|
return mBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect getBoundsInScreen() {
|
||||||
|
return mBoundsInScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Insets getInsets() {
|
||||||
|
return mInsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTaskId() {
|
||||||
|
return mTaskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUserId() {
|
||||||
|
return mUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentName getTopComponent() {
|
||||||
|
return mTopComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mType);
|
||||||
|
dest.writeInt(mSource);
|
||||||
|
dest.writeTypedObject(mTopComponent, 0);
|
||||||
|
dest.writeInt(mTaskId);
|
||||||
|
dest.writeInt(mUserId);
|
||||||
|
dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
|
||||||
|
dest.writeTypedObject(mBoundsInScreen, 0);
|
||||||
|
dest.writeTypedObject(mInsets, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static final 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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link ScreenshotRequest} objects.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
@WindowManager.ScreenshotType
|
||||||
|
private final int mType;
|
||||||
|
|
||||||
|
@WindowManager.ScreenshotSource
|
||||||
|
private final int mSource;
|
||||||
|
|
||||||
|
private Bitmap mBitmap;
|
||||||
|
private Rect mBoundsInScreen;
|
||||||
|
private Insets mInsets = Insets.NONE;
|
||||||
|
private int mTaskId = INVALID_TASK_ID;
|
||||||
|
private int mUserId = USER_NULL;
|
||||||
|
private ComponentName mTopComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin building a ScreenshotRequest.
|
||||||
|
*
|
||||||
|
* @param type The type of the screenshot request, defined by {@link
|
||||||
|
* WindowManager.ScreenshotType}
|
||||||
|
* @param source The source of the screenshot request, defined by {@link
|
||||||
|
* WindowManager.ScreenshotSource}
|
||||||
|
*/
|
||||||
|
public Builder(
|
||||||
|
@WindowManager.ScreenshotType int type,
|
||||||
|
@WindowManager.ScreenshotSource int source) {
|
||||||
|
mType = type;
|
||||||
|
mSource = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new {@link ScreenshotRequest} with the set parameters.
|
||||||
|
*/
|
||||||
|
public ScreenshotRequest build() {
|
||||||
|
if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) {
|
||||||
|
Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored.");
|
||||||
|
}
|
||||||
|
if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Request is PROVIDED_IMAGE, but no bitmap is provided!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
|
||||||
|
mBoundsInScreen, mInsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the top component associated with this request.
|
||||||
|
*
|
||||||
|
* @param topComponent The component name of the top component running in the task.
|
||||||
|
*/
|
||||||
|
public Builder setTopComponent(ComponentName topComponent) {
|
||||||
|
mTopComponent = topComponent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the task id associated with this request.
|
||||||
|
*
|
||||||
|
* @param taskId The taskId of the task that the screenshot was taken of.
|
||||||
|
*/
|
||||||
|
public Builder setTaskId(int taskId) {
|
||||||
|
mTaskId = taskId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user id associated with this request.
|
||||||
|
*
|
||||||
|
* @param userId The userId of user running the task provided in taskId.
|
||||||
|
*/
|
||||||
|
public Builder setUserId(int userId) {
|
||||||
|
mUserId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the bitmap associated with this request.
|
||||||
|
*
|
||||||
|
* @param bitmap The provided screenshot.
|
||||||
|
*/
|
||||||
|
public Builder setBitmap(Bitmap bitmap) {
|
||||||
|
mBitmap = bitmap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the bounds for the provided bitmap.
|
||||||
|
*
|
||||||
|
* @param bounds The bounds in screen coordinates that the bitmap originated from.
|
||||||
|
*/
|
||||||
|
public Builder setBoundsOnScreen(Rect bounds) {
|
||||||
|
mBoundsInScreen = bounds;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the insets for the provided bitmap.
|
||||||
|
*
|
||||||
|
* @param insets The insets that the image was shown with, inside the screen bounds.
|
||||||
|
*/
|
||||||
|
public Builder setInsets(@NonNull Insets insets) {
|
||||||
|
mInsets = insets;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundler used to convert between a hardware bitmap and a bundle without copying the internal
|
||||||
|
* content. This is used together with a fully-defined ScreenshotRequest to handle a hardware
|
||||||
|
* bitmap as a screenshot.
|
||||||
|
*/
|
||||||
|
private static final class HardwareBitmapBundler {
|
||||||
|
private static final String KEY_BUFFER = "bitmap_util_buffer";
|
||||||
|
private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
|
||||||
|
|
||||||
|
private HardwareBitmapBundler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Bundle that represents the given Bitmap.
|
||||||
|
* <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will
|
||||||
|
* avoid
|
||||||
|
* copies when passing across processes, only pass to processes you trust.
|
||||||
|
*
|
||||||
|
* <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions,
|
||||||
|
* the
|
||||||
|
* returned Bundle should be treated as a standalone object.
|
||||||
|
*
|
||||||
|
* @param bitmap to convert to bundle
|
||||||
|
* @return a Bundle representing the bitmap, should only be parsed by
|
||||||
|
* {@link #bundleToHardwareBitmap(Bundle)}
|
||||||
|
*/
|
||||||
|
private static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
|
||||||
|
if (bitmap == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Passed bitmap must have hardware config, found: "
|
||||||
|
+ bitmap.getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitmap assumes SRGB for null color space
|
||||||
|
ParcelableColorSpace colorSpace =
|
||||||
|
bitmap.getColorSpace() == null
|
||||||
|
? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
|
||||||
|
: new ParcelableColorSpace(bitmap.getColorSpace());
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
|
||||||
|
bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)}.
|
||||||
|
*
|
||||||
|
* <p>This Bitmap contains the HardwareBuffer from the original caller, be careful
|
||||||
|
* passing
|
||||||
|
* this Bitmap on to any other source.
|
||||||
|
*
|
||||||
|
* @param bundle containing the bitmap
|
||||||
|
* @return a hardware Bitmap
|
||||||
|
*/
|
||||||
|
private static Bitmap bundleToHardwareBitmap(Bundle bundle) {
|
||||||
|
if (bundle == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
|
||||||
|
throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
|
||||||
|
}
|
||||||
|
|
||||||
|
HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
|
||||||
|
ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
|
||||||
|
ParcelableColorSpace.class);
|
||||||
|
|
||||||
|
return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
|
||||||
|
colorSpace.getColorSpace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ android_test {
|
|||||||
srcs: [
|
srcs: [
|
||||||
"src/**/*.java",
|
"src/**/*.java",
|
||||||
],
|
],
|
||||||
|
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"frameworks-base-testutils",
|
"frameworks-base-testutils",
|
||||||
"androidx.test.runner",
|
"androidx.test.runner",
|
||||||
@@ -21,6 +21,7 @@ android_test {
|
|||||||
"androidx.test.ext.junit",
|
"androidx.test.ext.junit",
|
||||||
"mockito-target-minus-junit4",
|
"mockito-target-minus-junit4",
|
||||||
"platform-test-annotations",
|
"platform-test-annotations",
|
||||||
|
"testng",
|
||||||
],
|
],
|
||||||
|
|
||||||
libs: [
|
libs: [
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.internal.util;
|
package com.android.internal.util;
|
||||||
|
|
||||||
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
||||||
|
import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
|
||||||
|
|
||||||
import static junit.framework.Assert.assertNull;
|
import static junit.framework.Assert.assertNull;
|
||||||
import static junit.framework.Assert.fail;
|
import static junit.framework.Assert.fail;
|
||||||
@@ -31,9 +32,11 @@ import static org.mockito.Mockito.mock;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.ColorSpace;
|
||||||
import android.graphics.Insets;
|
import android.graphics.Insets;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Bundle;
|
import android.hardware.HardwareBuffer;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
@@ -79,30 +82,48 @@ public final class ScreenshotHelperTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFullscreenScreenshot() {
|
public void testFullscreenScreenshot() {
|
||||||
mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
|
mScreenshotHelper.takeScreenshot(
|
||||||
WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
|
WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFullscreenScreenshotRequest() {
|
||||||
|
ScreenshotRequest request = new ScreenshotRequest.Builder(
|
||||||
|
TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
|
||||||
|
.build();
|
||||||
|
mScreenshotHelper.takeScreenshot(request, mHandler, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProvidedImageScreenshot() {
|
public void testProvidedImageScreenshot() {
|
||||||
mScreenshotHelper.provideScreenshot(
|
HardwareBuffer buffer = HardwareBuffer.create(
|
||||||
new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
|
10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
|
||||||
WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
|
Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||||
|
ScreenshotRequest request = new ScreenshotRequest.Builder(
|
||||||
|
TAKE_SCREENSHOT_PROVIDED_IMAGE, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
|
||||||
|
.setTopComponent(new ComponentName("", ""))
|
||||||
|
.setTaskId(1)
|
||||||
|
.setUserId(1)
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.setBoundsOnScreen(new Rect())
|
||||||
|
.setInsets(Insets.NONE)
|
||||||
|
.build();
|
||||||
|
mScreenshotHelper.takeScreenshot(request, mHandler, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testScreenshotTimesOut() {
|
public void testScreenshotTimesOut() {
|
||||||
long timeoutMs = 10;
|
long timeoutMs = 10;
|
||||||
|
ScreenshotRequest request = new ScreenshotRequest.Builder(
|
||||||
|
TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
|
||||||
|
.build();
|
||||||
|
|
||||||
CountDownLatch lock = new CountDownLatch(1);
|
CountDownLatch lock = new CountDownLatch(1);
|
||||||
mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
|
mScreenshotHelper.takeScreenshotInternal(request, mHandler,
|
||||||
WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
|
|
||||||
mHandler,
|
|
||||||
timeoutMs,
|
|
||||||
uri -> {
|
uri -> {
|
||||||
assertNull(uri);
|
assertNull(uri);
|
||||||
lock.countDown();
|
lock.countDown();
|
||||||
});
|
}, timeoutMs);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Add tolerance for delay to prevent flakes.
|
// Add tolerance for delay to prevent flakes.
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.internal.util;
|
||||||
|
|
||||||
|
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||||
|
import static android.os.UserHandle.USER_NULL;
|
||||||
|
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
|
||||||
|
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
||||||
|
import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertEquals;
|
||||||
|
import static junit.framework.Assert.assertNull;
|
||||||
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertThrows;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.ColorSpace;
|
||||||
|
import android.graphics.Insets;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.hardware.HardwareBuffer;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class ScreenshotRequestTest {
|
||||||
|
private final ComponentName mComponentName =
|
||||||
|
new ComponentName("android.test", "android.test.Component");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleScreenshot() {
|
||||||
|
ScreenshotRequest in =
|
||||||
|
new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build();
|
||||||
|
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
in.writeToParcel(parcel, 0);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
|
||||||
|
|
||||||
|
assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
|
||||||
|
assertEquals(SCREENSHOT_OTHER, out.getSource());
|
||||||
|
assertNull("Top component was expected to be null", out.getTopComponent());
|
||||||
|
assertEquals(INVALID_TASK_ID, out.getTaskId());
|
||||||
|
assertEquals(USER_NULL, out.getUserId());
|
||||||
|
assertNull("Bitmap was expected to be null", out.getBitmap());
|
||||||
|
assertNull("Bounds were expected to be null", out.getBoundsInScreen());
|
||||||
|
assertEquals(Insets.NONE, out.getInsets());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProvidedScreenshot() {
|
||||||
|
Bitmap bitmap = makeHardwareBitmap(50, 50);
|
||||||
|
ScreenshotRequest in =
|
||||||
|
new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
|
||||||
|
.setTopComponent(mComponentName)
|
||||||
|
.setTaskId(2)
|
||||||
|
.setUserId(3)
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.setBoundsOnScreen(new Rect(10, 10, 60, 60))
|
||||||
|
.setInsets(Insets.of(2, 3, 4, 5))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
in.writeToParcel(parcel, 0);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
|
||||||
|
|
||||||
|
assertEquals(TAKE_SCREENSHOT_PROVIDED_IMAGE, out.getType());
|
||||||
|
assertEquals(SCREENSHOT_OTHER, out.getSource());
|
||||||
|
assertEquals(mComponentName, out.getTopComponent());
|
||||||
|
assertEquals(2, out.getTaskId());
|
||||||
|
assertEquals(3, out.getUserId());
|
||||||
|
assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
|
||||||
|
assertEquals(new Rect(10, 10, 60, 60), out.getBoundsInScreen());
|
||||||
|
assertEquals(Insets.of(2, 3, 4, 5), out.getInsets());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProvidedScreenshot_nullBitmap() {
|
||||||
|
ScreenshotRequest.Builder inBuilder =
|
||||||
|
new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
|
||||||
|
.setTopComponent(mComponentName)
|
||||||
|
.setTaskId(2)
|
||||||
|
.setUserId(3)
|
||||||
|
.setBoundsOnScreen(new Rect(10, 10, 60, 60))
|
||||||
|
.setInsets(Insets.of(2, 3, 4, 5));
|
||||||
|
|
||||||
|
assertThrows(IllegalStateException.class, inBuilder::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFullScreenshot_withBitmap() {
|
||||||
|
// A bitmap added to a FULLSCREEN request will be ignored, but it's technically valid
|
||||||
|
Bitmap bitmap = makeHardwareBitmap(50, 50);
|
||||||
|
ScreenshotRequest in =
|
||||||
|
new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
in.writeToParcel(parcel, 0);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
|
||||||
|
|
||||||
|
assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
|
||||||
|
assertEquals(SCREENSHOT_OTHER, out.getSource());
|
||||||
|
assertNull(out.getTopComponent());
|
||||||
|
assertEquals(INVALID_TASK_ID, out.getTaskId());
|
||||||
|
assertEquals(USER_NULL, out.getUserId());
|
||||||
|
assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
|
||||||
|
assertNull("Bounds expected to be null", out.getBoundsInScreen());
|
||||||
|
assertEquals(Insets.NONE, out.getInsets());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap makeHardwareBitmap(int width, int height) {
|
||||||
|
HardwareBuffer buffer = HardwareBuffer.create(
|
||||||
|
width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
|
||||||
|
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import android.graphics.Rect;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import com.android.internal.util.ScreenshotRequest;
|
||||||
|
|
||||||
import com.android.systemui.shared.recents.model.Task;
|
import com.android.systemui.shared.recents.model.Task;
|
||||||
|
|
||||||
@@ -86,12 +87,6 @@ interface ISystemUiProxy {
|
|||||||
*/
|
*/
|
||||||
void notifyPrioritizedRotation(int rotation) = 25;
|
void notifyPrioritizedRotation(int rotation) = 25;
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the provided image as if it was a screenshot.
|
|
||||||
*/
|
|
||||||
void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
|
|
||||||
in Insets visibleInsets, in Task.TaskKey task) = 28;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies to expand notification panel.
|
* Notifies to expand notification panel.
|
||||||
*/
|
*/
|
||||||
@@ -125,5 +120,10 @@ interface ISystemUiProxy {
|
|||||||
*/
|
*/
|
||||||
void toggleNotificationPanel() = 50;
|
void toggleNotificationPanel() = 50;
|
||||||
|
|
||||||
// Next id = 51
|
/**
|
||||||
|
* Handle the screenshot request.
|
||||||
|
*/
|
||||||
|
void takeScreenshot(in ScreenshotRequest request) = 51;
|
||||||
|
|
||||||
|
// Next id = 52
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import android.view.IWindowManager;
|
|||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.WindowManagerGlobal;
|
import android.view.WindowManagerGlobal;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
@@ -343,6 +342,7 @@ public class SystemActions implements CoreStartable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a system action.
|
* Register a system action.
|
||||||
|
*
|
||||||
* @param actionId the action ID to register.
|
* @param actionId the action ID to register.
|
||||||
*/
|
*/
|
||||||
public void register(int actionId) {
|
public void register(int actionId) {
|
||||||
@@ -440,6 +440,7 @@ public class SystemActions implements CoreStartable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister a system action.
|
* Unregister a system action.
|
||||||
|
*
|
||||||
* @param actionId the action ID to unregister.
|
* @param actionId the action ID to unregister.
|
||||||
*/
|
*/
|
||||||
public void unregister(int actionId) {
|
public void unregister(int actionId) {
|
||||||
@@ -475,7 +476,8 @@ public class SystemActions implements CoreStartable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleNotifications() {
|
private void handleNotifications() {
|
||||||
mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::animateExpandNotificationsPanel);
|
mCentralSurfacesOptionalLazy.get().ifPresent(
|
||||||
|
CentralSurfaces::animateExpandNotificationsPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleQuickSettings() {
|
private void handleQuickSettings() {
|
||||||
@@ -507,7 +509,7 @@ public class SystemActions implements CoreStartable {
|
|||||||
|
|
||||||
private void handleTakeScreenshot() {
|
private void handleTakeScreenshot() {
|
||||||
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
|
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
|
||||||
screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
|
screenshotHelper.takeScreenshot(
|
||||||
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
|
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
|||||||
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||||
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.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
|
||||||
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
|
||||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
|
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
|
||||||
|
|
||||||
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;
|
||||||
@@ -959,8 +958,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
|
|||||||
mHandler.postDelayed(new Runnable() {
|
mHandler.postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
|
mScreenshotHelper.takeScreenshot(SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
|
|||||||
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.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
|
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
|
||||||
@@ -44,8 +43,6 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.graphics.Insets;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
@@ -77,6 +74,7 @@ import com.android.internal.app.IVoiceInteractionSessionListener;
|
|||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.internal.policy.ScreenDecorationsUtils;
|
import com.android.internal.policy.ScreenDecorationsUtils;
|
||||||
import com.android.internal.util.ScreenshotHelper;
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
|
import com.android.internal.util.ScreenshotRequest;
|
||||||
import com.android.systemui.Dumpable;
|
import com.android.systemui.Dumpable;
|
||||||
import com.android.systemui.dagger.SysUISingleton;
|
import com.android.systemui.dagger.SysUISingleton;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
@@ -94,7 +92,6 @@ import com.android.systemui.settings.UserTracker;
|
|||||||
import com.android.systemui.shade.NotificationPanelViewController;
|
import com.android.systemui.shade.NotificationPanelViewController;
|
||||||
import com.android.systemui.shared.recents.IOverviewProxy;
|
import com.android.systemui.shared.recents.IOverviewProxy;
|
||||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||||
import com.android.systemui.shared.recents.model.Task;
|
|
||||||
import com.android.systemui.shared.system.QuickStepContract;
|
import com.android.systemui.shared.system.QuickStepContract;
|
||||||
import com.android.systemui.statusbar.CommandQueue;
|
import com.android.systemui.statusbar.CommandQueue;
|
||||||
import com.android.systemui.statusbar.NotificationShadeWindowController;
|
import com.android.systemui.statusbar.NotificationShadeWindowController;
|
||||||
@@ -322,18 +319,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
|
public void takeScreenshot(ScreenshotRequest request) {
|
||||||
Insets visibleInsets, Task.TaskKey task) {
|
mScreenshotHelper.takeScreenshot(request, mHandler, null);
|
||||||
mScreenshotHelper.provideScreenshot(
|
|
||||||
screenImageBundle,
|
|
||||||
locationInScreen,
|
|
||||||
visibleInsets,
|
|
||||||
task.id,
|
|
||||||
task.userId,
|
|
||||||
task.sourceComponent,
|
|
||||||
SCREENSHOT_OVERVIEW,
|
|
||||||
mHandler,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,27 +19,26 @@ package com.android.systemui.screenshot
|
|||||||
import android.graphics.Insets
|
import android.graphics.Insets
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||||
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
|
import com.android.internal.util.ScreenshotRequest
|
||||||
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
|
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.flags.FeatureFlags
|
import com.android.systemui.flags.FeatureFlags
|
||||||
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
|
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
|
||||||
import java.util.function.Consumer
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a screenshot request sent from {@link ScreenshotHelper}.
|
* Processes a screenshot request sent from {@link ScreenshotHelper}.
|
||||||
*/
|
*/
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class RequestProcessor @Inject constructor(
|
class RequestProcessor @Inject constructor(
|
||||||
private val capture: ImageCapture,
|
private val capture: ImageCapture,
|
||||||
private val policy: ScreenshotPolicy,
|
private val policy: ScreenshotPolicy,
|
||||||
private val flags: FeatureFlags,
|
private val flags: FeatureFlags,
|
||||||
/** For the Java Async version, to invoke the callback. */
|
/** For the Java Async version, to invoke the callback. */
|
||||||
@Application private val mainScope: CoroutineScope
|
@Application private val mainScope: CoroutineScope
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Inspects the incoming request, returning a potentially modified request depending on policy.
|
* Inspects the incoming request, returning a potentially modified request depending on policy.
|
||||||
@@ -58,7 +57,7 @@ class RequestProcessor @Inject constructor(
|
|||||||
// regardless of the managed profile status.
|
// regardless of the managed profile status.
|
||||||
|
|
||||||
if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
|
if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
|
||||||
flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
|
flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
|
val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
|
||||||
@@ -66,17 +65,21 @@ class RequestProcessor @Inject constructor(
|
|||||||
|
|
||||||
result = if (policy.isManagedProfile(info.user.identifier)) {
|
result = if (policy.isManagedProfile(info.user.identifier)) {
|
||||||
val image = capture.captureTask(info.taskId)
|
val image = capture.captureTask(info.taskId)
|
||||||
?: error("Task snapshot returned a null Bitmap!")
|
?: error("Task snapshot returned a null Bitmap!")
|
||||||
|
|
||||||
// Provide the task snapshot as the screenshot
|
// Provide the task snapshot as the screenshot
|
||||||
ScreenshotRequest(
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
|
||||||
TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
|
.setTopComponent(info.component)
|
||||||
HardwareBitmapBundler.hardwareBitmapToBundle(image),
|
.setTaskId(info.taskId)
|
||||||
info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
|
.setUserId(info.user.identifier)
|
||||||
)
|
.setBitmap(image)
|
||||||
|
.setBoundsOnScreen(info.bounds)
|
||||||
|
.setInsets(Insets.NONE)
|
||||||
|
.build()
|
||||||
} else {
|
} else {
|
||||||
// Create a new request of the same type which includes the top component
|
// Create a new request of the same type which includes the top component
|
||||||
ScreenshotRequest(request.type, request.source, info.component)
|
ScreenshotRequest.Builder(request.type, request.source)
|
||||||
|
.setTopComponent(info.component).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -416,10 +416,11 @@ public class ScreenshotController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean showFlash = false;
|
boolean showFlash = false;
|
||||||
if (!aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
|
if (screenshotScreenBounds == null
|
||||||
|
|| !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
|
||||||
showFlash = true;
|
showFlash = true;
|
||||||
visibleInsets = Insets.NONE;
|
visibleInsets = Insets.NONE;
|
||||||
screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
|
screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
|
||||||
}
|
}
|
||||||
mCurrentRequestCallback = requestCallback;
|
mCurrentRequestCallback = requestCallback;
|
||||||
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
|
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.internal.util.ScreenshotHelper;
|
import com.android.internal.util.ScreenshotRequest;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.dagger.qualifiers.Background;
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
@@ -186,8 +186,7 @@ public class TakeScreenshotService extends Service {
|
|||||||
final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
|
final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
|
||||||
RequestCallback callback = new RequestCallbackImpl(replyTo);
|
RequestCallback callback = new RequestCallbackImpl(replyTo);
|
||||||
|
|
||||||
ScreenshotHelper.ScreenshotRequest request =
|
ScreenshotRequest request = (ScreenshotRequest) msg.obj;
|
||||||
(ScreenshotHelper.ScreenshotRequest) msg.obj;
|
|
||||||
|
|
||||||
handleRequest(request, onSaved, callback);
|
handleRequest(request, onSaved, callback);
|
||||||
return true;
|
return true;
|
||||||
@@ -195,7 +194,7 @@ public class TakeScreenshotService extends Service {
|
|||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
|
void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
|
||||||
RequestCallback callback) {
|
RequestCallback callback) {
|
||||||
// If the storage for this user is locked, we have no place to store
|
// If the storage for this user is locked, we have no place to store
|
||||||
// the screenshot, so skip taking it instead of showing a misleading
|
// the screenshot, so skip taking it instead of showing a misleading
|
||||||
@@ -226,7 +225,7 @@ public class TakeScreenshotService extends Service {
|
|||||||
(r) -> dispatchToController(r, onSaved, callback));
|
(r) -> dispatchToController(r, onSaved, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
|
private void dispatchToController(ScreenshotRequest request,
|
||||||
Consumer<Uri> uriConsumer, RequestCallback callback) {
|
Consumer<Uri> uriConsumer, RequestCallback callback) {
|
||||||
|
|
||||||
ComponentName topComponent = request.getTopComponent();
|
ComponentName topComponent = request.getTopComponent();
|
||||||
@@ -244,8 +243,7 @@ public class TakeScreenshotService extends Service {
|
|||||||
if (DEBUG_SERVICE) {
|
if (DEBUG_SERVICE) {
|
||||||
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
|
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
|
||||||
}
|
}
|
||||||
Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
|
Bitmap screenshot = request.getBitmap();
|
||||||
request.getBitmapBundle());
|
|
||||||
Rect screenBounds = request.getBoundsInScreen();
|
Rect screenBounds = request.getBoundsInScreen();
|
||||||
Insets insets = request.getInsets();
|
Insets insets = request.getInsets();
|
||||||
int taskId = request.getTaskId();
|
int taskId = request.getTaskId();
|
||||||
|
|||||||
@@ -22,15 +22,12 @@ import android.graphics.ColorSpace
|
|||||||
import android.graphics.Insets
|
import android.graphics.Insets
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.hardware.HardwareBuffer
|
import android.hardware.HardwareBuffer
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
|
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
|
||||||
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
|
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
|
||||||
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
|
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
|
||||||
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||||
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
|
import com.android.internal.util.ScreenshotRequest
|
||||||
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
|
|
||||||
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
|
|
||||||
import com.android.systemui.flags.FakeFeatureFlags
|
import com.android.systemui.flags.FakeFeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
|
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
|
||||||
@@ -49,7 +46,6 @@ class RequestProcessorTest {
|
|||||||
private val bounds = Rect(25, 25, 75, 75)
|
private val bounds = Rect(25, 25, 75, 75)
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.Unconfined)
|
private val scope = CoroutineScope(Dispatchers.Unconfined)
|
||||||
private val dispatcher = Dispatchers.Unconfined
|
|
||||||
private val policy = FakeScreenshotPolicy()
|
private val policy = FakeScreenshotPolicy()
|
||||||
private val flags = FakeFeatureFlags()
|
private val flags = FakeFeatureFlags()
|
||||||
|
|
||||||
@@ -58,7 +54,8 @@ class RequestProcessorTest {
|
|||||||
fun testProcessAsync() {
|
fun testProcessAsync() {
|
||||||
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
|
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
val request =
|
||||||
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
|
||||||
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
||||||
|
|
||||||
var result: ScreenshotRequest? = null
|
var result: ScreenshotRequest? = null
|
||||||
@@ -80,7 +77,8 @@ class RequestProcessorTest {
|
|||||||
fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
|
fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
|
||||||
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
|
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
val request =
|
||||||
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
|
||||||
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
||||||
|
|
||||||
val processedRequest = processor.process(request)
|
val processedRequest = processor.process(request)
|
||||||
@@ -97,9 +95,11 @@ class RequestProcessorTest {
|
|||||||
policy.setManagedProfile(USER_ID, false)
|
policy.setManagedProfile(USER_ID, false)
|
||||||
policy.setDisplayContentInfo(
|
policy.setDisplayContentInfo(
|
||||||
policy.getDefaultDisplayId(),
|
policy.getDefaultDisplayId(),
|
||||||
DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
|
DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
|
||||||
|
)
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
|
val request =
|
||||||
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
|
||||||
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
||||||
|
|
||||||
val processedRequest = processor.process(request)
|
val processedRequest = processor.process(request)
|
||||||
@@ -120,17 +120,20 @@ class RequestProcessorTest {
|
|||||||
|
|
||||||
// Indicate that the primary content belongs to a manged profile
|
// Indicate that the primary content belongs to a manged profile
|
||||||
policy.setManagedProfile(USER_ID, true)
|
policy.setManagedProfile(USER_ID, true)
|
||||||
policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
|
policy.setDisplayContentInfo(
|
||||||
DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
|
policy.getDefaultDisplayId(),
|
||||||
|
DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
|
||||||
|
)
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
val request =
|
||||||
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
|
||||||
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
||||||
|
|
||||||
val processedRequest = processor.process(request)
|
val processedRequest = processor.process(request)
|
||||||
|
|
||||||
// Expect a task snapshot is taken, overriding the full screen mode
|
// Expect a task snapshot is taken, overriding the full screen mode
|
||||||
assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
|
assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
|
||||||
assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
|
assertThat(bitmap.equalsHardwareBitmap(processedRequest.bitmap)).isTrue()
|
||||||
assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
|
assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
|
||||||
assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
|
assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
|
||||||
assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
|
assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
|
||||||
@@ -147,10 +150,16 @@ class RequestProcessorTest {
|
|||||||
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
||||||
|
|
||||||
val bitmap = makeHardwareBitmap(100, 100)
|
val bitmap = makeHardwareBitmap(100, 100)
|
||||||
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
|
val request =
|
||||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
|
||||||
|
.setTopComponent(component)
|
||||||
|
.setTaskId(TASK_ID)
|
||||||
|
.setUserId(USER_ID)
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.setBoundsOnScreen(bounds)
|
||||||
|
.setInsets(Insets.NONE)
|
||||||
|
.build()
|
||||||
|
|
||||||
val processedRequest = processor.process(request)
|
val processedRequest = processor.process(request)
|
||||||
|
|
||||||
@@ -168,10 +177,16 @@ class RequestProcessorTest {
|
|||||||
policy.setManagedProfile(USER_ID, false)
|
policy.setManagedProfile(USER_ID, false)
|
||||||
|
|
||||||
val bitmap = makeHardwareBitmap(100, 100)
|
val bitmap = makeHardwareBitmap(100, 100)
|
||||||
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
|
val request =
|
||||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
|
||||||
|
.setTopComponent(component)
|
||||||
|
.setTaskId(TASK_ID)
|
||||||
|
.setUserId(USER_ID)
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.setBoundsOnScreen(bounds)
|
||||||
|
.setInsets(Insets.NONE)
|
||||||
|
.build()
|
||||||
|
|
||||||
val processedRequest = processor.process(request)
|
val processedRequest = processor.process(request)
|
||||||
|
|
||||||
@@ -190,10 +205,16 @@ class RequestProcessorTest {
|
|||||||
policy.setManagedProfile(USER_ID, true)
|
policy.setManagedProfile(USER_ID, true)
|
||||||
|
|
||||||
val bitmap = makeHardwareBitmap(100, 100)
|
val bitmap = makeHardwareBitmap(100, 100)
|
||||||
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
|
val request =
|
||||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
|
||||||
|
.setTopComponent(component)
|
||||||
|
.setTaskId(TASK_ID)
|
||||||
|
.setUserId(USER_ID)
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.setBoundsOnScreen(bounds)
|
||||||
|
.setInsets(Insets.NONE)
|
||||||
|
.build()
|
||||||
|
|
||||||
val processedRequest = processor.process(request)
|
val processedRequest = processor.process(request)
|
||||||
|
|
||||||
@@ -202,14 +223,18 @@ class RequestProcessorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
|
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
|
||||||
val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
|
val buffer =
|
||||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
|
HardwareBuffer.create(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
HardwareBuffer.RGBA_8888,
|
||||||
|
1,
|
||||||
|
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
|
||||||
|
)
|
||||||
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
|
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
|
private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
|
||||||
val provided = bundleToHardwareBitmap(bundle)
|
return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
|
||||||
return provided.hardwareBuffer == this.hardwareBuffer &&
|
|
||||||
provided.colorSpace == this.colorSpace
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
|
|||||||
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.logging.testing.UiEventLoggerFake
|
import com.android.internal.logging.testing.UiEventLoggerFake
|
||||||
import com.android.internal.util.ScreenshotHelper
|
import com.android.internal.util.ScreenshotRequest
|
||||||
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
|
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.flags.FakeFeatureFlags
|
import com.android.systemui.flags.FakeFeatureFlags
|
||||||
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
|
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
|
||||||
@@ -80,24 +79,39 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
|||||||
private val flags = FakeFeatureFlags()
|
private val flags = FakeFeatureFlags()
|
||||||
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
|
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
|
||||||
|
|
||||||
private val service = TakeScreenshotService(
|
private val service =
|
||||||
controller, userManager, devicePolicyManager, eventLogger,
|
TakeScreenshotService(
|
||||||
notificationsController, mContext, Runnable::run, flags, requestProcessor)
|
controller,
|
||||||
|
userManager,
|
||||||
|
devicePolicyManager,
|
||||||
|
eventLogger,
|
||||||
|
notificationsController,
|
||||||
|
mContext,
|
||||||
|
Runnable::run,
|
||||||
|
flags,
|
||||||
|
requestProcessor
|
||||||
|
)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
|
whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
|
||||||
whenever(devicePolicyManager.getScreenCaptureDisabled(
|
whenever(
|
||||||
/* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
|
devicePolicyManager.getScreenCaptureDisabled(
|
||||||
|
/* admin component (null: any admin) */ isNull(),
|
||||||
|
eq(UserHandle.USER_ALL)
|
||||||
|
)
|
||||||
|
)
|
||||||
.thenReturn(false)
|
.thenReturn(false)
|
||||||
whenever(userManager.isUserUnlocked).thenReturn(true)
|
whenever(userManager.isUserUnlocked).thenReturn(true)
|
||||||
|
|
||||||
// Stub request processor as a synchronous no-op for tests with the flag enabled
|
// Stub request processor as a synchronous no-op for tests with the flag enabled
|
||||||
doAnswer {
|
doAnswer {
|
||||||
val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
|
val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
|
||||||
val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
|
val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
|
||||||
consumer.accept(request)
|
consumer.accept(request)
|
||||||
}.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
|
}
|
||||||
|
.`when`(requestProcessor)
|
||||||
|
.processAsync(/* request= */ any(), /* callback= */ any())
|
||||||
|
|
||||||
// Flipped in selected test cases
|
// Flipped in selected test cases
|
||||||
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
|
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
|
||||||
@@ -108,7 +122,8 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
|||||||
/* className = */ null,
|
/* className = */ null,
|
||||||
/* token = */ null,
|
/* token = */ null,
|
||||||
application,
|
application,
|
||||||
/* activityManager = */ null)
|
/* activityManager = */ null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -125,63 +140,89 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun takeScreenshotFullscreen() {
|
fun takeScreenshotFullscreen() {
|
||||||
val request = ScreenshotRequest(
|
val request =
|
||||||
TAKE_SCREENSHOT_FULLSCREEN,
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||||
SCREENSHOT_KEY_CHORD,
|
.setTopComponent(topComponent)
|
||||||
topComponent)
|
.build()
|
||||||
|
|
||||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
service.handleRequest(request, { /* onSaved */}, callback)
|
||||||
|
|
||||||
verify(controller, times(1)).takeScreenshotFullscreen(
|
verify(controller, times(1))
|
||||||
eq(topComponent),
|
.takeScreenshotFullscreen(
|
||||||
/* onSavedListener = */ any(),
|
eq(topComponent),
|
||||||
/* requestCallback = */ any())
|
/* onSavedListener = */ any(),
|
||||||
|
/* requestCallback = */ any()
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
|
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
|
||||||
val logEvent = eventLogger.get(0)
|
val logEvent = eventLogger.get(0)
|
||||||
|
|
||||||
assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
|
assertEquals(
|
||||||
logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
|
"Expected SCREENSHOT_REQUESTED UiEvent",
|
||||||
assertEquals("Expected supplied package name",
|
logEvent.eventId,
|
||||||
topComponent.packageName, eventLogger.get(0).packageName)
|
SCREENSHOT_REQUESTED_KEY_CHORD.id
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"Expected supplied package name",
|
||||||
|
topComponent.packageName,
|
||||||
|
eventLogger.get(0).packageName
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun takeScreenshotProvidedImage() {
|
fun takeScreenshotProvidedImage() {
|
||||||
val bounds = Rect(50, 50, 150, 150)
|
val bounds = Rect(50, 50, 150, 150)
|
||||||
val bitmap = makeHardwareBitmap(100, 100)
|
val bitmap = makeHardwareBitmap(100, 100)
|
||||||
val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
|
||||||
|
|
||||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
|
val request =
|
||||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
|
||||||
|
.setTopComponent(topComponent)
|
||||||
|
.setTaskId(TASK_ID)
|
||||||
|
.setUserId(USER_ID)
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.setBoundsOnScreen(bounds)
|
||||||
|
.setInsets(Insets.NONE)
|
||||||
|
.build()
|
||||||
|
|
||||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
service.handleRequest(request, { /* onSaved */}, callback)
|
||||||
|
|
||||||
verify(controller, times(1)).handleImageAsScreenshot(
|
verify(controller, times(1))
|
||||||
argThat { b -> b.equalsHardwareBitmap(bitmap) },
|
.handleImageAsScreenshot(
|
||||||
eq(bounds),
|
argThat { b -> b.equalsHardwareBitmap(bitmap) },
|
||||||
eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
|
eq(bounds),
|
||||||
/* onSavedListener = */ any(), /* requestCallback = */ any())
|
eq(Insets.NONE),
|
||||||
|
eq(TASK_ID),
|
||||||
|
eq(USER_ID),
|
||||||
|
eq(topComponent),
|
||||||
|
/* onSavedListener = */ any(),
|
||||||
|
/* requestCallback = */ any()
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
|
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
|
||||||
val logEvent = eventLogger.get(0)
|
val logEvent = eventLogger.get(0)
|
||||||
|
|
||||||
assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
|
assertEquals(
|
||||||
logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
|
"Expected SCREENSHOT_REQUESTED_* UiEvent",
|
||||||
assertEquals("Expected supplied package name",
|
logEvent.eventId,
|
||||||
topComponent.packageName, eventLogger.get(0).packageName)
|
SCREENSHOT_REQUESTED_OVERVIEW.id
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"Expected supplied package name",
|
||||||
|
topComponent.packageName,
|
||||||
|
eventLogger.get(0).packageName
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun takeScreenshotFullscreen_userLocked() {
|
fun takeScreenshotFullscreen_userLocked() {
|
||||||
whenever(userManager.isUserUnlocked).thenReturn(false)
|
whenever(userManager.isUserUnlocked).thenReturn(false)
|
||||||
|
|
||||||
val request = ScreenshotRequest(
|
val request =
|
||||||
TAKE_SCREENSHOT_FULLSCREEN,
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||||
SCREENSHOT_KEY_CHORD,
|
.setTopComponent(topComponent)
|
||||||
topComponent)
|
.build()
|
||||||
|
|
||||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
service.handleRequest(request, { /* onSaved */}, callback)
|
||||||
|
|
||||||
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
|
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
|
||||||
verify(callback, times(1)).reportError()
|
verify(callback, times(1)).reportError()
|
||||||
@@ -190,21 +231,24 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
|
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
|
||||||
whenever(devicePolicyManager.getScreenCaptureDisabled(
|
whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
|
||||||
isNull(), eq(UserHandle.USER_ALL))
|
.thenReturn(true)
|
||||||
).thenReturn(true)
|
|
||||||
|
|
||||||
whenever(devicePolicyResourcesManager.getString(
|
whenever(
|
||||||
eq(SCREENSHOT_BLOCKED_BY_ADMIN),
|
devicePolicyResourcesManager.getString(
|
||||||
/* Supplier<String> */ any(),
|
eq(SCREENSHOT_BLOCKED_BY_ADMIN),
|
||||||
)).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
|
/* Supplier<String> */
|
||||||
|
any(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
|
||||||
|
|
||||||
val request = ScreenshotRequest(
|
val request =
|
||||||
TAKE_SCREENSHOT_FULLSCREEN,
|
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||||
SCREENSHOT_KEY_CHORD,
|
.setTopComponent(topComponent)
|
||||||
topComponent)
|
.build()
|
||||||
|
|
||||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
service.handleRequest(request, { /* onSaved */}, callback)
|
||||||
|
|
||||||
// error shown: Toast.makeText(...).show(), untestable
|
// error shown: Toast.makeText(...).show(), untestable
|
||||||
verify(callback, times(1)).reportError()
|
verify(callback, times(1)).reportError()
|
||||||
@@ -214,14 +258,20 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
|
private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
|
||||||
return config == HARDWARE &&
|
return config == HARDWARE &&
|
||||||
other.config == HARDWARE &&
|
other.config == HARDWARE &&
|
||||||
hardwareBuffer == other.hardwareBuffer &&
|
hardwareBuffer == other.hardwareBuffer &&
|
||||||
colorSpace == other.colorSpace
|
colorSpace == other.colorSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
|
/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
|
||||||
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
|
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
|
||||||
val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
|
val buffer =
|
||||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
|
HardwareBuffer.create(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
HardwareBuffer.RGBA_8888,
|
||||||
|
1,
|
||||||
|
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
|
||||||
|
)
|
||||||
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
|
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ public class SystemActionPerformer {
|
|||||||
private boolean takeScreenshot() {
|
private boolean takeScreenshot() {
|
||||||
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
|
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
|
||||||
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
|
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
|
||||||
screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
|
screenshotHelper.takeScreenshot(
|
||||||
WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
|
WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
|
||||||
new Handler(Looper.getMainLooper()), null);
|
new Handler(Looper.getMainLooper()), null);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.server.app;
|
package com.android.server.app;
|
||||||
|
|
||||||
|
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
|
||||||
|
import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
@@ -33,7 +36,6 @@ 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.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.service.games.CreateGameSessionRequest;
|
import android.service.games.CreateGameSessionRequest;
|
||||||
@@ -50,7 +52,6 @@ import android.text.TextUtils;
|
|||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.SurfaceControlViewHost.SurfacePackage;
|
import android.view.SurfaceControlViewHost.SurfacePackage;
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
@@ -59,6 +60,7 @@ import com.android.internal.infra.ServiceConnector;
|
|||||||
import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
|
import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
|
||||||
import com.android.internal.os.BackgroundThread;
|
import com.android.internal.os.BackgroundThread;
|
||||||
import com.android.internal.util.ScreenshotHelper;
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
|
import com.android.internal.util.ScreenshotRequest;
|
||||||
import com.android.server.wm.ActivityTaskManagerInternal;
|
import com.android.server.wm.ActivityTaskManagerInternal;
|
||||||
import com.android.server.wm.WindowManagerInternal;
|
import com.android.server.wm.WindowManagerInternal;
|
||||||
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
|
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
|
||||||
@@ -861,8 +863,6 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
|
|||||||
Slog.w(TAG, "Could not get bitmap for id: " + taskId);
|
Slog.w(TAG, "Could not get bitmap for id: " + taskId);
|
||||||
callback.complete(GameScreenshotResult.createInternalErrorResult());
|
callback.complete(GameScreenshotResult.createInternalErrorResult());
|
||||||
} else {
|
} else {
|
||||||
final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(
|
|
||||||
bitmap);
|
|
||||||
final RunningTaskInfo runningTaskInfo =
|
final RunningTaskInfo runningTaskInfo =
|
||||||
mGameTaskInfoProvider.getRunningTaskInfo(taskId);
|
mGameTaskInfoProvider.getRunningTaskInfo(taskId);
|
||||||
if (runningTaskInfo == null) {
|
if (runningTaskInfo == null) {
|
||||||
@@ -877,11 +877,17 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
|
|||||||
callback.complete(GameScreenshotResult.createSuccessResult());
|
callback.complete(GameScreenshotResult.createSuccessResult());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId,
|
ScreenshotRequest request = new ScreenshotRequest.Builder(
|
||||||
mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(),
|
TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
|
||||||
WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
|
.setTopComponent(gameSessionRecord.getComponentName())
|
||||||
BackgroundThread.getHandler(),
|
.setTaskId(taskId)
|
||||||
completionConsumer);
|
.setUserId(mUserHandle.getIdentifier())
|
||||||
|
.setBitmap(bitmap)
|
||||||
|
.setBoundsOnScreen(crop)
|
||||||
|
.setInsets(Insets.NONE)
|
||||||
|
.build();
|
||||||
|
mScreenshotHelper.takeScreenshot(
|
||||||
|
request, BackgroundThread.getHandler(), completionConsumer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -711,7 +711,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
handleRingerChordGesture();
|
handleRingerChordGesture();
|
||||||
break;
|
break;
|
||||||
case MSG_SCREENSHOT_CHORD:
|
case MSG_SCREENSHOT_CHORD:
|
||||||
handleScreenShot(msg.arg1, msg.arg2);
|
handleScreenShot(msg.arg1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1502,9 +1502,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
|| mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
|
|| mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void interceptScreenshotChord(int type, int source, long pressDelay) {
|
private void interceptScreenshotChord(int source, long pressDelay) {
|
||||||
mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
|
mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
|
||||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, type, source),
|
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
|
||||||
pressDelay);
|
pressDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1574,9 +1574,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private void handleScreenShot(@WindowManager.ScreenshotType int type,
|
private void handleScreenShot(@WindowManager.ScreenshotSource int source) {
|
||||||
@WindowManager.ScreenshotSource int source) {
|
mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source);
|
||||||
mDefaultDisplayPolicy.takeScreenshot(type, source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -2173,7 +2172,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
mPowerKeyHandled = true;
|
mPowerKeyHandled = true;
|
||||||
interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN,
|
interceptScreenshotChord(
|
||||||
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
|
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -2867,8 +2866,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_S:
|
case KeyEvent.KEYCODE_S:
|
||||||
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
|
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
|
||||||
interceptScreenshotChord(
|
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
||||||
TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
|
||||||
return key_consumed;
|
return key_consumed;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -3252,8 +3250,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_SYSRQ:
|
case KeyEvent.KEYCODE_SYSRQ:
|
||||||
if (down && repeatCount == 0) {
|
if (down && repeatCount == 0) {
|
||||||
interceptScreenshotChord(
|
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
||||||
TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ import com.android.internal.policy.ScreenDecorationsUtils;
|
|||||||
import com.android.internal.protolog.common.ProtoLog;
|
import com.android.internal.protolog.common.ProtoLog;
|
||||||
import com.android.internal.statusbar.LetterboxDetails;
|
import com.android.internal.statusbar.LetterboxDetails;
|
||||||
import com.android.internal.util.ScreenshotHelper;
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
|
import com.android.internal.util.ScreenshotRequest;
|
||||||
import com.android.internal.util.function.TriConsumer;
|
import com.android.internal.util.function.TriConsumer;
|
||||||
import com.android.internal.view.AppearanceRegion;
|
import com.android.internal.view.AppearanceRegion;
|
||||||
import com.android.internal.widget.PointerLocationView;
|
import com.android.internal.widget.PointerLocationView;
|
||||||
@@ -2666,8 +2667,9 @@ public class DisplayPolicy {
|
|||||||
*/
|
*/
|
||||||
public void takeScreenshot(int screenshotType, int source) {
|
public void takeScreenshot(int screenshotType, int source) {
|
||||||
if (mScreenshotHelper != null) {
|
if (mScreenshotHelper != null) {
|
||||||
mScreenshotHelper.takeScreenshot(screenshotType,
|
ScreenshotRequest request =
|
||||||
source, mHandler, null /* completionConsumer */);
|
new ScreenshotRequest.Builder(screenshotType, source).build();
|
||||||
|
mScreenshotHelper.takeScreenshot(request, mHandler, null /* completionConsumer */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1089,8 +1089,7 @@ public final class GameServiceProviderInstanceImplTest {
|
|||||||
Consumer<Uri> consumer = invocation.getArgument(invocation.getArguments().length - 1);
|
Consumer<Uri> consumer = invocation.getArgument(invocation.getArguments().length - 1);
|
||||||
consumer.accept(Uri.parse("a/b.png"));
|
consumer.accept(Uri.parse("a/b.png"));
|
||||||
return null;
|
return null;
|
||||||
}).when(mMockScreenshotHelper).provideScreenshot(
|
}).when(mMockScreenshotHelper).takeScreenshot(any(), any(), any());
|
||||||
any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any());
|
|
||||||
mGameServiceProviderInstance.start();
|
mGameServiceProviderInstance.start();
|
||||||
startTask(taskId, GAME_A_MAIN_ACTIVITY);
|
startTask(taskId, GAME_A_MAIN_ACTIVITY);
|
||||||
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
|
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
package com.android.server.accessibility;
|
package com.android.server.accessibility;
|
||||||
|
|
||||||
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
|
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
|
||||||
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
@@ -301,9 +300,7 @@ public class SystemActionPerformerTest {
|
|||||||
mSystemActionPerformer.performSystemAction(
|
mSystemActionPerformer.performSystemAction(
|
||||||
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
|
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
|
||||||
verify(mMockScreenshotHelper).takeScreenshot(
|
verify(mMockScreenshotHelper).takeScreenshot(
|
||||||
eq(TAKE_SCREENSHOT_FULLSCREEN),
|
eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
|
||||||
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