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

Commit 8b9be354 authored by Xiang Wang's avatar Xiang Wang
Browse files

Add IGameModeListener and methods to add/remove in GameManagerService

Bug: b/243448953
Test: atest GameManagerServiceTests
Change-Id: I5e286ee46d118fe2e88782bc7751f4ec650323b3
parent db78635e
Loading
Loading
Loading
Loading
+14 −0
Original line number Original line Diff line number Diff line
@@ -19,20 +19,34 @@ package android.app;
import android.app.GameModeConfiguration;
import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.GameState;
import android.app.IGameModeListener;


/**
/**
 * @hide
 * @hide
 */
 */
interface IGameManagerService {
interface IGameManagerService {
    int getGameMode(String packageName, int userId);
    int getGameMode(String packageName, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void setGameMode(String packageName, int gameMode, int userId);
    void setGameMode(String packageName, int gameMode, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    int[] getAvailableGameModes(String packageName);
    int[] getAvailableGameModes(String packageName);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    boolean isAngleEnabled(String packageName, int userId);
    boolean isAngleEnabled(String packageName, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void notifyGraphicsEnvironmentSetup(String packageName, int userId);
    void notifyGraphicsEnvironmentSetup(String packageName, int userId);
    void setGameState(String packageName, in GameState gameState, int userId);
    void setGameState(String packageName, in GameState gameState, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    GameModeInfo getGameModeInfo(String packageName, int userId);
    GameModeInfo getGameModeInfo(String packageName, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_GAME_SERVICE)")
    void setGameServiceProvider(String packageName);
    void setGameServiceProvider(String packageName);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, int userId);
    void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    float getResolutionScalingFactor(String packageName, int gameMode, int userId);
    float getResolutionScalingFactor(String packageName, int gameMode, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void updateCustomGameModeConfiguration(String packageName, in GameModeConfiguration gameModeConfig, int userId);
    void updateCustomGameModeConfiguration(String packageName, in GameModeConfiguration gameModeConfig, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void addGameModeListener(IGameModeListener gameModeListener);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void removeGameModeListener(IGameModeListener gameModeListener);
}
}
+25 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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;

/** @hide */
interface IGameModeListener {
    /**
     * Called when the game mode of the user has changed.
     */
    void onGameModeChanged(String packageName, int gameModeFrom, int gameModeTo, int userId);
}
+71 −1
Original line number Original line Diff line number Diff line
@@ -39,6 +39,7 @@ import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
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.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
@@ -58,10 +59,12 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Environment;
import android.os.FileUtils;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCallback;
import android.os.UserManager;
import android.os.UserManager;
@@ -132,6 +135,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final Context mContext;
    private final Context mContext;
    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();
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    final Handler mHandler;
    final Handler mHandler;
    private final PackageManager mPackageManager;
    private final PackageManager mPackageManager;
@@ -145,6 +149,9 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
    private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
    @GuardedBy("mDeviceConfigLock")
    @GuardedBy("mDeviceConfigLock")
    private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
    private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
    // listener to caller uid map
    @GuardedBy("mGameModeListenerLock")
    private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
    @Nullable
    @Nullable
    private final GameServiceController mGameServiceController;
    private final GameServiceController mGameServiceController;


@@ -1102,7 +1109,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
            // Restrict to games and valid game modes only.
            // Restrict to games and valid game modes only.
            return;
            return;
        }
        }

        int fromGameMode;
        synchronized (mLock) {
        synchronized (mLock) {
            userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
            userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                    Binder.getCallingUid(), userId, false, true, "setGameMode",
                    Binder.getCallingUid(), userId, false, true, "setGameMode",
@@ -1114,9 +1121,21 @@ public final class GameManagerService extends IGameManagerService.Stub {
                return;
                return;
            }
            }
            GameManagerSettings userSettings = mSettings.get(userId);
            GameManagerSettings userSettings = mSettings.get(userId);
            fromGameMode = userSettings.getGameModeLocked(packageName);
            userSettings.setGameModeLocked(packageName, gameMode);
            userSettings.setGameModeLocked(packageName, gameMode);
        }
        }
        updateInterventions(packageName, gameMode, userId);
        updateInterventions(packageName, gameMode, userId);
        synchronized (mGameModeListenerLock) {
            for (IGameModeListener listener : mGameModeListeners.keySet()) {
                Binder.allowBlocking(listener.asBinder());
                try {
                    listener.onGameModeChanged(packageName, fromGameMode, gameMode, userId);
                } catch (RemoteException ex) {
                    Slog.w(TAG, "Cannot notify game mode change for listener added by "
                            + mGameModeListeners.get(listener));
                }
            }
        }
        sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
        sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
        sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
        sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
                "SET_GAME_MODE", 0 /*delayMillis*/);
                "SET_GAME_MODE", 0 /*delayMillis*/);
