Loading core/java/android/app/IGameManagerService.aidl +3 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.app.GameModeConfiguration; import android.app.GameModeInfo; import android.app.GameModeInfo; import android.app.GameState; import android.app.GameState; import android.app.IGameModeListener; import android.app.IGameModeListener; import android.app.IGameStateListener; /** /** * @hide * @hide Loading Loading @@ -49,4 +50,6 @@ interface IGameManagerService { void addGameModeListener(IGameModeListener gameModeListener); void addGameModeListener(IGameModeListener gameModeListener); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)") void removeGameModeListener(IGameModeListener gameModeListener); void removeGameModeListener(IGameModeListener gameModeListener); void addGameStateListener(IGameStateListener gameStateListener); void removeGameStateListener(IGameStateListener gameStateListener); } } core/java/android/app/IGameStateListener.aidl 0 → 100644 +27 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import android.app.GameState; /** @hide */ interface IGameStateListener { /** * Called when the state of the game has changed. */ oneway void onGameStateChanged(String packageName, in GameState state, int userId); } services/core/java/com/android/server/app/GameManagerService.java +51 −0 Original line number Original line Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.app.GameModeInfo; import android.app.GameState; import android.app.GameState; import android.app.IGameManagerService; import android.app.IGameManagerService; import android.app.IGameModeListener; import android.app.IGameModeListener; import android.app.IGameStateListener; import android.app.StatsManager; import android.app.StatsManager; import android.app.UidObserver; import android.app.UidObserver; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; Loading Loading @@ -148,6 +149,7 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Object mLock = new Object(); private final Object mLock = new Object(); private final Object mDeviceConfigLock = new Object(); private final Object mDeviceConfigLock = new Object(); private final Object mGameModeListenerLock = new Object(); private final Object mGameModeListenerLock = new Object(); private final Object mGameStateListenerLock = new Object(); @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) final Handler mHandler; final Handler mHandler; private final PackageManager mPackageManager; private final PackageManager mPackageManager; Loading @@ -164,6 +166,8 @@ public final class GameManagerService extends IGameManagerService.Stub { // listener to caller uid map // listener to caller uid map @GuardedBy("mGameModeListenerLock") @GuardedBy("mGameModeListenerLock") private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>(); private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>(); @GuardedBy("mGameStateListenerLock") private final ArrayMap<IGameStateListener, Integer> mGameStateListeners = new ArrayMap<>(); @Nullable @Nullable private final GameServiceController mGameServiceController; private final GameServiceController mGameServiceController; private final Object mUidObserverLock = new Object(); private final Object mUidObserverLock = new Object(); Loading Loading @@ -352,6 +356,16 @@ public final class GameManagerService extends IGameManagerService.Stub { loadingBoostDuration); loadingBoostDuration); } } } } synchronized (mGameStateListenerLock) { for (IGameStateListener listener : mGameStateListeners.keySet()) { try { listener.onGameStateChanged(packageName, gameState, userId); } catch (RemoteException ex) { Slog.w(TAG, "Cannot notify game state change for listener added by " + mGameStateListeners.get(listener)); } } } break; break; } } case CANCEL_GAME_LOADING_MODE: { case CANCEL_GAME_LOADING_MODE: { Loading Loading @@ -1473,6 +1487,43 @@ public final class GameManagerService extends IGameManagerService.Stub { } } } } /** * Adds a game state listener. */ @Override public void addGameStateListener(@NonNull IGameStateListener listener) { try { final IBinder listenerBinder = listener.asBinder(); listenerBinder.linkToDeath(new DeathRecipient() { @Override public void binderDied() { removeGameStateListenerUnchecked(listener); listenerBinder.unlinkToDeath(this, 0 /*flags*/); } }, 0 /*flags*/); synchronized (mGameStateListenerLock) { mGameStateListeners.put(listener, Binder.getCallingUid()); } } catch (RemoteException ex) { Slog.e(TAG, "Failed to link death recipient for IGameStateListener from caller " + Binder.getCallingUid() + ", abandoned its listener registration", ex); } } /** * Removes a game state listener. */ @Override public void removeGameStateListener(@NonNull IGameStateListener listener) { removeGameStateListenerUnchecked(listener); } private void removeGameStateListenerUnchecked(IGameStateListener listener) { synchronized (mGameStateListenerLock) { mGameStateListeners.remove(listener); } } /** /** * Notified when boot is completed. * Notified when boot is completed. */ */ Loading services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +66 −0 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.app.GameModeConfiguration; import android.app.GameModeInfo; import android.app.GameModeInfo; import android.app.GameState; import android.app.GameState; import android.app.IGameModeListener; import android.app.IGameModeListener; import android.app.IGameStateListener; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Context; import android.content.ContextWrapper; import android.content.ContextWrapper; Loading Loading @@ -1578,6 +1579,71 @@ public class GameManagerServiceTests { assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); } } @Test public void testAddGameStateListener() throws Exception { mockModifyGameModeGranted(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); mockDeviceConfigAll(); startUser(gameManagerService, USER_ID_1); IGameStateListener mockListener = Mockito.mock(IGameStateListener.class); IBinder binder = Mockito.mock(IBinder.class); when(mockListener.asBinder()).thenReturn(binder); gameManagerService.addGameStateListener(mockListener); verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO); GameState gameState = new GameState(true, GameState.MODE_NONE); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt()); mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME); gameState = new GameState(true, GameState.MODE_NONE); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1); reset(mockListener); gameState = new GameState(false, GameState.MODE_CONTENT); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); mTestLooper.dispatchAll(); verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1); reset(mockListener); mDeathRecipientCaptor.getValue().binderDied(); verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt()); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt()); } @Test public void testRemoveGameStateListener() throws Exception { mockModifyGameModeGranted(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); mockDeviceConfigAll(); startUser(gameManagerService, USER_ID_1); IGameStateListener mockListener = Mockito.mock(IGameStateListener.class); IBinder binder = Mockito.mock(IBinder.class); when(mockListener.asBinder()).thenReturn(binder); gameManagerService.addGameStateListener(mockListener); gameManagerService.removeGameStateListener(mockListener); mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME); GameState gameState = new GameState(false, GameState.MODE_CONTENT); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt()); } private List<String> readGameModeInterventionList() throws Exception { private List<String> readGameModeInterventionList() throws Exception { final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(), final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(), "system/game_mode_intervention.list"); "system/game_mode_intervention.list"); Loading Loading
core/java/android/app/IGameManagerService.aidl +3 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.app.GameModeConfiguration; import android.app.GameModeInfo; import android.app.GameModeInfo; import android.app.GameState; import android.app.GameState; import android.app.IGameModeListener; import android.app.IGameModeListener; import android.app.IGameStateListener; /** /** * @hide * @hide Loading Loading @@ -49,4 +50,6 @@ interface IGameManagerService { void addGameModeListener(IGameModeListener gameModeListener); void addGameModeListener(IGameModeListener gameModeListener); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)") void removeGameModeListener(IGameModeListener gameModeListener); void removeGameModeListener(IGameModeListener gameModeListener); void addGameStateListener(IGameStateListener gameStateListener); void removeGameStateListener(IGameStateListener gameStateListener); } }
core/java/android/app/IGameStateListener.aidl 0 → 100644 +27 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import android.app.GameState; /** @hide */ interface IGameStateListener { /** * Called when the state of the game has changed. */ oneway void onGameStateChanged(String packageName, in GameState state, int userId); }
services/core/java/com/android/server/app/GameManagerService.java +51 −0 Original line number Original line Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.app.GameModeInfo; import android.app.GameState; import android.app.GameState; import android.app.IGameManagerService; import android.app.IGameManagerService; import android.app.IGameModeListener; import android.app.IGameModeListener; import android.app.IGameStateListener; import android.app.StatsManager; import android.app.StatsManager; import android.app.UidObserver; import android.app.UidObserver; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; Loading Loading @@ -148,6 +149,7 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Object mLock = new Object(); private final Object mLock = new Object(); private final Object mDeviceConfigLock = new Object(); private final Object mDeviceConfigLock = new Object(); private final Object mGameModeListenerLock = new Object(); private final Object mGameModeListenerLock = new Object(); private final Object mGameStateListenerLock = new Object(); @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) final Handler mHandler; final Handler mHandler; private final PackageManager mPackageManager; private final PackageManager mPackageManager; Loading @@ -164,6 +166,8 @@ public final class GameManagerService extends IGameManagerService.Stub { // listener to caller uid map // listener to caller uid map @GuardedBy("mGameModeListenerLock") @GuardedBy("mGameModeListenerLock") private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>(); private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>(); @GuardedBy("mGameStateListenerLock") private final ArrayMap<IGameStateListener, Integer> mGameStateListeners = new ArrayMap<>(); @Nullable @Nullable private final GameServiceController mGameServiceController; private final GameServiceController mGameServiceController; private final Object mUidObserverLock = new Object(); private final Object mUidObserverLock = new Object(); Loading Loading @@ -352,6 +356,16 @@ public final class GameManagerService extends IGameManagerService.Stub { loadingBoostDuration); loadingBoostDuration); } } } } synchronized (mGameStateListenerLock) { for (IGameStateListener listener : mGameStateListeners.keySet()) { try { listener.onGameStateChanged(packageName, gameState, userId); } catch (RemoteException ex) { Slog.w(TAG, "Cannot notify game state change for listener added by " + mGameStateListeners.get(listener)); } } } break; break; } } case CANCEL_GAME_LOADING_MODE: { case CANCEL_GAME_LOADING_MODE: { Loading Loading @@ -1473,6 +1487,43 @@ public final class GameManagerService extends IGameManagerService.Stub { } } } } /** * Adds a game state listener. */ @Override public void addGameStateListener(@NonNull IGameStateListener listener) { try { final IBinder listenerBinder = listener.asBinder(); listenerBinder.linkToDeath(new DeathRecipient() { @Override public void binderDied() { removeGameStateListenerUnchecked(listener); listenerBinder.unlinkToDeath(this, 0 /*flags*/); } }, 0 /*flags*/); synchronized (mGameStateListenerLock) { mGameStateListeners.put(listener, Binder.getCallingUid()); } } catch (RemoteException ex) { Slog.e(TAG, "Failed to link death recipient for IGameStateListener from caller " + Binder.getCallingUid() + ", abandoned its listener registration", ex); } } /** * Removes a game state listener. */ @Override public void removeGameStateListener(@NonNull IGameStateListener listener) { removeGameStateListenerUnchecked(listener); } private void removeGameStateListenerUnchecked(IGameStateListener listener) { synchronized (mGameStateListenerLock) { mGameStateListeners.remove(listener); } } /** /** * Notified when boot is completed. * Notified when boot is completed. */ */ Loading
services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +66 −0 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.app.GameModeConfiguration; import android.app.GameModeInfo; import android.app.GameModeInfo; import android.app.GameState; import android.app.GameState; import android.app.IGameModeListener; import android.app.IGameModeListener; import android.app.IGameStateListener; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Context; import android.content.ContextWrapper; import android.content.ContextWrapper; Loading Loading @@ -1578,6 +1579,71 @@ public class GameManagerServiceTests { assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); } } @Test public void testAddGameStateListener() throws Exception { mockModifyGameModeGranted(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); mockDeviceConfigAll(); startUser(gameManagerService, USER_ID_1); IGameStateListener mockListener = Mockito.mock(IGameStateListener.class); IBinder binder = Mockito.mock(IBinder.class); when(mockListener.asBinder()).thenReturn(binder); gameManagerService.addGameStateListener(mockListener); verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO); GameState gameState = new GameState(true, GameState.MODE_NONE); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt()); mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME); gameState = new GameState(true, GameState.MODE_NONE); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1); reset(mockListener); gameState = new GameState(false, GameState.MODE_CONTENT); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); mTestLooper.dispatchAll(); verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1); reset(mockListener); mDeathRecipientCaptor.getValue().binderDied(); verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt()); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt()); } @Test public void testRemoveGameStateListener() throws Exception { mockModifyGameModeGranted(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); mockDeviceConfigAll(); startUser(gameManagerService, USER_ID_1); IGameStateListener mockListener = Mockito.mock(IGameStateListener.class); IBinder binder = Mockito.mock(IBinder.class); when(mockListener.asBinder()).thenReturn(binder); gameManagerService.addGameStateListener(mockListener); gameManagerService.removeGameStateListener(mockListener); mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME); GameState gameState = new GameState(false, GameState.MODE_CONTENT); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt()); } private List<String> readGameModeInterventionList() throws Exception { private List<String> readGameModeInterventionList() throws Exception { final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(), final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(), "system/game_mode_intervention.list"); "system/game_mode_intervention.list"); Loading