Send writing file messages on shutdown with no delay

Fix the bug that we are removing messages based on object reference
instead of equality and add unit tests.

Bug: b/240335717
Test: atest GameManagerServiceTests
Change-Id: Ia3905cd14b4122ff8565bf68dc4f1a4ed208bd4b
This commit is contained in:
Xiang Wang
2022-08-16 20:57:57 -07:00
parent 4b3c928df0
commit 117debcb2f
2 changed files with 116 additions and 58 deletions

View File

@@ -99,6 +99,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* Service to manage game related features.
@@ -119,7 +120,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
static final int SET_GAME_STATE = 4;
static final int CANCEL_GAME_LOADING_MODE = 5;
static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds
static final int WRITE_DELAY_MILLIS = 10 * 1000; // 10 seconds
static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds
private static final String PACKAGE_NAME_MSG_KEY = "packageName";
@@ -130,7 +131,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final Context mContext;
private final Object mLock = new Object();
private final Object mDeviceConfigLock = new Object();
private final Handler mHandler;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
final Handler mHandler;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
private final PowerManagerInternal mPowerManagerInternal;
@@ -255,13 +257,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
synchronized (mLock) {
removeMessages(WRITE_SETTINGS, msg.obj);
removeEqualMessages(WRITE_SETTINGS, msg.obj);
}
break;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
synchronized (mLock) {
removeMessages(WRITE_SETTINGS, msg.obj);
removeEqualMessages(WRITE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
GameManagerSettings userSettings = mSettings.get(userId);
userSettings.writePersistentDataLocked();
@@ -275,8 +277,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
synchronized (mLock) {
removeMessages(WRITE_SETTINGS, msg.obj);
removeMessages(REMOVE_SETTINGS, msg.obj);
removeEqualMessages(WRITE_SETTINGS, msg.obj);
removeEqualMessages(REMOVE_SETTINGS, msg.obj);
}
break;
}
@@ -284,8 +286,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
synchronized (mLock) {
// Since the user was removed, ignore previous write message
// and do write here.
removeMessages(WRITE_SETTINGS, msg.obj);
removeMessages(REMOVE_SETTINGS, msg.obj);
removeEqualMessages(WRITE_SETTINGS, msg.obj);
removeEqualMessages(REMOVE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
final GameManagerSettings userSettings = mSettings.get(userId);
mSettings.remove(userId);
@@ -295,7 +297,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
break;
}
case POPULATE_GAME_MODE_SETTINGS: {
removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
final int userId = (int) msg.obj;
final String[] packageNames = getInstalledGamePackageNames(userId);
updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
@@ -341,13 +343,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
synchronized (mLock) {
removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
}
break;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
writeGameModeInterventionsToFile(userId);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
break;
@@ -843,7 +845,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Override
public void onUserStarting(@NonNull TargetUser user) {
mService.onUserStarting(user);
mService.onUserStarting(user,
Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
}
@Override
@@ -1010,18 +1013,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
GameManagerSettings userSettings = mSettings.get(userId);
userSettings.setGameModeLocked(packageName, gameMode);
final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
msg.obj = userId;
if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) {
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
updateInterventions(packageName, gameMode, userId);
final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
msg.obj = userId;
if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
mHandler.sendMessage(msg);
}
sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
"SET_GAME_MODE", 0 /*delayMillis*/);
}
/**
@@ -1208,22 +1204,46 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (mGameServiceController != null) {
mGameServiceController.onBootComplete();
}
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
synchronized (mLock) {
// Note that the max wait time of broadcast is 10s (see
// {@ShutdownThread#MAX_BROADCAST_TIMEMAX_BROADCAST_TIME}) currently so
// this can be optional only if we have message delay plus processing
// time significant smaller to prevent data loss.
for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) {
final int userId = entry.getKey();
sendUserMessage(userId, WRITE_SETTINGS,
Intent.ACTION_SHUTDOWN, 0 /*delayMillis*/);
sendUserMessage(userId,
WRITE_GAME_MODE_INTERVENTION_LIST_FILE, Intent.ACTION_SHUTDOWN,
0 /*delayMillis*/);
}
}
}
}
}, new IntentFilter(Intent.ACTION_SHUTDOWN));
}
void onUserStarting(@NonNull TargetUser user) {
final int userId = user.getUserIdentifier();
private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
Message msg = mHandler.obtainMessage(what, userId);
if (!mHandler.sendMessageDelayed(msg, delayMillis)) {
Slog.e(TAG, "Failed to send user message " + what + " on " + eventForLog);
}
}
void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
final int userId = user.getUserIdentifier();
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
GameManagerSettings userSettings =
new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
mSettings.put(userId, userSettings);
userSettings.readPersistentDataLocked();
}
}
final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
msg.obj = userId;
mHandler.sendMessage(msg);
sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_STARTING", 0 /*delayMillis*/);
if (mGameServiceController != null) {
mGameServiceController.notifyUserStarted(user);
@@ -1243,9 +1263,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (!mSettings.containsKey(userId)) {
return;
}
final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
msg.obj = userId;
mHandler.sendMessage(msg);
sendUserMessage(userId, REMOVE_SETTINGS, "ON_USER_STOPPING", 0 /*delayMillis*/);
}
if (mGameServiceController != null) {
@@ -1259,15 +1277,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
synchronized (mLock) {
final int fromUserId = from.getUserIdentifier();
if (mSettings.containsKey(fromUserId)) {
final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
msg.obj = fromUserId;
mHandler.sendMessage(msg);
sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
0 /*delayMillis*/);
}
}
}
final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
msg.obj = toUserId;
mHandler.sendMessage(msg);
sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
0 /*delayMillis*/);
if (mGameServiceController != null) {
mGameServiceController.notifyNewForegroundUser(to);
@@ -1539,15 +1556,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
updateInterventions(packageName, gameMode, userId);
}
}
sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
"UPDATE_CONFIGS_FOR_USERS", 0 /*delayMillis*/);
} catch (Exception e) {
Slog.e(TAG, "Failed to update configs for user " + userId + ": " + e);
}
final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
msg.obj = userId;
if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
mHandler.sendMessage(msg);
}
}
/*
@@ -1700,18 +1713,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (mSettings.containsKey(userId)) {
mSettings.get(userId).removeGame(packageName);
}
Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
msg.obj = userId;
if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) {
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
msg = mHandler.obtainMessage(
WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
msg.obj = userId;
if (!mHandler.hasEqualMessages(
WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
mHandler.sendMessage(msg);
}
sendUserMessage(userId, WRITE_SETTINGS,
Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
sendUserMessage(userId,
WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
}
}
break;

View File

@@ -17,8 +17,10 @@
package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -35,11 +37,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.annotation.Nullable;
import android.app.GameManager;
import android.app.GameModeInfo;
import android.app.GameState;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -97,6 +103,7 @@ public class GameManagerServiceTests {
private PowerManagerInternal mMockPowerManager;
@Mock
private UserManager mMockUserManager;
private BroadcastReceiver mShutDownActionReceiver;
// Stolen from ConnectivityServiceTest.MockContext
class MockContext extends ContextWrapper {
@@ -165,6 +172,12 @@ public class GameManagerServiceTests {
}
throw new UnsupportedOperationException("Couldn't find system service: " + name);
}
@Override
public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
mShutDownActionReceiver = receiver;
return null;
}
}
@Before
@@ -200,15 +213,16 @@ public class GameManagerServiceTests {
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PowerManagerInternal.class);
GameManagerService gameManagerService = new GameManagerService(mMockContext);
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
private void startUser(GameManagerService gameManagerService, int userId) {
UserInfo userInfo = new UserInfo(userId, "name", 0);
gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo));
gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo),
InstrumentationRegistry.getContext().getFilesDir());
mTestLooper.dispatchAll();
}
@@ -1508,4 +1522,42 @@ public class GameManagerServiceTests {
GameManager.GAME_MODE_BATTERY, USER_ID_1);
});
}
@Test
public void testWritingSettingFile_onShutdown() throws InterruptedException {
mockModifyGameModeGranted();
mockDeviceConfigAll();
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onBootCompleted();
startUser(gameManagerService, USER_ID_1);
Thread.sleep(500);
gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
GameManager.GAME_MODE_BATTERY, "60", "0.5");
gameManagerService.setGameMode("com.android.app1", USER_ID_1,
GameManager.GAME_MODE_PERFORMANCE);
GameManagerSettings settings = new GameManagerSettings(
InstrumentationRegistry.getContext().getFilesDir());
Thread.sleep(500);
// no data written as delayed messages are queued
assertFalse(settings.readPersistentDataLocked());
assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
Intent shutdown = new Intent();
shutdown.setAction(Intent.ACTION_SHUTDOWN);
mShutDownActionReceiver.onReceive(mMockContext, shutdown);
Thread.sleep(500);
// data is written on processing new message with no delay on shutdown,
// and all queued messages should be removed
assertTrue(settings.readPersistentDataLocked());
assertFalse(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
}
private static void deleteFolder(File folder) {
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
deleteFolder(file);
}
}
folder.delete();
}
}