diff --git a/core/api/current.txt b/core/api/current.txt index b40a3e527f726..3d3e24ee7e6a9 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 78759db28539e..29e1b70097f2b 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 0000000000000..b829e866a056c --- /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 0000000000000..979dd34e8276c --- /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 189f0a2fca23b..d9aa586c6bbbb 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 e099716cd45ac..068304d75910a 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 9351415c1fb0a..bfefdf2cba459 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 456a75832b161..05764b35f189f 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 978eb1d52f3d9..9ec37f4d26acc 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",