Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 117debcb authored by Xiang Wang's avatar Xiang Wang
Browse files

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
parent 4b3c928d
Loading
Loading
Loading
Loading
+62 −56
Original line number Diff line number Diff line
@@ -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;
+54 −2
Original line number Diff line number Diff line
@@ -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();
    }
}