summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/service/games/GameScreenshotResult.java181
-rw-r--r--core/java/android/service/games/GameSession.java127
-rw-r--r--core/java/android/service/games/GameSessionService.java11
-rw-r--r--core/java/android/service/games/IGameSessionController.aidl26
-rw-r--r--core/java/android/service/games/IGameSessionService.aidl2
5 files changed, 341 insertions, 6 deletions
diff --git a/core/java/android/service/games/GameScreenshotResult.java b/core/java/android/service/games/GameScreenshotResult.java
new file mode 100644
index 000000000000..ae76e08c7971
--- /dev/null
+++ b/core/java/android/service/games/GameScreenshotResult.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 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 android.service.games;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Result object for calls to {@link IGameSessionController#takeScreenshot}.
+ *
+ * It includes a status (see {@link #getStatus}) and, if the status is
+ * {@link #GAME_SCREENSHOT_SUCCESS} an {@link android.graphics.Bitmap} result (see {@link
+ * #getBitmap}).
+ *
+ * @hide
+ */
+public final class GameScreenshotResult implements Parcelable {
+
+ /**
+ * The status of a call to {@link IGameSessionController#takeScreenshot} will be represented by
+ * one of these values.
+ *
+ * @hide
+ */
+ @IntDef(flag = false, prefix = {"GAME_SCREENSHOT_"}, value = {
+ GAME_SCREENSHOT_SUCCESS, // 0
+ GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, // 1
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GameScreenshotStatus {
+ }
+
+ /**
+ * 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
+ * {@link #getBitmap}.
+ *
+ * @hide
+ */
+ public static final int GAME_SCREENSHOT_SUCCESS = 0;
+
+ /**
+ * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} failed
+ * due to an internal error.
+ *
+ * This error may occur if the device is not in a suitable state for a screenshot to be taken
+ * (e.g., the screen is off) or if the game task is not in a suitable state for a screenshot
+ * to be taken (e.g., the task is not visible). To make sure that the device and game are
+ * in a suitable state, the caller can monitor the lifecycle methods for the {@link
+ * GameSession} to make sure that the game task is focused. If the conditions are met, then the
+ * caller may try again immediately.
+ *
+ * @hide
+ */
+ public static final int GAME_SCREENSHOT_ERROR_INTERNAL_ERROR = 1;
+
+ @NonNull
+ public static final Parcelable.Creator<GameScreenshotResult> CREATOR =
+ new Parcelable.Creator<GameScreenshotResult>() {
+ @Override
+ public GameScreenshotResult createFromParcel(Parcel source) {
+ return new GameScreenshotResult(
+ source.readInt(),
+ source.readParcelable(null, Bitmap.class));
+ }
+
+ @Override
+ public GameScreenshotResult[] newArray(int size) {
+ return new GameScreenshotResult[0];
+ }
+ };
+
+ @GameScreenshotStatus
+ private final int mStatus;
+
+ @Nullable
+ private final Bitmap mBitmap;
+
+ /**
+ * Creates a successful {@link GameScreenshotResult} with the provided bitmap.
+ */
+ public static GameScreenshotResult createSuccessResult(@NonNull Bitmap bitmap) {
+ return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS, bitmap);
+ }
+
+ /**
+ * Creates a failed {@link GameScreenshotResult} with an
+ * {@link #GAME_SCREENSHOT_ERROR_INTERNAL_ERROR} status.
+ */
+ public static GameScreenshotResult createInternalErrorResult() {
+ return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, null);
+ }
+
+ private GameScreenshotResult(@GameScreenshotStatus int status, @Nullable Bitmap bitmap) {
+ this.mStatus = status;
+ this.mBitmap = bitmap;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeParcelable(mBitmap, flags);
+ }
+
+ @GameScreenshotStatus
+ public int getStatus() {
+ 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
+ public String toString() {
+ return "GameScreenshotResult{"
+ + "mStatus="
+ + mStatus
+ + ", has bitmap='"
+ + mBitmap != null ? "yes" : "no"
+ + "\'}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof GameScreenshotResult)) {
+ return false;
+ }
+
+ GameScreenshotResult that = (GameScreenshotResult) o;
+ return mStatus == that.mStatus
+ && Objects.equals(mBitmap, that.mBitmap);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus, mBitmap);
+ }
+}
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index 1a5331f10525..b6fe067cfc71 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -17,19 +17,29 @@
package android.service.games;
import android.annotation.Hide;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Slog;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.function.pooled.PooledLambda;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
/**
* An active game session, providing a facility for the implementation to interact with the game.
*
@@ -42,6 +52,8 @@ import com.android.internal.util.function.pooled.PooledLambda;
@SystemApi
public abstract class GameSession {
+ private static final String TAG = "GameSession";
+
final IGameSession mInterface = new IGameSession.Stub() {
@Override
public void destroy() {
@@ -50,15 +62,24 @@ public abstract class GameSession {
}
};
+ private IGameSessionController mGameSessionController;
+ private int mTaskId;
private GameSessionRootView mGameSessionRootView;
private SurfaceControlViewHost mSurfaceControlViewHost;
- @Hide
- void attach(
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public void attach(
+ IGameSessionController gameSessionController,
+ int taskId,
@NonNull Context context,
@NonNull SurfaceControlViewHost surfaceControlViewHost,
int widthPx,
int heightPx) {
+ mGameSessionController = gameSessionController;
+ mTaskId = taskId;
mSurfaceControlViewHost = surfaceControlViewHost;
mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost);
surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx);
@@ -133,4 +154,106 @@ public abstract class GameSession {
mSurfaceControlViewHost.relayout(bounds.width(), bounds.height());
}
}
+
+ /**
+ * Interface for returning screenshot outcome from calls to {@link #takeScreenshot}.
+ */
+ public interface ScreenshotCallback {
+
+ /**
+ * The status of a failed screenshot attempt provided by {@link #onFailure}.
+ *
+ * @hide
+ */
+ @IntDef(flag = false, prefix = {"ERROR_TAKE_SCREENSHOT_"}, value = {
+ ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, // 0
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ScreenshotFailureStatus {
+ }
+
+ /**
+ * An error code indicating that an internal error occurred when attempting to take a
+ * screenshot of the game task. If this code is returned, the caller should verify that the
+ * conditions for taking a screenshot are met (device screen is on and the game task is
+ * visible). To do so, the caller can monitor the lifecycle methods for this session to
+ * make sure that the game task is focused. If the conditions are met, then the caller may
+ * try again immediately.
+ */
+ int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0;
+
+ /**
+ * Called when taking the screenshot failed.
+ * @param statusCode Indicates the reason for failure.
+ */
+ void onFailure(@ScreenshotFailureStatus int statusCode);
+
+ /**
+ * Called when taking the screenshot succeeded.
+ * @param bitmap The screenshot.
+ */
+ void onSuccess(@NonNull Bitmap bitmap);
+ }
+
+ /**
+ * 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.
+ *
+ * If the callback is called with {@link ScreenshotCallback#onSuccess}, the provided {@link
+ * Bitmap} may be used.
+ *
+ * If the callback is called with {@link ScreenshotCallback#onFailure}, the provided status
+ * code should be checked.
+ *
+ * If the status code is {@link ScreenshotCallback#ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR},
+ * then the caller should verify that the conditions for calling this method are met (device
+ * screen is on and the game task is visible). To do so, the caller can monitor the lifecycle
+ * methods for this session to make sure that the game task is focused. If the conditions are
+ * met, then the caller may try again immediately.
+ *
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when taking screenshot has succeeded
+ * or failed.
+ * @throws IllegalStateException if this method is called prior to {@link #onCreate}.
+ */
+ public void takeScreenshot(@NonNull Executor executor, @NonNull ScreenshotCallback callback) {
+ if (mGameSessionController == null) {
+ throw new IllegalStateException("Can not call before onCreate()");
+ }
+
+ AndroidFuture<GameScreenshotResult> takeScreenshotResult =
+ new AndroidFuture<GameScreenshotResult>().whenCompleteAsync((result, error) -> {
+ handleScreenshotResult(callback, result, error);
+ }, executor);
+
+ try {
+ mGameSessionController.takeScreenshot(mTaskId, takeScreenshotResult);
+ } catch (RemoteException ex) {
+ takeScreenshotResult.completeExceptionally(ex);
+ }
+ }
+
+ private void handleScreenshotResult(
+ @NonNull ScreenshotCallback callback,
+ @NonNull GameScreenshotResult result,
+ @NonNull Throwable error) {
+ if (error != null) {
+ Slog.w(TAG, error.getMessage(), error.getCause());
+ callback.onFailure(
+ ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR);
+ return;
+ }
+
+ @GameScreenshotResult.GameScreenshotStatus int status = result.getStatus();
+ switch (status) {
+ case GameScreenshotResult.GAME_SCREENSHOT_SUCCESS:
+ callback.onSuccess(result.getBitmap());
+ break;
+ case GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR:
+ Slog.w(TAG, "Error taking screenshot");
+ callback.onFailure(
+ ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR);
+ break;
+ }
+ }
}
diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java
index 195a0f233307..df5bad5c53b2 100644
--- a/core/java/android/service/games/GameSessionService.java
+++ b/core/java/android/service/games/GameSessionService.java
@@ -52,8 +52,6 @@ import java.util.Objects;
*/
@SystemApi
public abstract class GameSessionService extends Service {
- private static final String TAG = "GameSessionService";
-
/**
* The {@link Intent} action used when binding to the service.
* To be supported, the service must require the
@@ -67,11 +65,13 @@ public abstract class GameSessionService extends Service {
private final IGameSessionService mInterface = new IGameSessionService.Stub() {
@Override
public void create(
+ IGameSessionController gameSessionController,
CreateGameSessionRequest createGameSessionRequest,
GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
AndroidFuture gameSessionFuture) {
Handler.getMain().post(PooledLambda.obtainRunnable(
GameSessionService::doCreate, GameSessionService.this,
+ gameSessionController,
createGameSessionRequest,
gameSessionViewHostConfiguration,
gameSessionFuture));
@@ -101,6 +101,7 @@ public abstract class GameSessionService extends Service {
}
private void doCreate(
+ IGameSessionController gameSessionController,
CreateGameSessionRequest createGameSessionRequest,
GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) {
@@ -119,7 +120,10 @@ public abstract class GameSessionService extends Service {
SurfaceControlViewHost surfaceControlViewHost =
new SurfaceControlViewHost(this, display, hostToken);
- gameSession.attach(this,
+ gameSession.attach(
+ gameSessionController,
+ createGameSessionRequest.getTaskId(),
+ this,
surfaceControlViewHost,
gameSessionViewHostConfiguration.mWidthPx,
gameSessionViewHostConfiguration.mHeightPx);
@@ -130,7 +134,6 @@ public abstract class GameSessionService extends Service {
createGameSessionResultFuture.complete(createGameSessionResult);
-
gameSession.doCreate();
}
diff --git a/core/java/android/service/games/IGameSessionController.aidl b/core/java/android/service/games/IGameSessionController.aidl
new file mode 100644
index 000000000000..fe1d3629918e
--- /dev/null
+++ b/core/java/android/service/games/IGameSessionController.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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 android.service.games;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * @hide
+ */
+oneway interface IGameSessionController {
+ void takeScreenshot(int taskId, in AndroidFuture gameScreenshotResultFuture);
+} \ No newline at end of file
diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl
index dcbcbc16a374..37cde561f549 100644
--- a/core/java/android/service/games/IGameSessionService.aidl
+++ b/core/java/android/service/games/IGameSessionService.aidl
@@ -16,6 +16,7 @@
package android.service.games;
+import android.service.games.IGameSessionController;
import android.service.games.IGameSession;
import android.service.games.CreateGameSessionRequest;
import android.service.games.GameSessionViewHostConfiguration;
@@ -28,6 +29,7 @@ import com.android.internal.infra.AndroidFuture;
*/
oneway interface IGameSessionService {
void create(
+ in IGameSessionController gameSessionController,
in CreateGameSessionRequest createGameSessionRequest,
in GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
in AndroidFuture /* T=CreateGameSessionResult */ createGameSessionResultFuture);