Merge "Make ScreenshotRequest handle hardware bitmap conversion" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a5e45d3124
@@ -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