Merge "Add IGameStateListener" into udc-qpr-dev

This commit is contained in:
Xiang Wang
2023-07-12 19:28:48 +00:00
committed by Android (Google) Code Review
4 changed files with 147 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameModeListener;
import android.app.IGameStateListener;
/**
* @hide
@@ -49,4 +50,6 @@ interface IGameManagerService {
void addGameModeListener(IGameModeListener gameModeListener);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
void removeGameModeListener(IGameModeListener gameModeListener);
void addGameStateListener(IGameStateListener gameStateListener);
void removeGameStateListener(IGameStateListener gameStateListener);
}

View File

@@ -0,0 +1,27 @@
/*
* 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);
}

View File

@@ -40,6 +40,7 @@ import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.IGameModeListener;
import android.app.IGameStateListener;
import android.app.StatsManager;
import android.app.UidObserver;
import android.content.BroadcastReceiver;
@@ -148,6 +149,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final Object mLock = new Object();
private final Object mDeviceConfigLock = new Object();
private final Object mGameModeListenerLock = new Object();
private final Object mGameStateListenerLock = new Object();
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
final Handler mHandler;
private final PackageManager mPackageManager;
@@ -164,6 +166,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
// listener to caller uid map
@GuardedBy("mGameModeListenerLock")
private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
@GuardedBy("mGameStateListenerLock")
private final ArrayMap<IGameStateListener, Integer> mGameStateListeners = new ArrayMap<>();
@Nullable
private final GameServiceController mGameServiceController;
private final Object mUidObserverLock = new Object();
@@ -352,6 +356,16 @@ public final class GameManagerService extends IGameManagerService.Stub {
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;
}
case CANCEL_GAME_LOADING_MODE: {
@@ -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.
*/

View File

@@ -52,6 +52,7 @@ import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameModeListener;
import android.app.IGameStateListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
@@ -1578,6 +1579,71 @@ public class GameManagerServiceTests {
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 {
final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(),
"system/game_mode_intervention.list");