Merge "Provide game task bitmap to TakeScreenshotService as screenshot" into tm-dev am: 81aa0eac27
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16938819 Change-Id: I3efaffc791c8fc2c9ea52c1901e7133a9aa32b3e
This commit is contained in:
@@ -11405,7 +11405,7 @@ package android.service.games {
|
|||||||
|
|
||||||
public static interface GameSession.ScreenshotCallback {
|
public static interface GameSession.ScreenshotCallback {
|
||||||
method public void onFailure(int);
|
method public void onFailure(int);
|
||||||
method public void onSuccess(@NonNull android.graphics.Bitmap);
|
method public void onSuccess();
|
||||||
field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0
|
field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ package android.service.games;
|
|||||||
|
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
@@ -30,9 +28,7 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* Result object for calls to {@link IGameSessionController#takeScreenshot}.
|
* Result object for calls to {@link IGameSessionController#takeScreenshot}.
|
||||||
*
|
*
|
||||||
* It includes a status (see {@link #getStatus}) and, if the status is
|
* It includes a status only (see {@link #getStatus}).
|
||||||
* {@link #GAME_SCREENSHOT_SUCCESS} an {@link android.graphics.Bitmap} result (see {@link
|
|
||||||
* #getBitmap}).
|
|
||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@@ -54,8 +50,7 @@ public final class GameScreenshotResult implements Parcelable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} was
|
* Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} was
|
||||||
* successful and an {@link android.graphics.Bitmap} result should be available by calling
|
* successful.
|
||||||
* {@link #getBitmap}.
|
|
||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@@ -81,9 +76,7 @@ public final class GameScreenshotResult implements Parcelable {
|
|||||||
new Parcelable.Creator<GameScreenshotResult>() {
|
new Parcelable.Creator<GameScreenshotResult>() {
|
||||||
@Override
|
@Override
|
||||||
public GameScreenshotResult createFromParcel(Parcel source) {
|
public GameScreenshotResult createFromParcel(Parcel source) {
|
||||||
return new GameScreenshotResult(
|
return new GameScreenshotResult(source.readInt());
|
||||||
source.readInt(),
|
|
||||||
source.readParcelable(null, Bitmap.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,14 +88,11 @@ public final class GameScreenshotResult implements Parcelable {
|
|||||||
@GameScreenshotStatus
|
@GameScreenshotStatus
|
||||||
private final int mStatus;
|
private final int mStatus;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final Bitmap mBitmap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a successful {@link GameScreenshotResult} with the provided bitmap.
|
* Creates a successful {@link GameScreenshotResult}.
|
||||||
*/
|
*/
|
||||||
public static GameScreenshotResult createSuccessResult(@NonNull Bitmap bitmap) {
|
public static GameScreenshotResult createSuccessResult() {
|
||||||
return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS, bitmap);
|
return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,12 +100,11 @@ public final class GameScreenshotResult implements Parcelable {
|
|||||||
* {@link #GAME_SCREENSHOT_ERROR_INTERNAL_ERROR} status.
|
* {@link #GAME_SCREENSHOT_ERROR_INTERNAL_ERROR} status.
|
||||||
*/
|
*/
|
||||||
public static GameScreenshotResult createInternalErrorResult() {
|
public static GameScreenshotResult createInternalErrorResult() {
|
||||||
return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, null);
|
return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GameScreenshotResult(@GameScreenshotStatus int status, @Nullable Bitmap bitmap) {
|
private GameScreenshotResult(@GameScreenshotStatus int status) {
|
||||||
this.mStatus = status;
|
this.mStatus = status;
|
||||||
this.mBitmap = bitmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -126,7 +115,6 @@ public final class GameScreenshotResult implements Parcelable {
|
|||||||
@Override
|
@Override
|
||||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
dest.writeInt(mStatus);
|
dest.writeInt(mStatus);
|
||||||
dest.writeParcelable(mBitmap, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GameScreenshotStatus
|
@GameScreenshotStatus
|
||||||
@@ -134,29 +122,12 @@ public final class GameScreenshotResult implements Parcelable {
|
|||||||
return mStatus;
|
return mStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the {@link Bitmap} result from a successful screenshot attempt.
|
|
||||||
*
|
|
||||||
* @return The bitmap.
|
|
||||||
* @throws IllegalStateException if this method is called when {@link #getStatus} does not
|
|
||||||
* return {@link #GAME_SCREENSHOT_SUCCESS}.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public Bitmap getBitmap() {
|
|
||||||
if (mBitmap == null) {
|
|
||||||
throw new IllegalStateException("Bitmap not available for failed screenshot result");
|
|
||||||
}
|
|
||||||
return mBitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "GameScreenshotResult{"
|
return "GameScreenshotResult{"
|
||||||
+ "mStatus="
|
+ "mStatus="
|
||||||
+ mStatus
|
+ mStatus
|
||||||
+ ", has bitmap='"
|
+ "}";
|
||||||
+ mBitmap != null ? "yes" : "no"
|
|
||||||
+ "\'}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -170,12 +141,11 @@ public final class GameScreenshotResult implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GameScreenshotResult that = (GameScreenshotResult) o;
|
GameScreenshotResult that = (GameScreenshotResult) o;
|
||||||
return mStatus == that.mStatus
|
return mStatus == that.mStatus;
|
||||||
&& Objects.equals(mBitmap, that.mBitmap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(mStatus, mBitmap);
|
return Objects.hash(mStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -367,7 +366,7 @@ public abstract class GameSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for returning screenshot outcome from calls to {@link #takeScreenshot}.
|
* Interface for handling result of {@link #takeScreenshot}.
|
||||||
*/
|
*/
|
||||||
public interface ScreenshotCallback {
|
public interface ScreenshotCallback {
|
||||||
|
|
||||||
@@ -402,18 +401,16 @@ public abstract class GameSession {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when taking the screenshot succeeded.
|
* Called when taking the screenshot succeeded.
|
||||||
*
|
|
||||||
* @param bitmap The screenshot.
|
|
||||||
*/
|
*/
|
||||||
void onSuccess(@NonNull Bitmap bitmap);
|
void onSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a screenshot of the associated game. For this call to succeed, the device screen
|
* Takes a screenshot of the associated game. For this call to succeed, the device screen
|
||||||
* must be turned on and the game task must be visible.
|
* must be turned on and the game task must be visible.
|
||||||
*
|
*
|
||||||
* If the callback is called with {@link ScreenshotCallback#onSuccess}, the provided {@link
|
* If the callback is called with {@link ScreenshotCallback#onSuccess}, the screenshot is
|
||||||
* Bitmap} may be used.
|
* taken successfully.
|
||||||
*
|
*
|
||||||
* If the callback is called with {@link ScreenshotCallback#onFailure}, the provided status
|
* If the callback is called with {@link ScreenshotCallback#onFailure}, the provided status
|
||||||
* code should be checked.
|
* code should be checked.
|
||||||
@@ -460,7 +457,7 @@ public abstract class GameSession {
|
|||||||
@GameScreenshotResult.GameScreenshotStatus int status = result.getStatus();
|
@GameScreenshotResult.GameScreenshotStatus int status = result.getStatus();
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case GameScreenshotResult.GAME_SCREENSHOT_SUCCESS:
|
case GameScreenshotResult.GAME_SCREENSHOT_SUCCESS:
|
||||||
callback.onSuccess(result.getBitmap());
|
callback.onSuccess();
|
||||||
break;
|
break;
|
||||||
case GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR:
|
case GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR:
|
||||||
Slog.w(TAG, "Error taking screenshot");
|
Slog.w(TAG, "Error taking screenshot");
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.ColorSpace;
|
||||||
import android.graphics.Insets;
|
import android.graphics.Insets;
|
||||||
|
import android.graphics.ParcelableColorSpace;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.hardware.HardwareBuffer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -26,6 +30,7 @@ import android.os.UserHandle;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class ScreenshotHelper {
|
public class ScreenshotHelper {
|
||||||
@@ -154,6 +159,72 @@ public class ScreenshotHelper {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE);
|
||||||
|
|
||||||
|
return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
|
||||||
|
colorSpace.getColorSpace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final String TAG = "ScreenshotHelper";
|
private static final String TAG = "ScreenshotHelper";
|
||||||
|
|
||||||
// Time until we give up on the screenshot & show an error instead.
|
// Time until we give up on the screenshot & show an error instead.
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.systemui.shared.recents.utilities;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.ColorSpace;
|
|
||||||
import android.graphics.ParcelableColorSpace;
|
|
||||||
import android.hardware.HardwareBuffer;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utils for working with Bitmaps.
|
|
||||||
*/
|
|
||||||
public final class BitmapUtil {
|
|
||||||
private static final String KEY_BUFFER = "bitmap_util_buffer";
|
|
||||||
private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
|
|
||||||
|
|
||||||
private BitmapUtil(){ }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE);
|
|
||||||
|
|
||||||
return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
|
|
||||||
colorSpace.getColorSpace());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@ import androidx.annotation.NonNull;
|
|||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.internal.util.ScreenshotHelper;
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.shared.recents.utilities.BitmapUtil;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -208,7 +207,7 @@ public class TakeScreenshotService extends Service {
|
|||||||
if (DEBUG_SERVICE) {
|
if (DEBUG_SERVICE) {
|
||||||
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
|
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
|
||||||
}
|
}
|
||||||
Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(
|
Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
|
||||||
screenshotRequest.getBitmapBundle());
|
screenshotRequest.getBitmapBundle());
|
||||||
Rect screenBounds = screenshotRequest.getBoundsInScreen();
|
Rect screenBounds = screenshotRequest.getBoundsInScreen();
|
||||||
Insets insets = screenshotRequest.getInsets();
|
Insets insets = screenshotRequest.getInsets();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.service.games.IGameSessionService;
|
|||||||
|
|
||||||
import com.android.internal.infra.ServiceConnector;
|
import com.android.internal.infra.ServiceConnector;
|
||||||
import com.android.internal.os.BackgroundThread;
|
import com.android.internal.os.BackgroundThread;
|
||||||
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
import com.android.server.LocalServices;
|
import com.android.server.LocalServices;
|
||||||
import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
|
import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
|
||||||
import com.android.server.wm.WindowManagerInternal;
|
import com.android.server.wm.WindowManagerInternal;
|
||||||
@@ -55,7 +56,8 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide
|
|||||||
(WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
|
(WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
|
||||||
LocalServices.getService(WindowManagerInternal.class),
|
LocalServices.getService(WindowManagerInternal.class),
|
||||||
new GameServiceConnector(mContext, configuration),
|
new GameServiceConnector(mContext, configuration),
|
||||||
new GameSessionServiceConnector(mContext, configuration));
|
new GameSessionServiceConnector(mContext, configuration),
|
||||||
|
new ScreenshotHelper(mContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class GameServiceConnector extends ServiceConnector.Impl<IGameService> {
|
private static final class GameServiceConnector extends ServiceConnector.Impl<IGameService> {
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Insets;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.service.games.CreateGameSessionRequest;
|
import android.service.games.CreateGameSessionRequest;
|
||||||
@@ -45,12 +48,15 @@ import android.service.games.IGameSessionService;
|
|||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.SurfaceControlViewHost.SurfacePackage;
|
import android.view.SurfaceControlViewHost.SurfacePackage;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.infra.AndroidFuture;
|
import com.android.internal.infra.AndroidFuture;
|
||||||
import com.android.internal.infra.ServiceConnector;
|
import com.android.internal.infra.ServiceConnector;
|
||||||
import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
|
import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
|
||||||
|
import com.android.internal.os.BackgroundThread;
|
||||||
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
import com.android.server.wm.WindowManagerInternal;
|
import com.android.server.wm.WindowManagerInternal;
|
||||||
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
|
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
|
||||||
import com.android.server.wm.WindowManagerService;
|
import com.android.server.wm.WindowManagerService;
|
||||||
@@ -59,6 +65,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
final class GameServiceProviderInstanceImpl implements GameServiceProviderInstance {
|
final class GameServiceProviderInstanceImpl implements GameServiceProviderInstance {
|
||||||
private static final String TAG = "GameServiceProviderInstance";
|
private static final String TAG = "GameServiceProviderInstance";
|
||||||
@@ -188,6 +195,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
|
|||||||
private final IActivityTaskManager mActivityTaskManager;
|
private final IActivityTaskManager mActivityTaskManager;
|
||||||
private final WindowManagerService mWindowManagerService;
|
private final WindowManagerService mWindowManagerService;
|
||||||
private final WindowManagerInternal mWindowManagerInternal;
|
private final WindowManagerInternal mWindowManagerInternal;
|
||||||
|
private final ScreenshotHelper mScreenshotHelper;
|
||||||
private final ServiceConnector<IGameService> mGameServiceConnector;
|
private final ServiceConnector<IGameService> mGameServiceConnector;
|
||||||
private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector;
|
private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector;
|
||||||
|
|
||||||
@@ -207,7 +215,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
|
|||||||
@NonNull WindowManagerService windowManagerService,
|
@NonNull WindowManagerService windowManagerService,
|
||||||
@NonNull WindowManagerInternal windowManagerInternal,
|
@NonNull WindowManagerInternal windowManagerInternal,
|
||||||
@NonNull ServiceConnector<IGameService> gameServiceConnector,
|
@NonNull ServiceConnector<IGameService> gameServiceConnector,
|
||||||
@NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) {
|
@NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector,
|
||||||
|
@NonNull ScreenshotHelper screenshotHelper) {
|
||||||
mUserHandle = userHandle;
|
mUserHandle = userHandle;
|
||||||
mBackgroundExecutor = backgroundExecutor;
|
mBackgroundExecutor = backgroundExecutor;
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -218,6 +227,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
|
|||||||
mWindowManagerInternal = windowManagerInternal;
|
mWindowManagerInternal = windowManagerInternal;
|
||||||
mGameServiceConnector = gameServiceConnector;
|
mGameServiceConnector = gameServiceConnector;
|
||||||
mGameSessionServiceConnector = gameSessionServiceConnector;
|
mGameSessionServiceConnector = gameSessionServiceConnector;
|
||||||
|
mScreenshotHelper = screenshotHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -651,7 +661,26 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
|
|||||||
Slog.w(TAG, "Could not get bitmap for id: " + taskId);
|
Slog.w(TAG, "Could not get bitmap for id: " + taskId);
|
||||||
callback.complete(GameScreenshotResult.createInternalErrorResult());
|
callback.complete(GameScreenshotResult.createInternalErrorResult());
|
||||||
} else {
|
} else {
|
||||||
callback.complete(GameScreenshotResult.createSuccessResult(bitmap));
|
final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(
|
||||||
|
bitmap);
|
||||||
|
final RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId);
|
||||||
|
if (runningTaskInfo == null) {
|
||||||
|
Slog.w(TAG, "Could not get running task info for id: " + taskId);
|
||||||
|
callback.complete(GameScreenshotResult.createInternalErrorResult());
|
||||||
|
}
|
||||||
|
final Rect crop = runningTaskInfo.configuration.windowConfiguration.getBounds();
|
||||||
|
final Consumer<Uri> completionConsumer = (uri) -> {
|
||||||
|
if (uri == null) {
|
||||||
|
callback.complete(GameScreenshotResult.createInternalErrorResult());
|
||||||
|
} else {
|
||||||
|
callback.complete(GameScreenshotResult.createSuccessResult());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId,
|
||||||
|
mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(),
|
||||||
|
WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
|
||||||
|
BackgroundThread.getHandler(),
|
||||||
|
completionConsumer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import static org.junit.Assert.fail;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
import android.service.games.GameSession.ScreenshotCallback;
|
import android.service.games.GameSession.ScreenshotCallback;
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
@@ -61,7 +60,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||||
public final class GameSessionTest {
|
public final class GameSessionTest {
|
||||||
private static final long WAIT_FOR_CALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
|
private static final long WAIT_FOR_CALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
|
||||||
private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private IGameSessionController mMockGameSessionController;
|
private IGameSessionController mMockGameSessionController;
|
||||||
@@ -101,7 +99,7 @@ public final class GameSessionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Bitmap bitmap) {
|
public void onSuccess() {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -131,7 +129,7 @@ public final class GameSessionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Bitmap bitmap) {
|
public void onSuccess() {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -160,7 +158,7 @@ public final class GameSessionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Bitmap bitmap) {
|
public void onSuccess() {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -170,10 +168,10 @@ public final class GameSessionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void takeScreenshot_gameManagerSuccess_returnsBitmap() throws Exception {
|
public void takeScreenshot_gameManagerSuccess() throws Exception {
|
||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
AndroidFuture result = invocation.getArgument(1);
|
AndroidFuture result = invocation.getArgument(1);
|
||||||
result.complete(GameScreenshotResult.createSuccessResult(TEST_BITMAP));
|
result.complete(GameScreenshotResult.createSuccessResult());
|
||||||
return null;
|
return null;
|
||||||
}).when(mMockGameSessionController).takeScreenshot(anyInt(), any());
|
}).when(mMockGameSessionController).takeScreenshot(anyInt(), any());
|
||||||
|
|
||||||
@@ -187,8 +185,7 @@ public final class GameSessionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Bitmap bitmap) {
|
public void onSuccess() {
|
||||||
assertEquals(TEST_BITMAP, bitmap);
|
|
||||||
countDownLatch.countDown();
|
countDownLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,7 +46,12 @@ import android.content.ContextWrapper;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Picture;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
@@ -71,6 +76,7 @@ import com.android.internal.infra.AndroidFuture;
|
|||||||
import com.android.internal.util.ConcurrentUtils;
|
import com.android.internal.util.ConcurrentUtils;
|
||||||
import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
|
import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
|
||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
|
import com.android.internal.util.ScreenshotHelper;
|
||||||
import com.android.server.wm.WindowManagerInternal;
|
import com.android.server.wm.WindowManagerInternal;
|
||||||
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
|
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
|
||||||
import com.android.server.wm.WindowManagerService;
|
import com.android.server.wm.WindowManagerService;
|
||||||
@@ -87,6 +93,7 @@ import org.mockito.quality.Strictness;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,7 +121,16 @@ public final class GameServiceProviderInstanceImplTest {
|
|||||||
new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity");
|
new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity");
|
||||||
|
|
||||||
|
|
||||||
private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
|
private static final Bitmap TEST_BITMAP;
|
||||||
|
static {
|
||||||
|
Picture picture = new Picture();
|
||||||
|
Canvas canvas = picture.beginRecording(200, 100);
|
||||||
|
Paint p = new Paint();
|
||||||
|
p.setColor(Color.BLACK);
|
||||||
|
canvas.drawCircle(10, 10, 10, p);
|
||||||
|
picture.endRecording();
|
||||||
|
TEST_BITMAP = Bitmap.createBitmap(picture);
|
||||||
|
}
|
||||||
|
|
||||||
private MockitoSession mMockingSession;
|
private MockitoSession mMockingSession;
|
||||||
private GameServiceProviderInstance mGameServiceProviderInstance;
|
private GameServiceProviderInstance mGameServiceProviderInstance;
|
||||||
@@ -126,6 +142,8 @@ public final class GameServiceProviderInstanceImplTest {
|
|||||||
private WindowManagerInternal mMockWindowManagerInternal;
|
private WindowManagerInternal mMockWindowManagerInternal;
|
||||||
@Mock
|
@Mock
|
||||||
private IActivityManager mMockActivityManager;
|
private IActivityManager mMockActivityManager;
|
||||||
|
@Mock
|
||||||
|
private ScreenshotHelper mMockScreenshotHelper;
|
||||||
private MockContext mMockContext;
|
private MockContext mMockContext;
|
||||||
private FakeGameClassifier mFakeGameClassifier;
|
private FakeGameClassifier mFakeGameClassifier;
|
||||||
private FakeGameService mFakeGameService;
|
private FakeGameService mFakeGameService;
|
||||||
@@ -192,7 +210,8 @@ public final class GameServiceProviderInstanceImplTest {
|
|||||||
mMockWindowManagerService,
|
mMockWindowManagerService,
|
||||||
mMockWindowManagerInternal,
|
mMockWindowManagerInternal,
|
||||||
mFakeGameServiceConnector,
|
mFakeGameServiceConnector,
|
||||||
mFakeGameSessionServiceConnector);
|
mFakeGameSessionServiceConnector,
|
||||||
|
mMockScreenshotHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -425,6 +444,7 @@ public final class GameServiceProviderInstanceImplTest {
|
|||||||
public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() {
|
public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() {
|
||||||
mGameServiceProviderInstance.start();
|
mGameServiceProviderInstance.start();
|
||||||
startTask(10, GAME_A_MAIN_ACTIVITY);
|
startTask(10, GAME_A_MAIN_ACTIVITY);
|
||||||
|
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
|
||||||
mFakeGameService.requestCreateGameSession(10);
|
mFakeGameService.requestCreateGameSession(10);
|
||||||
|
|
||||||
FakeGameSession gameSession10 = new FakeGameSession();
|
FakeGameSession gameSession10 = new FakeGameSession();
|
||||||
@@ -446,6 +466,7 @@ public final class GameServiceProviderInstanceImplTest {
|
|||||||
public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() {
|
public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() {
|
||||||
mGameServiceProviderInstance.start();
|
mGameServiceProviderInstance.start();
|
||||||
startTask(10, GAME_A_MAIN_ACTIVITY);
|
startTask(10, GAME_A_MAIN_ACTIVITY);
|
||||||
|
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
|
||||||
mFakeGameService.requestCreateGameSession(10);
|
mFakeGameService.requestCreateGameSession(10);
|
||||||
|
|
||||||
FakeGameSession gameSession10 = new FakeGameSession();
|
FakeGameSession gameSession10 = new FakeGameSession();
|
||||||
@@ -799,27 +820,32 @@ public final class GameServiceProviderInstanceImplTest {
|
|||||||
SurfaceControl mockOverlaySurfaceControl = Mockito.mock(SurfaceControl.class);
|
SurfaceControl mockOverlaySurfaceControl = Mockito.mock(SurfaceControl.class);
|
||||||
SurfaceControl[] excludeLayers = new SurfaceControl[1];
|
SurfaceControl[] excludeLayers = new SurfaceControl[1];
|
||||||
excludeLayers[0] = mockOverlaySurfaceControl;
|
excludeLayers[0] = mockOverlaySurfaceControl;
|
||||||
|
int taskId = 10;
|
||||||
when(mMockWindowManagerService.captureTaskBitmap(eq(10), any())).thenReturn(TEST_BITMAP);
|
when(mMockWindowManagerService.captureTaskBitmap(eq(10), any())).thenReturn(TEST_BITMAP);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
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());
|
||||||
mGameServiceProviderInstance.start();
|
mGameServiceProviderInstance.start();
|
||||||
startTask(10, GAME_A_MAIN_ACTIVITY);
|
startTask(taskId, GAME_A_MAIN_ACTIVITY);
|
||||||
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
|
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
|
||||||
mFakeGameService.requestCreateGameSession(10);
|
mFakeGameService.requestCreateGameSession(taskId);
|
||||||
|
|
||||||
FakeGameSession gameSession10 = new FakeGameSession();
|
FakeGameSession gameSession10 = new FakeGameSession();
|
||||||
SurfacePackage mockOverlaySurfacePackage = Mockito.mock(SurfacePackage.class);
|
SurfacePackage mockOverlaySurfacePackage = Mockito.mock(SurfacePackage.class);
|
||||||
when(mockOverlaySurfacePackage.getSurfaceControl()).thenReturn(mockOverlaySurfaceControl);
|
when(mockOverlaySurfacePackage.getSurfaceControl()).thenReturn(mockOverlaySurfaceControl);
|
||||||
mFakeGameSessionService.removePendingFutureForTaskId(10)
|
mFakeGameSessionService.removePendingFutureForTaskId(taskId)
|
||||||
.complete(new CreateGameSessionResult(gameSession10, mockOverlaySurfacePackage));
|
.complete(new CreateGameSessionResult(gameSession10, mockOverlaySurfacePackage));
|
||||||
|
|
||||||
IGameSessionController gameSessionController = getOnlyElement(
|
IGameSessionController gameSessionController = getOnlyElement(
|
||||||
mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
|
mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
|
||||||
AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();
|
AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();
|
||||||
gameSessionController.takeScreenshot(10, resultFuture);
|
gameSessionController.takeScreenshot(taskId, resultFuture);
|
||||||
|
|
||||||
GameScreenshotResult result = resultFuture.get();
|
GameScreenshotResult result = resultFuture.get();
|
||||||
assertEquals(GameScreenshotResult.GAME_SCREENSHOT_SUCCESS, result.getStatus());
|
assertEquals(GameScreenshotResult.GAME_SCREENSHOT_SUCCESS, result.getStatus());
|
||||||
assertEquals(TEST_BITMAP, result.getBitmap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user