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:
Miranda Kephart
2023-01-19 17:39:21 +00:00
committed by Automerger Merge Worker
21 changed files with 795 additions and 451 deletions

View File

@@ -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);
} }
} }

View 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;

View 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());
}
}
}

View File

@@ -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: [

View File

@@ -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.

View File

@@ -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));
}
}

View File

@@ -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
} }

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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

View File

@@ -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()
} }
} }

View File

@@ -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,

View File

@@ -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();

View File

@@ -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
} }
} }

View File

@@ -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))!!
} }

View File

@@ -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;

View File

@@ -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);
} }
}); });
} }

View File

@@ -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;
} }

View File

@@ -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 */);
} }
} }

View File

@@ -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);

View File

@@ -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