Make ScreenshotRequest handle hardware bitmap conversion
Currently, ScreenshotHelper provides utility functions to convert between a hardware bitmap and a parcelable form for sending between processes, but the originating code must handle the conversion itself. This change moves the conversion so that it occurs seamlessly inside of the ScreenshotRequest class, and refactors the inputs to ScreenshotHelper slightly so that the ScreenshotRequest data class is what gets passed in. Bug: 264457397 Test: atest Merged-In: I1041bbcfdc5f5ece6e98bd0844017d4f3dc4e9f3 Change-Id: I1041bbcfdc5f5ece6e98bd0844017d4f3dc4e9f3
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package com.android.internal.util;
|
||||
|
||||
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.Nullable;
|
||||
@@ -11,29 +11,18 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
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.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager.ScreenshotSource;
|
||||
import android.view.WindowManager.ScreenshotType;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ScreenshotHelper {
|
||||
@@ -41,212 +30,6 @@ public class ScreenshotHelper {
|
||||
public static final int SCREENSHOT_MSG_URI = 1;
|
||||
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";
|
||||
|
||||
// Time until we give up on the screenshot & show an error instead.
|
||||
@@ -277,20 +60,35 @@ public class ScreenshotHelper {
|
||||
/**
|
||||
* Request a screenshot be taken.
|
||||
* <p>
|
||||
* Added to support reducing unit test duration; the method variant without a timeout argument
|
||||
* is recommended for general use.
|
||||
* Convenience method for taking a full screenshot with provided source.
|
||||
*
|
||||
* @param type The type of screenshot, defined by {@link ScreenshotType}
|
||||
* @param source The source of the screenshot request, defined by {@link ScreenshotSource}
|
||||
* @param handler used to process messages received from the screenshot service
|
||||
* @param source source of the screenshot request, defined by {@link
|
||||
* ScreenshotSource}
|
||||
* @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
|
||||
* null if no screenshot was saved
|
||||
*/
|
||||
public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
|
||||
@NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
|
||||
ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
|
||||
takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
|
||||
completionConsumer);
|
||||
public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
|
||||
@Nullable Consumer<Uri> completionConsumer) {
|
||||
ScreenshotRequest request =
|
||||
new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
|
||||
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
|
||||
* is recommended for general use.
|
||||
*
|
||||
* @param type The type of screenshot, defined by {@link ScreenshotType}
|
||||
* @param source The source of the screenshot request, defined by {@link ScreenshotSource}
|
||||
* @param handler used to process messages received from the screenshot service
|
||||
* @param timeoutMs time limit for processing, intended only for testing
|
||||
* @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 timeoutMs time limit for processing, intended only for testing
|
||||
* @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
|
||||
public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
|
||||
@NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
|
||||
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) {
|
||||
public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
|
||||
@Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
|
||||
synchronized (mScreenshotLock) {
|
||||
|
||||
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()) {
|
||||
@Override
|
||||
@@ -471,5 +239,4 @@ public class ScreenshotHelper {
|
||||
Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
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: [
|
||||
"src/**/*.java",
|
||||
],
|
||||
|
||||
|
||||
static_libs: [
|
||||
"frameworks-base-testutils",
|
||||
"androidx.test.runner",
|
||||
@@ -21,6 +21,7 @@ android_test {
|
||||
"androidx.test.ext.junit",
|
||||
"mockito-target-minus-junit4",
|
||||
"platform-test-annotations",
|
||||
"testng",
|
||||
],
|
||||
|
||||
libs: [
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.internal.util;
|
||||
|
||||
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.fail;
|
||||
@@ -31,9 +32,11 @@ import static org.mockito.Mockito.mock;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.ColorSpace;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.hardware.HardwareBuffer;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.WindowManager;
|
||||
@@ -79,30 +82,48 @@ public final class ScreenshotHelperTest {
|
||||
|
||||
@Test
|
||||
public void testFullscreenScreenshot() {
|
||||
mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
|
||||
mScreenshotHelper.takeScreenshot(
|
||||
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
|
||||
public void testProvidedImageScreenshot() {
|
||||
mScreenshotHelper.provideScreenshot(
|
||||
new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
|
||||
WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
|
||||
HardwareBuffer buffer = HardwareBuffer.create(
|
||||
10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
|
||||
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
|
||||
public void testScreenshotTimesOut() {
|
||||
long timeoutMs = 10;
|
||||
ScreenshotRequest request = new ScreenshotRequest.Builder(
|
||||
TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
|
||||
.build();
|
||||
|
||||
CountDownLatch lock = new CountDownLatch(1);
|
||||
mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
|
||||
WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
|
||||
mHandler,
|
||||
timeoutMs,
|
||||
mScreenshotHelper.takeScreenshotInternal(request, mHandler,
|
||||
uri -> {
|
||||
assertNull(uri);
|
||||
lock.countDown();
|
||||
});
|
||||
}, timeoutMs);
|
||||
|
||||
try {
|
||||
// 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.UserHandle;
|
||||
import android.view.MotionEvent;
|
||||
import com.android.internal.util.ScreenshotRequest;
|
||||
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
@@ -86,12 +87,6 @@ interface ISystemUiProxy {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -125,5 +120,10 @@ interface ISystemUiProxy {
|
||||
*/
|
||||
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.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
@@ -343,6 +342,7 @@ public class SystemActions implements CoreStartable {
|
||||
|
||||
/**
|
||||
* Register a system action.
|
||||
*
|
||||
* @param actionId the action ID to register.
|
||||
*/
|
||||
public void register(int actionId) {
|
||||
@@ -440,6 +440,7 @@ public class SystemActions implements CoreStartable {
|
||||
|
||||
/**
|
||||
* Unregister a system action.
|
||||
*
|
||||
* @param actionId the action ID to unregister.
|
||||
*/
|
||||
public void unregister(int actionId) {
|
||||
@@ -475,7 +476,8 @@ public class SystemActions implements CoreStartable {
|
||||
}
|
||||
|
||||
private void handleNotifications() {
|
||||
mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::animateExpandNotificationsPanel);
|
||||
mCentralSurfacesOptionalLazy.get().ifPresent(
|
||||
CentralSurfaces::animateExpandNotificationsPanel);
|
||||
}
|
||||
|
||||
private void handleQuickSettings() {
|
||||
@@ -507,7 +509,7 @@ public class SystemActions implements CoreStartable {
|
||||
|
||||
private void handleTakeScreenshot() {
|
||||
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
|
||||
screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
|
||||
screenshotHelper.takeScreenshot(
|
||||
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.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
|
||||
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
|
||||
|
||||
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() {
|
||||
@Override
|
||||
public void run() {
|
||||
mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
|
||||
SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
|
||||
mScreenshotHelper.takeScreenshot(SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
|
||||
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
|
||||
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_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
|
||||
|
||||
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
|
||||
@@ -44,8 +43,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Binder;
|
||||
@@ -77,6 +74,7 @@ import com.android.internal.app.IVoiceInteractionSessionListener;
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.internal.policy.ScreenDecorationsUtils;
|
||||
import com.android.internal.util.ScreenshotHelper;
|
||||
import com.android.internal.util.ScreenshotRequest;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
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.shared.recents.IOverviewProxy;
|
||||
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.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.NotificationShadeWindowController;
|
||||
@@ -322,18 +319,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
|
||||
Insets visibleInsets, Task.TaskKey task) {
|
||||
mScreenshotHelper.provideScreenshot(
|
||||
screenImageBundle,
|
||||
locationInScreen,
|
||||
visibleInsets,
|
||||
task.id,
|
||||
task.userId,
|
||||
task.sourceComponent,
|
||||
SCREENSHOT_OVERVIEW,
|
||||
mHandler,
|
||||
null);
|
||||
public void takeScreenshot(ScreenshotRequest request) {
|
||||
mScreenshotHelper.takeScreenshot(request, mHandler, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,27 +19,26 @@ package com.android.systemui.screenshot
|
||||
import android.graphics.Insets
|
||||
import android.util.Log
|
||||
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
|
||||
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
|
||||
import com.android.internal.util.ScreenshotRequest
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
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.launch
|
||||
import java.util.function.Consumer
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Processes a screenshot request sent from {@link ScreenshotHelper}.
|
||||
*/
|
||||
@SysUISingleton
|
||||
class RequestProcessor @Inject constructor(
|
||||
private val capture: ImageCapture,
|
||||
private val policy: ScreenshotPolicy,
|
||||
private val flags: FeatureFlags,
|
||||
/** For the Java Async version, to invoke the callback. */
|
||||
@Application private val mainScope: CoroutineScope
|
||||
private val capture: ImageCapture,
|
||||
private val policy: ScreenshotPolicy,
|
||||
private val flags: FeatureFlags,
|
||||
/** For the Java Async version, to invoke the callback. */
|
||||
@Application private val mainScope: CoroutineScope
|
||||
) {
|
||||
/**
|
||||
* 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.
|
||||
|
||||
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())
|
||||
@@ -66,17 +65,21 @@ class RequestProcessor @Inject constructor(
|
||||
|
||||
result = if (policy.isManagedProfile(info.user.identifier)) {
|
||||
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
|
||||
ScreenshotRequest(
|
||||
TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
|
||||
HardwareBitmapBundler.hardwareBitmapToBundle(image),
|
||||
info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
|
||||
)
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
|
||||
.setTopComponent(info.component)
|
||||
.setTaskId(info.taskId)
|
||||
.setUserId(info.user.identifier)
|
||||
.setBitmap(image)
|
||||
.setBoundsOnScreen(info.bounds)
|
||||
.setInsets(Insets.NONE)
|
||||
.build()
|
||||
} else {
|
||||
// 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;
|
||||
if (!aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
|
||||
if (screenshotScreenBounds == null
|
||||
|| !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
|
||||
showFlash = true;
|
||||
visibleInsets = Insets.NONE;
|
||||
screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
|
||||
screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
|
||||
}
|
||||
mCurrentRequestCallback = requestCallback;
|
||||
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
|
||||
|
||||
@@ -54,7 +54,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
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.dagger.qualifiers.Background;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
@@ -186,8 +186,7 @@ public class TakeScreenshotService extends Service {
|
||||
final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
|
||||
RequestCallback callback = new RequestCallbackImpl(replyTo);
|
||||
|
||||
ScreenshotHelper.ScreenshotRequest request =
|
||||
(ScreenshotHelper.ScreenshotRequest) msg.obj;
|
||||
ScreenshotRequest request = (ScreenshotRequest) msg.obj;
|
||||
|
||||
handleRequest(request, onSaved, callback);
|
||||
return true;
|
||||
@@ -195,7 +194,7 @@ public class TakeScreenshotService extends Service {
|
||||
|
||||
@MainThread
|
||||
@VisibleForTesting
|
||||
void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
|
||||
void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
|
||||
RequestCallback callback) {
|
||||
// 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
|
||||
@@ -226,7 +225,7 @@ public class TakeScreenshotService extends Service {
|
||||
(r) -> dispatchToController(r, onSaved, callback));
|
||||
}
|
||||
|
||||
private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
|
||||
private void dispatchToController(ScreenshotRequest request,
|
||||
Consumer<Uri> uriConsumer, RequestCallback callback) {
|
||||
|
||||
ComponentName topComponent = request.getTopComponent();
|
||||
@@ -244,8 +243,7 @@ public class TakeScreenshotService extends Service {
|
||||
if (DEBUG_SERVICE) {
|
||||
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
|
||||
}
|
||||
Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
|
||||
request.getBitmapBundle());
|
||||
Bitmap screenshot = request.getBitmap();
|
||||
Rect screenBounds = request.getBoundsInScreen();
|
||||
Insets insets = request.getInsets();
|
||||
int taskId = request.getTaskId();
|
||||
|
||||
@@ -22,15 +22,12 @@ import android.graphics.ColorSpace
|
||||
import android.graphics.Insets
|
||||
import android.graphics.Rect
|
||||
import android.hardware.HardwareBuffer
|
||||
import android.os.Bundle
|
||||
import android.os.UserHandle
|
||||
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
|
||||
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
|
||||
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
|
||||
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
|
||||
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
|
||||
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
|
||||
import com.android.internal.util.ScreenshotRequest
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
|
||||
@@ -49,7 +46,6 @@ class RequestProcessorTest {
|
||||
private val bounds = Rect(25, 25, 75, 75)
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Unconfined)
|
||||
private val dispatcher = Dispatchers.Unconfined
|
||||
private val policy = FakeScreenshotPolicy()
|
||||
private val flags = FakeFeatureFlags()
|
||||
|
||||
@@ -58,7 +54,8 @@ class RequestProcessorTest {
|
||||
fun testProcessAsync() {
|
||||
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)
|
||||
|
||||
var result: ScreenshotRequest? = null
|
||||
@@ -80,7 +77,8 @@ class RequestProcessorTest {
|
||||
fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
|
||||
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 processedRequest = processor.process(request)
|
||||
@@ -97,9 +95,11 @@ class RequestProcessorTest {
|
||||
policy.setManagedProfile(USER_ID, false)
|
||||
policy.setDisplayContentInfo(
|
||||
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 processedRequest = processor.process(request)
|
||||
@@ -120,17 +120,20 @@ class RequestProcessorTest {
|
||||
|
||||
// Indicate that the primary content belongs to a manged profile
|
||||
policy.setManagedProfile(USER_ID, true)
|
||||
policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
|
||||
DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
|
||||
policy.setDisplayContentInfo(
|
||||
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 processedRequest = processor.process(request)
|
||||
|
||||
// Expect a task snapshot is taken, overriding the full screen mode
|
||||
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.insets).isEqualTo(Insets.NONE)
|
||||
assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
|
||||
@@ -147,10 +150,16 @@ class RequestProcessorTest {
|
||||
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
||||
|
||||
val bitmap = makeHardwareBitmap(100, 100)
|
||||
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
||||
|
||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
|
||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
|
||||
val request =
|
||||
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)
|
||||
|
||||
@@ -168,10 +177,16 @@ class RequestProcessorTest {
|
||||
policy.setManagedProfile(USER_ID, false)
|
||||
|
||||
val bitmap = makeHardwareBitmap(100, 100)
|
||||
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
||||
|
||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
|
||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
|
||||
val request =
|
||||
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)
|
||||
|
||||
@@ -190,10 +205,16 @@ class RequestProcessorTest {
|
||||
policy.setManagedProfile(USER_ID, true)
|
||||
|
||||
val bitmap = makeHardwareBitmap(100, 100)
|
||||
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
||||
|
||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
|
||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
|
||||
val request =
|
||||
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)
|
||||
|
||||
@@ -202,14 +223,18 @@ class RequestProcessorTest {
|
||||
}
|
||||
|
||||
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
|
||||
val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
|
||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
|
||||
val buffer =
|
||||
HardwareBuffer.create(
|
||||
width,
|
||||
height,
|
||||
HardwareBuffer.RGBA_8888,
|
||||
1,
|
||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
|
||||
)
|
||||
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
|
||||
}
|
||||
|
||||
private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
|
||||
val provided = bundleToHardwareBitmap(bundle)
|
||||
return provided.hardwareBuffer == this.hardwareBuffer &&
|
||||
provided.colorSpace == this.colorSpace
|
||||
private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
|
||||
return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
|
||||
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.testing.UiEventLoggerFake
|
||||
import com.android.internal.util.ScreenshotHelper
|
||||
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
|
||||
import com.android.internal.util.ScreenshotRequest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
|
||||
@@ -80,24 +79,39 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
private val flags = FakeFeatureFlags()
|
||||
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
|
||||
|
||||
private val service = TakeScreenshotService(
|
||||
controller, userManager, devicePolicyManager, eventLogger,
|
||||
notificationsController, mContext, Runnable::run, flags, requestProcessor)
|
||||
private val service =
|
||||
TakeScreenshotService(
|
||||
controller,
|
||||
userManager,
|
||||
devicePolicyManager,
|
||||
eventLogger,
|
||||
notificationsController,
|
||||
mContext,
|
||||
Runnable::run,
|
||||
flags,
|
||||
requestProcessor
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
|
||||
whenever(devicePolicyManager.getScreenCaptureDisabled(
|
||||
/* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
|
||||
whenever(
|
||||
devicePolicyManager.getScreenCaptureDisabled(
|
||||
/* admin component (null: any admin) */ isNull(),
|
||||
eq(UserHandle.USER_ALL)
|
||||
)
|
||||
)
|
||||
.thenReturn(false)
|
||||
whenever(userManager.isUserUnlocked).thenReturn(true)
|
||||
|
||||
// Stub request processor as a synchronous no-op for tests with the flag enabled
|
||||
doAnswer {
|
||||
val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
|
||||
val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
|
||||
consumer.accept(request)
|
||||
}.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
|
||||
val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
|
||||
val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
|
||||
consumer.accept(request)
|
||||
}
|
||||
.`when`(requestProcessor)
|
||||
.processAsync(/* request= */ any(), /* callback= */ any())
|
||||
|
||||
// Flipped in selected test cases
|
||||
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
|
||||
@@ -108,7 +122,8 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
/* className = */ null,
|
||||
/* token = */ null,
|
||||
application,
|
||||
/* activityManager = */ null)
|
||||
/* activityManager = */ null
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -125,63 +140,89 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun takeScreenshotFullscreen() {
|
||||
val request = ScreenshotRequest(
|
||||
TAKE_SCREENSHOT_FULLSCREEN,
|
||||
SCREENSHOT_KEY_CHORD,
|
||||
topComponent)
|
||||
val request =
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||
.setTopComponent(topComponent)
|
||||
.build()
|
||||
|
||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
||||
service.handleRequest(request, { /* onSaved */}, callback)
|
||||
|
||||
verify(controller, times(1)).takeScreenshotFullscreen(
|
||||
eq(topComponent),
|
||||
/* onSavedListener = */ any(),
|
||||
/* requestCallback = */ any())
|
||||
verify(controller, times(1))
|
||||
.takeScreenshotFullscreen(
|
||||
eq(topComponent),
|
||||
/* onSavedListener = */ any(),
|
||||
/* requestCallback = */ any()
|
||||
)
|
||||
|
||||
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
|
||||
val logEvent = eventLogger.get(0)
|
||||
|
||||
assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
|
||||
logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
|
||||
assertEquals("Expected supplied package name",
|
||||
topComponent.packageName, eventLogger.get(0).packageName)
|
||||
assertEquals(
|
||||
"Expected SCREENSHOT_REQUESTED UiEvent",
|
||||
logEvent.eventId,
|
||||
SCREENSHOT_REQUESTED_KEY_CHORD.id
|
||||
)
|
||||
assertEquals(
|
||||
"Expected supplied package name",
|
||||
topComponent.packageName,
|
||||
eventLogger.get(0).packageName
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun takeScreenshotProvidedImage() {
|
||||
val bounds = Rect(50, 50, 150, 150)
|
||||
val bitmap = makeHardwareBitmap(100, 100)
|
||||
val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
|
||||
|
||||
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
|
||||
bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
|
||||
val request =
|
||||
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(
|
||||
argThat { b -> b.equalsHardwareBitmap(bitmap) },
|
||||
eq(bounds),
|
||||
eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
|
||||
/* onSavedListener = */ any(), /* requestCallback = */ any())
|
||||
verify(controller, times(1))
|
||||
.handleImageAsScreenshot(
|
||||
argThat { b -> b.equalsHardwareBitmap(bitmap) },
|
||||
eq(bounds),
|
||||
eq(Insets.NONE),
|
||||
eq(TASK_ID),
|
||||
eq(USER_ID),
|
||||
eq(topComponent),
|
||||
/* onSavedListener = */ any(),
|
||||
/* requestCallback = */ any()
|
||||
)
|
||||
|
||||
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
|
||||
val logEvent = eventLogger.get(0)
|
||||
|
||||
assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
|
||||
logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
|
||||
assertEquals("Expected supplied package name",
|
||||
topComponent.packageName, eventLogger.get(0).packageName)
|
||||
assertEquals(
|
||||
"Expected SCREENSHOT_REQUESTED_* UiEvent",
|
||||
logEvent.eventId,
|
||||
SCREENSHOT_REQUESTED_OVERVIEW.id
|
||||
)
|
||||
assertEquals(
|
||||
"Expected supplied package name",
|
||||
topComponent.packageName,
|
||||
eventLogger.get(0).packageName
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun takeScreenshotFullscreen_userLocked() {
|
||||
whenever(userManager.isUserUnlocked).thenReturn(false)
|
||||
|
||||
val request = ScreenshotRequest(
|
||||
TAKE_SCREENSHOT_FULLSCREEN,
|
||||
SCREENSHOT_KEY_CHORD,
|
||||
topComponent)
|
||||
val request =
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||
.setTopComponent(topComponent)
|
||||
.build()
|
||||
|
||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
||||
service.handleRequest(request, { /* onSaved */}, callback)
|
||||
|
||||
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
|
||||
verify(callback, times(1)).reportError()
|
||||
@@ -190,21 +231,24 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
|
||||
whenever(devicePolicyManager.getScreenCaptureDisabled(
|
||||
isNull(), eq(UserHandle.USER_ALL))
|
||||
).thenReturn(true)
|
||||
whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
|
||||
.thenReturn(true)
|
||||
|
||||
whenever(devicePolicyResourcesManager.getString(
|
||||
eq(SCREENSHOT_BLOCKED_BY_ADMIN),
|
||||
/* Supplier<String> */ any(),
|
||||
)).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
|
||||
whenever(
|
||||
devicePolicyResourcesManager.getString(
|
||||
eq(SCREENSHOT_BLOCKED_BY_ADMIN),
|
||||
/* Supplier<String> */
|
||||
any(),
|
||||
)
|
||||
)
|
||||
.thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
|
||||
|
||||
val request = ScreenshotRequest(
|
||||
TAKE_SCREENSHOT_FULLSCREEN,
|
||||
SCREENSHOT_KEY_CHORD,
|
||||
topComponent)
|
||||
val request =
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||
.setTopComponent(topComponent)
|
||||
.build()
|
||||
|
||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
||||
service.handleRequest(request, { /* onSaved */}, callback)
|
||||
|
||||
// error shown: Toast.makeText(...).show(), untestable
|
||||
verify(callback, times(1)).reportError()
|
||||
@@ -214,14 +258,20 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
|
||||
private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
|
||||
return config == HARDWARE &&
|
||||
other.config == HARDWARE &&
|
||||
hardwareBuffer == other.hardwareBuffer &&
|
||||
colorSpace == other.colorSpace
|
||||
other.config == HARDWARE &&
|
||||
hardwareBuffer == other.hardwareBuffer &&
|
||||
colorSpace == other.colorSpace
|
||||
}
|
||||
|
||||
/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
|
||||
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
|
||||
val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
|
||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
|
||||
val buffer =
|
||||
HardwareBuffer.create(
|
||||
width,
|
||||
height,
|
||||
HardwareBuffer.RGBA_8888,
|
||||
1,
|
||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
|
||||
)
|
||||
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ public class SystemActionPerformer {
|
||||
private boolean takeScreenshot() {
|
||||
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
|
||||
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
|
||||
screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
|
||||
screenshotHelper.takeScreenshot(
|
||||
WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
|
||||
new Handler(Looper.getMainLooper()), null);
|
||||
return true;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
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.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -33,7 +36,6 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.service.games.CreateGameSessionRequest;
|
||||
@@ -50,7 +52,6 @@ import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceControlViewHost.SurfacePackage;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
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.os.BackgroundThread;
|
||||
import com.android.internal.util.ScreenshotHelper;
|
||||
import com.android.internal.util.ScreenshotRequest;
|
||||
import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
import com.android.server.wm.WindowManagerInternal;
|
||||
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);
|
||||
callback.complete(GameScreenshotResult.createInternalErrorResult());
|
||||
} else {
|
||||
final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(
|
||||
bitmap);
|
||||
final RunningTaskInfo runningTaskInfo =
|
||||
mGameTaskInfoProvider.getRunningTaskInfo(taskId);
|
||||
if (runningTaskInfo == null) {
|
||||
@@ -877,11 +877,17 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
|
||||
callback.complete(GameScreenshotResult.createSuccessResult());
|
||||
}
|
||||
};
|
||||
mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId,
|
||||
mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(),
|
||||
WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
|
||||
BackgroundThread.getHandler(),
|
||||
completionConsumer);
|
||||
ScreenshotRequest request = new ScreenshotRequest.Builder(
|
||||
TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
|
||||
.setTopComponent(gameSessionRecord.getComponentName())
|
||||
.setTaskId(taskId)
|
||||
.setUserId(mUserHandle.getIdentifier())
|
||||
.setBitmap(bitmap)
|
||||
.setBoundsOnScreen(crop)
|
||||
.setInsets(Insets.NONE)
|
||||
.build();
|
||||
mScreenshotHelper.takeScreenshot(
|
||||
request, BackgroundThread.getHandler(), completionConsumer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
handleRingerChordGesture();
|
||||
break;
|
||||
case MSG_SCREENSHOT_CHORD:
|
||||
handleScreenShot(msg.arg1, msg.arg2);
|
||||
handleScreenShot(msg.arg1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1500,9 +1500,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
|| 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.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, type, source),
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
|
||||
pressDelay);
|
||||
}
|
||||
|
||||
@@ -1572,9 +1572,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
}
|
||||
};
|
||||
|
||||
private void handleScreenShot(@WindowManager.ScreenshotType int type,
|
||||
@WindowManager.ScreenshotSource int source) {
|
||||
mDefaultDisplayPolicy.takeScreenshot(type, source);
|
||||
private void handleScreenShot(@WindowManager.ScreenshotSource int source) {
|
||||
mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2170,7 +2169,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
@Override
|
||||
void execute() {
|
||||
mPowerKeyHandled = true;
|
||||
interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN,
|
||||
interceptScreenshotChord(
|
||||
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
|
||||
}
|
||||
@Override
|
||||
@@ -2864,8 +2863,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
break;
|
||||
case KeyEvent.KEYCODE_S:
|
||||
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
|
||||
interceptScreenshotChord(
|
||||
TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
||||
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
||||
return key_consumed;
|
||||
}
|
||||
break;
|
||||
@@ -3239,8 +3237,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
||||
break;
|
||||
case KeyEvent.KEYCODE_SYSRQ:
|
||||
if (down && repeatCount == 0) {
|
||||
interceptScreenshotChord(
|
||||
TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
||||
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ import com.android.internal.policy.ScreenDecorationsUtils;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.internal.statusbar.LetterboxDetails;
|
||||
import com.android.internal.util.ScreenshotHelper;
|
||||
import com.android.internal.util.ScreenshotRequest;
|
||||
import com.android.internal.util.function.TriConsumer;
|
||||
import com.android.internal.view.AppearanceRegion;
|
||||
import com.android.internal.widget.PointerLocationView;
|
||||
@@ -2666,8 +2667,9 @@ public class DisplayPolicy {
|
||||
*/
|
||||
public void takeScreenshot(int screenshotType, int source) {
|
||||
if (mScreenshotHelper != null) {
|
||||
mScreenshotHelper.takeScreenshot(screenshotType,
|
||||
source, mHandler, null /* completionConsumer */);
|
||||
ScreenshotRequest request =
|
||||
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.accept(Uri.parse("a/b.png"));
|
||||
return null;
|
||||
}).when(mMockScreenshotHelper).provideScreenshot(
|
||||
any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any());
|
||||
}).when(mMockScreenshotHelper).takeScreenshot(any(), any(), any());
|
||||
mGameServiceProviderInstance.start();
|
||||
startTask(taskId, GAME_A_MAIN_ACTIVITY);
|
||||
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.server.accessibility;
|
||||
|
||||
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.is;
|
||||
@@ -301,9 +300,7 @@ public class SystemActionPerformerTest {
|
||||
mSystemActionPerformer.performSystemAction(
|
||||
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user