@@ -1341,6 +1360,57 @@ public final class GameManagerService extends IGameManagerService.Stub {
                + internalConfig.getScaling());
                + internalConfig.getScaling());
    }
    }


    /**
     * Adds a game mode listener.
     *
     * @throws SecurityException if caller doesn't have
     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
     *                           permission.
     */
    @Override
    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
    public void addGameModeListener(@NonNull IGameModeListener listener) {
        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
        try {
            final IBinder listenerBinder = listener.asBinder();
            listenerBinder.linkToDeath(new DeathRecipient() {
                @Override public void binderDied() {
                    // TODO(b/258851194): add traces on binder death based listener removal
                    removeGameModeListenerUnchecked(listener);
                    listenerBinder.unlinkToDeath(this, 0 /*flags*/);
                }
            }, 0 /*flags*/);
            synchronized (mGameModeListenerLock) {
                mGameModeListeners.put(listener, Binder.getCallingUid());
            }
        } catch (RemoteException ex) {
            Slog.e(TAG,
                    "Failed to link death recipient for IGameModeListener from caller "
                            + Binder.getCallingUid() + ", abandoned its listener registration", ex);
        }
    }

    /**
     * Removes a game mode listener.
     *
     * @throws SecurityException if caller doesn't have
     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
     *                           permission.
     */
    @Override
    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
    public void removeGameModeListener(@NonNull IGameModeListener listener) {
        // TODO(b/258851194): add traces on manual listener removal
        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
        removeGameModeListenerUnchecked(listener);
    }

    private void removeGameModeListenerUnchecked(IGameModeListener listener) {
        synchronized (mGameModeListenerLock) {
            mGameModeListeners.remove(listener);
        }
    }

    /**
    /**
     * Notified when boot is completed.
     * Notified when boot is completed.
     */
     */
+56 −0
Original line number Original line Diff line number Diff line
@@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;
@@ -43,6 +44,7 @@ import android.app.GameManager;
import android.app.GameModeConfiguration;
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.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.ContextWrapper;
@@ -57,7 +59,9 @@ import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.content.res.XmlResourceParser;
import android.hardware.power.Mode;
import android.hardware.power.Mode;
import android.os.Bundle;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.UserManager;
import android.os.test.TestLooper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
@@ -74,7 +78,9 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Before;
import org.junit.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.MockitoSession;
@@ -110,6 +116,9 @@ public class GameManagerServiceTests {
    private UserManager mMockUserManager;
    private UserManager mMockUserManager;
    private BroadcastReceiver mShutDownActionReceiver;
    private BroadcastReceiver mShutDownActionReceiver;


    @Captor
    ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;

    // Stolen from ConnectivityServiceTest.MockContext
    // Stolen from ConnectivityServiceTest.MockContext
    class MockContext extends ContextWrapper {
    class MockContext extends ContextWrapper {
        private static final String TAG = "MockContext";
        private static final String TAG = "MockContext";
@@ -1874,6 +1883,53 @@ public class GameManagerServiceTests {
                ArgumentMatchers.eq(0.0f));
                ArgumentMatchers.eq(0.0f));
    }
    }


    @Test
    public void testAddGameModeListener() throws RemoteException {
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        mockDeviceConfigAll();
        startUser(gameManagerService, USER_ID_1);
        mockModifyGameModeGranted();

        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
        IBinder binder = Mockito.mock(IBinder.class);
        when(mockListener.asBinder()).thenReturn(binder);
        gameManagerService.addGameModeListener(mockListener);
        verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());

        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        reset(mockListener);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_BATTERY, USER_ID_1);
        reset(mockListener);

        mDeathRecipientCaptor.getValue().binderDied();
        verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_CUSTOM, USER_ID_1);
        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
    }

    @Test
    public void testRemoveGameModeListener() throws RemoteException {
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        mockDeviceConfigAll();
        startUser(gameManagerService, USER_ID_1);
        mockModifyGameModeGranted();

        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
        IBinder binder = Mockito.mock(IBinder.class);
        when(mockListener.asBinder()).thenReturn(binder);

        gameManagerService.addGameModeListener(mockListener);
        gameManagerService.removeGameModeListener(mockListener);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
    }

    private static void deleteFolder(File folder) {
    private static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        File[] files = folder.listFiles();
        if (files != null) {
        if (files != null) {