From 620c5cccd129db4fbfeaa454abdb718b2436dcc8 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 17 Nov 2021 16:22:25 +0000 Subject: Game Loading Time improvement by boosting CPU via ADPF Adds the concept of Game State (supplied by the application) to the GameManager API. This allows the setting of various modes to describe the current high-level runtime state of the game, as well as the loading state. In Performance Mode, during loading a hint is sent to the device PowerHAL via the PowerManager. Bug: 201769701 Test: atest android.gamemanager.cts.GameManagerTest Ignore-AOSP-First: Mentions a confidential API Change-Id: I695e45b12f9a30c1824df210bfefbd5a9b4bd935 --- core/api/current.txt | 18 +++ core/java/android/app/GameManager.java | 12 ++ core/java/android/app/GameState.aidl | 19 +++ core/java/android/app/GameState.java | 179 +++++++++++++++++++++ core/java/android/app/IGameManagerService.aidl | 3 + core/java/android/app/OWNERS | 1 + services/core/Android.bp | 3 +- .../com/android/server/app/GameManagerService.java | 71 ++++++-- services/core/jni/Android.bp | 2 +- 9 files changed, 295 insertions(+), 13 deletions(-) create mode 100644 core/java/android/app/GameState.aidl create mode 100644 core/java/android/app/GameState.java diff --git a/core/api/current.txt b/core/api/current.txt index b40a3e527f72..3d3e24ee7e6a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5438,12 +5438,30 @@ package android.app { public final class GameManager { method public int getGameMode(); + method public void setGameState(@NonNull android.app.GameState); field public static final int GAME_MODE_BATTERY = 3; // 0x3 field public static final int GAME_MODE_PERFORMANCE = 2; // 0x2 field public static final int GAME_MODE_STANDARD = 1; // 0x1 field public static final int GAME_MODE_UNSUPPORTED = 0; // 0x0 } + public final class GameState implements android.os.Parcelable { + ctor public GameState(boolean, int); + ctor public GameState(boolean, int, @Nullable String, @NonNull android.os.Bundle); + method public int describeContents(); + method @Nullable public String getDescription(); + method @NonNull public android.os.Bundle getMetadata(); + method public int getMode(); + method public boolean isLoading(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int MODE_CONTENT = 4; // 0x4 + field public static final int MODE_GAMEPLAY_INTERRUPTIBLE = 2; // 0x2 + field public static final int MODE_GAMEPLAY_UNINTERRUPTIBLE = 3; // 0x3 + field public static final int MODE_NONE = 1; // 0x1 + field public static final int MODE_UNKNOWN = 0; // 0x0 + } + public class Instrumentation { ctor public Instrumentation(); method public android.os.TestLooperManager acquireLooperManager(android.os.Looper); diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index 78759db28539..29e1b70097f2 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -167,4 +167,16 @@ public final class GameManager { throw e.rethrowFromSystemServer(); } } + + /** + * Called by games to communicate the current state to the platform. + * @param gameState An object set to the current state. + */ + public void setGameState(@NonNull GameState gameState) { + try { + mService.setGameState(mContext.getPackageName(), gameState, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/GameState.aidl b/core/java/android/app/GameState.aidl new file mode 100644 index 000000000000..b829e866a056 --- /dev/null +++ b/core/java/android/app/GameState.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.app; + +parcelable GameState; \ No newline at end of file diff --git a/core/java/android/app/GameState.java b/core/java/android/app/GameState.java new file mode 100644 index 000000000000..979dd34e8276 --- /dev/null +++ b/core/java/android/app/GameState.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * State of the game passed to the GameManager. + * + * This includes a top-level state for the game (indicating if the game can be interrupted without + * interfering with content that can't be paused). Since content can be loaded in any state, it + * includes an independent boolean flag to indicate loading status. + * + * Free-form metadata (as a Bundle) and a string description can also be specified by the + * application. + */ +public final class GameState implements Parcelable { + /** + * Default Game mode is unknown. + */ + public static final int MODE_UNKNOWN = 0; + + /** + * No mode means that the game is not in active play, for example the user is using the game + * menu. + */ + public static final int MODE_NONE = 1; + + /** + * Indicates if the game is in active, but interruptible, game play. + */ + public static final int MODE_GAMEPLAY_INTERRUPTIBLE = 2; + + /** + * Indicates if the game is in active user play mode, which is real time and cannot be + * interrupted. + */ + public static final int MODE_GAMEPLAY_UNINTERRUPTIBLE = 3; + + /** + * Indicates that the current content shown is not gameplay related. For example it can be an + * ad, a web page, a text, or a video. + */ + public static final int MODE_CONTENT = 4; + + /** + * Implement the parcelable interface. + */ + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public GameState createFromParcel(Parcel in) { + return new GameState(in); + } + + @Override + public GameState[] newArray(int size) { + return new GameState[size]; + } + }; + + // Indicates if the game is loading assets/resources/compiling/etc. This is independent of game + // mode because there could be a loading UI displayed, or there could be loading in the + // background. + private final boolean mIsLoading; + + // One of the states listed above. + private final @GameStateMode int mMode; + + // This is a game specific description. For example can be level or scene name. + private final @Nullable String mDescription; + + // This contains any other game specific parameters not covered by the fields above. It can be + // quality parameter data, settings, or game modes. + private final @NonNull Bundle mMetaData; + + /** + * Create a GameState with the specified loading status. + * @param isLoading Whether the game is in the loading state. + * @param mode The game state mode of type @GameStateMode. + */ + public GameState(boolean isLoading, @GameStateMode int mode) { + this(isLoading, mode, null, new Bundle()); + } + + /** + * Create a GameState with the given state variables. + * @param isLoading Whether the game is in the loading state. + * @param mode The game state mode of type @GameStateMode. + * @param description An optional description of the state. + * @param metaData Optional metadata. + */ + public GameState(boolean isLoading, @GameStateMode int mode, @Nullable String description, + @NonNull Bundle metaData) { + mIsLoading = isLoading; + mMode = mode; + mDescription = description; + mMetaData = metaData; + } + + private GameState(Parcel in) { + mIsLoading = in.readBoolean(); + mMode = in.readInt(); + mDescription = in.readString(); + mMetaData = in.readBundle(); + } + + /** + * @return If the game is loading assets/resources/compiling/etc. + */ + public boolean isLoading() { + return mIsLoading; + } + + /** + * @return The game state mode. + */ + public @GameStateMode int getMode() { + return mMode; + } + + /** + * @return The state description, or null if one is not set. + */ + public @Nullable String getDescription() { + return mDescription; + } + + /** + * @return metadata associated with the state. + */ + public @NonNull Bundle getMetadata() { + return mMetaData; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeBoolean(mIsLoading); + parcel.writeInt(mMode); + parcel.writeString(mDescription); + parcel.writeBundle(mMetaData); + } + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({MODE_UNKNOWN, MODE_NONE, MODE_GAMEPLAY_INTERRUPTIBLE, MODE_GAMEPLAY_UNINTERRUPTIBLE, + MODE_CONTENT + + }) + @interface GameStateMode {} +} diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index 189f0a2fca23..d9aa586c6bbb 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -16,6 +16,8 @@ package android.app; +import android.app.GameState; + /** * @hide */ @@ -24,4 +26,5 @@ interface IGameManagerService { void setGameMode(String packageName, int gameMode, int userId); int[] getAvailableGameModes(String packageName); boolean getAngleEnabled(String packageName, int userId); + void setGameState(String packageName, in GameState gameState, int userId); } \ No newline at end of file diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index e099716cd45a..068304d75910 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -31,6 +31,7 @@ per-file SystemServiceRegistry.java = file:/services/core/java/com/android/serve per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS per-file UiAutomation.java = file:/services/accessibility/OWNERS per-file GameManager* = file:/GAME_MANAGER_OWNERS +per-file GameState* = file:/GAME_MANAGER_OWNERS per-file IGameManager* = file:/GAME_MANAGER_OWNERS # ActivityThread diff --git a/services/core/Android.bp b/services/core/Android.bp index 9351415c1fb0..bfefdf2cba45 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -128,8 +128,6 @@ java_library_static { "android.hardware.common-V2-java", "android.hardware.light-V2.0-java", "android.hardware.gnss-V2-java", - "android.hardware.power-V1-java", - "android.hardware.power-V1.0-java", "android.hardware.vibrator-V2-java", "app-compat-annotations", "framework-tethering.stubs.module_lib", @@ -167,6 +165,7 @@ java_library_static { "android.hardware.rebootescrow-V1-java", "android.hardware.soundtrigger-V2.3-java", "android.hardware.power.stats-V1-java", + "android.hardware.power-V3-java", "android.hidl.manager-V1.2-java", "capture_state_listener-aidl-java", "icu4j_calendar_astronomer", diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 456a75832b16..05764b35f189 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -42,6 +42,7 @@ import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; +import android.app.GameState; import android.app.IGameManagerService; import android.app.compat.PackageOverride; import android.content.BroadcastReceiver; @@ -51,12 +52,15 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.hardware.power.Mode; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -72,6 +76,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IPlatformCompat; +import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; @@ -95,11 +100,14 @@ public final class GameManagerService extends IGameManagerService.Stub { static final int WRITE_SETTINGS = 1; static final int REMOVE_SETTINGS = 2; static final int POPULATE_GAME_MODE_SETTINGS = 3; + static final int SET_GAME_STATE = 4; static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds static final PackageOverride COMPAT_ENABLED = new PackageOverride.Builder().setEnabled(true) .build(); static final PackageOverride COMPAT_DISABLED = new PackageOverride.Builder().setEnabled(false) .build(); + private static final String PACKAGE_NAME_MSG_KEY = "packageName"; + private static final String USER_ID_MSG_KEY = "userId"; private final Context mContext; private final Object mLock = new Object(); @@ -107,6 +115,7 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Handler mHandler; private final PackageManager mPackageManager; private final IPlatformCompat mPlatformCompat; + private final PowerManagerInternal mPowerManagerInternal; private DeviceConfigListener mDeviceConfigListener; @GuardedBy("mLock") private final ArrayMap mSettings = new ArrayMap<>(); @@ -123,6 +132,7 @@ public final class GameManagerService extends IGameManagerService.Stub { mPackageManager = mContext.getPackageManager(); mPlatformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); } @Override @@ -200,6 +210,19 @@ public final class GameManagerService extends IGameManagerService.Stub { updateConfigsForUser(userId, packageNames); break; } + case SET_GAME_STATE: { + if (mPowerManagerInternal == null) { + final Bundle data = msg.getData(); + Slog.d(TAG, "Error setting loading mode for package " + + data.getString(PACKAGE_NAME_MSG_KEY) + + " and userId " + data.getInt(USER_ID_MSG_KEY)); + break; + } + final GameState gameState = (GameState) msg.obj; + final boolean isLoading = gameState.isLoading(); + mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading); + break; + } } } } @@ -257,6 +280,32 @@ public final class GameManagerService extends IGameManagerService.Stub { return 0; } + /** + * Called by games to communicate the current state to the platform. + * @param packageName The client package name. + * @param gameState An object set to the current state. + * @param userId The user associated with this state. + */ + public void setGameState(String packageName, @NonNull GameState gameState, int userId) { + if (!isPackageGame(packageName, userId)) { + // Restrict to games only. + return; + } + + if (getGameMode(packageName, userId) != GameManager.GAME_MODE_PERFORMANCE) { + // Requires performance mode to be enabled. + return; + } + + final Message msg = mHandler.obtainMessage(SET_GAME_STATE); + final Bundle data = new Bundle(); + data.putString(PACKAGE_NAME_MSG_KEY, packageName); + data.putInt(USER_ID_MSG_KEY, userId); + msg.setData(data); + msg.obj = gameState; + mHandler.sendMessage(msg); + } + /** * GamePackageConfiguration manages all game mode config details for its associated package. */ @@ -620,6 +669,16 @@ public final class GameManagerService extends IGameManagerService.Stub { return getGameModeFromSettings(packageName, userId); } + private boolean isPackageGame(String packageName, int userId) { + try { + final ApplicationInfo applicationInfo = mPackageManager + .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); + return applicationInfo.category == ApplicationInfo.CATEGORY_GAME; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + /** * Sets the Game Mode for the package name. * Verifies that the calling process has {@link android.Manifest.permission#MANAGE_GAME_MODE}. @@ -630,16 +689,8 @@ public final class GameManagerService extends IGameManagerService.Stub { throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); - // Restrict to games only. - try { - final ApplicationInfo applicationInfo = mPackageManager - .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); - if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { - // Ignore attempt to set the game mode for applications that are not identified - // as game. See {@link PackageManager#setApplicationCategoryHint(String, int)} - return; - } - } catch (PackageManager.NameNotFoundException e) { + if (!isPackageGame(packageName, userId)) { + // Restrict to games only. return; } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 978eb1d52f3d..9ec37f4d26ac 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -161,7 +161,7 @@ cc_defaults { "android.hardware.memtrack-V1-ndk", "android.hardware.power@1.0", "android.hardware.power@1.1", - "android.hardware.power-V2-cpp", + "android.hardware.power-V3-cpp", "android.hardware.power.stats@1.0", "android.hardware.power.stats-V1-ndk", "android.hardware.thermal@1.0", -- cgit v1.2.3-59-g8ed1b