Merge "Add GameManager service." into sc-dev
This commit is contained in:
@@ -72,6 +72,7 @@ import android.content.res.Resources;
|
||||
import android.content.rollback.RollbackManagerFrameworkInitializer;
|
||||
import android.debug.AdbManager;
|
||||
import android.debug.IAdbManager;
|
||||
import android.graphics.GameManager;
|
||||
import android.graphics.fonts.FontManager;
|
||||
import android.hardware.ConsumerIrManager;
|
||||
import android.hardware.ISerialManager;
|
||||
@@ -1426,6 +1427,16 @@ public final class SystemServiceRegistry {
|
||||
return new MediaMetricsManager(service, ctx.getUserId());
|
||||
}});
|
||||
|
||||
registerService(Context.GAME_SERVICE, GameManager.class,
|
||||
new CachedServiceFetcher<GameManager>() {
|
||||
@Override
|
||||
public GameManager createService(ContextImpl ctx)
|
||||
throws ServiceNotFoundException {
|
||||
return new GameManager(ctx.getOuterContext(),
|
||||
ctx.mMainThread.getHandler());
|
||||
}
|
||||
});
|
||||
|
||||
sInitializing = true;
|
||||
try {
|
||||
// Note: the following functions need to be @SystemApis, once they become mainline
|
||||
|
||||
@@ -5417,6 +5417,16 @@ public abstract class Context {
|
||||
*/
|
||||
public static final String SPEECH_RECOGNITION_SERVICE = "speech_recognition";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve a
|
||||
* {@link android.graphics.GameManager}.
|
||||
*
|
||||
* @see #getSystemService(String)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String GAME_SERVICE = "game";
|
||||
|
||||
/**
|
||||
* Determine whether the given permission is allowed for a particular
|
||||
* process and user ID running in the system.
|
||||
|
||||
29
core/tests/GameManagerTests/Android.bp
Normal file
29
core/tests/GameManagerTests/Android.bp
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2021 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.
|
||||
|
||||
android_test {
|
||||
name: "FrameworksCoreGameManagerTests",
|
||||
// Include all test java files
|
||||
srcs: ["src/**/*.java"],
|
||||
static_libs: [
|
||||
"androidx.test.rules",
|
||||
"frameworks-base-testutils",
|
||||
"junit",
|
||||
"truth-prebuilt",
|
||||
],
|
||||
libs: ["android.test.runner"],
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
test_suites: ["device-tests"],
|
||||
}
|
||||
30
core/tests/GameManagerTests/AndroidManifest.xml
Normal file
30
core/tests/GameManagerTests/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.graphics.gamemanagertests"
|
||||
android:sharedUserId="android.uid.system" >
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.graphics.gamemanagertests"
|
||||
android:label="Game Manager Tests"/>
|
||||
|
||||
</manifest>
|
||||
32
core/tests/GameManagerTests/AndroidTest.xml
Normal file
32
core/tests/GameManagerTests/AndroidTest.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 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.
|
||||
-->
|
||||
|
||||
<configuration description="Runs Game Manager Tests.">
|
||||
<option name="test-suite-tag" value="apct"/>
|
||||
<option name="test-suite-tag" value="apct-instrumentation"/>
|
||||
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="test-file-name" value="FrameworksCoreGameManagerTests.apk" />
|
||||
</target_preparer>
|
||||
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="com.android.graphics.gamemanagertests" />
|
||||
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
|
||||
<option name="hidden-api-checks" value="false" />
|
||||
</test>
|
||||
</configuration>
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.graphics;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import android.graphics.GameManager.GameMode;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GameManager}.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public final class GameManagerTests {
|
||||
private static final String PACKAGE_NAME_0 = "com.android.app0";
|
||||
private static final String PACKAGE_NAME_1 = "com.android.app1";
|
||||
|
||||
private TestGameManagerService mService;
|
||||
private GameManager mGameManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mService = new TestGameManagerService();
|
||||
mGameManager = new GameManager(
|
||||
InstrumentationRegistry.getContext(), mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameModeGetterSetter() {
|
||||
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
|
||||
mGameManager.getGameMode(PACKAGE_NAME_0));
|
||||
|
||||
mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD);
|
||||
assertEquals(GameManager.GAME_MODE_STANDARD,
|
||||
mGameManager.getGameMode(PACKAGE_NAME_1));
|
||||
|
||||
mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE);
|
||||
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
|
||||
mGameManager.getGameMode(PACKAGE_NAME_1));
|
||||
}
|
||||
|
||||
private final class TestGameManagerService extends IGameManagerService.Stub {
|
||||
private final ArrayMap<Pair<String, Integer>, Integer> mGameModes = new ArrayMap<>();
|
||||
|
||||
@Override
|
||||
public @GameMode int getGameMode(String packageName, int userId) {
|
||||
final Pair key = Pair.create(packageName, userId);
|
||||
if (mGameModes.containsKey(key)) {
|
||||
return mGameModes.get(key);
|
||||
}
|
||||
return GameManager.GAME_MODE_UNSUPPORTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
|
||||
mGameModes.put(Pair.create(packageName, userId), gameMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
98
graphics/java/android/graphics/GameManager.java
Normal file
98
graphics/java/android/graphics/GameManager.java
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.graphics;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.SystemService;
|
||||
import android.annotation.UserHandleAware;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The GameManager allows system apps to modify and query the game mode of apps.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
@SystemService(Context.GAME_SERVICE)
|
||||
public final class GameManager {
|
||||
|
||||
private static final String TAG = "GameManager";
|
||||
|
||||
private final Context mContext;
|
||||
private final IGameManagerService mService;
|
||||
|
||||
@IntDef(flag = false, prefix = { "GAME_MODE_" }, value = {
|
||||
GAME_MODE_UNSUPPORTED, // 0
|
||||
GAME_MODE_STANDARD, // 1
|
||||
GAME_MODE_PERFORMANCE, // 2
|
||||
GAME_MODE_BATTERY, // 3
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface GameMode {}
|
||||
|
||||
public static final int GAME_MODE_UNSUPPORTED = 0;
|
||||
public static final int GAME_MODE_STANDARD = 1;
|
||||
public static final int GAME_MODE_PERFORMANCE = 2;
|
||||
public static final int GAME_MODE_BATTERY = 3;
|
||||
|
||||
public GameManager(Context context, Handler handler) throws ServiceNotFoundException {
|
||||
mContext = context;
|
||||
mService = IGameManagerService.Stub.asInterface(
|
||||
ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public GameManager(Context context, IGameManagerService gameManagerService) {
|
||||
mContext = context;
|
||||
mService = gameManagerService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the game mode for the given package.
|
||||
*/
|
||||
// TODO(b/178111358): Add @RequiresPermission.
|
||||
@UserHandleAware
|
||||
public @GameMode int getGameMode(String packageName) {
|
||||
try {
|
||||
return mService.getGameMode(packageName, mContext.getUserId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the game mode for the given package.
|
||||
*/
|
||||
// TODO(b/178111358): Add @RequiresPermission.
|
||||
@UserHandleAware
|
||||
public void setGameMode(String packageName, @GameMode int gameMode) {
|
||||
try {
|
||||
mService.setGameMode(packageName, gameMode, mContext.getUserId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
graphics/java/android/graphics/IGameManagerService.aidl
Normal file
25
graphics/java/android/graphics/IGameManagerService.aidl
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
** Copyright 2021, 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.graphics;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
interface IGameManagerService {
|
||||
int getGameMode(String packageName, int userId);
|
||||
void setGameMode(String packageName, int gameMode, int userId);
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.graphics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.graphics.GameManager;
|
||||
import android.graphics.GameManager.GameMode;
|
||||
import android.graphics.IGameManagerService;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
/**
|
||||
* Service to manage game related features.
|
||||
*
|
||||
* <p>Game service is a core service that monitors, coordinates game related features,
|
||||
* as well as collect metrics.</p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class GameManagerService extends IGameManagerService.Stub {
|
||||
public static final String TAG = "GameManagerService";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
static final int WRITE_SETTINGS = 1;
|
||||
static final int REMOVE_SETTINGS = 2;
|
||||
static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds
|
||||
|
||||
private final Context mContext;
|
||||
private final Object mLock = new Object();
|
||||
private final Handler mHandler;
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Integer, Settings> mSettings = new ArrayMap<>();
|
||||
|
||||
public GameManagerService(Context context) {
|
||||
this(context, createServiceThread().getLooper());
|
||||
}
|
||||
|
||||
GameManagerService(Context context, Looper looper) {
|
||||
mContext = context;
|
||||
mHandler = new SettingsHandler(looper);
|
||||
}
|
||||
|
||||
class SettingsHandler extends Handler {
|
||||
|
||||
SettingsHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
doHandleMessage(msg);
|
||||
}
|
||||
|
||||
void doHandleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case WRITE_SETTINGS: {
|
||||
final int userId = (int) msg.obj;
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
|
||||
synchronized (mLock) {
|
||||
removeMessages(WRITE_SETTINGS, msg.obj);
|
||||
if (mSettings.containsKey(userId)) {
|
||||
Settings userSettings = mSettings.get(userId);
|
||||
userSettings.writePersistentDataLocked();
|
||||
}
|
||||
}
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
break;
|
||||
}
|
||||
case REMOVE_SETTINGS: {
|
||||
final int userId = (int) msg.obj;
|
||||
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);
|
||||
if (mSettings.containsKey(userId)) {
|
||||
final Settings userSettings = mSettings.get(userId);
|
||||
mSettings.remove(userId);
|
||||
userSettings.writePersistentDataLocked();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemService lifecycle for GameService.
|
||||
* @hide
|
||||
*/
|
||||
public static class Lifecycle extends SystemService {
|
||||
private GameManagerService mService;
|
||||
|
||||
public Lifecycle(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
mService = new GameManagerService(getContext());
|
||||
publishBinderService(Context.GAME_SERVICE, mService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (phase == PHASE_BOOT_COMPLETED) {
|
||||
mService.onBootCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserStarting(@NonNull TargetUser user) {
|
||||
mService.onUserStarting(user.getUserIdentifier());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserStopping(@NonNull TargetUser user) {
|
||||
mService.onUserStopping(user.getUserIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
//TODO(b/178111358) Add proper permission check and multi-user handling
|
||||
@Override
|
||||
public @GameMode int getGameMode(String packageName, int userId) {
|
||||
synchronized (mLock) {
|
||||
if (!mSettings.containsKey(userId)) {
|
||||
return GameManager.GAME_MODE_UNSUPPORTED;
|
||||
}
|
||||
Settings userSettings = mSettings.get(userId);
|
||||
return userSettings.getGameModeLocked(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO(b/178111358) Add proper permission check and multi-user handling
|
||||
@Override
|
||||
public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
|
||||
synchronized (mLock) {
|
||||
if (!mSettings.containsKey(userId)) {
|
||||
return;
|
||||
}
|
||||
Settings 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notified when boot is completed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void onBootCompleted() {
|
||||
Slog.d(TAG, "onBootCompleted");
|
||||
}
|
||||
|
||||
void onUserStarting(int userId) {
|
||||
synchronized (mLock) {
|
||||
if (mSettings.containsKey(userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings userSettings = new Settings(Environment.getDataSystemDeDirectory(userId));
|
||||
mSettings.put(userId, userSettings);
|
||||
userSettings.readPersistentDataLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void onUserStopping(int userId) {
|
||||
synchronized (mLock) {
|
||||
if (!mSettings.containsKey(userId)) {
|
||||
return;
|
||||
}
|
||||
final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
|
||||
msg.obj = userId;
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static ServiceThread createServiceThread() {
|
||||
ServiceThread handlerThread = new ServiceThread(TAG,
|
||||
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
|
||||
handlerThread.start();
|
||||
return handlerThread;
|
||||
}
|
||||
}
|
||||
193
services/core/java/com/android/server/graphics/Settings.java
Normal file
193
services/core/java/com/android/server/graphics/Settings.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.graphics;
|
||||
|
||||
import android.graphics.GameManager;
|
||||
import android.os.FileUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Persists all GameService related settings.
|
||||
* @hide
|
||||
*/
|
||||
public class Settings {
|
||||
|
||||
// The XML file follows the below format:
|
||||
// <?xml>
|
||||
// <packages>
|
||||
// <package></package>
|
||||
// ...
|
||||
// </packages>
|
||||
private static final String GAME_SERVICE_FILE_NAME = "game-manager-service.xml";
|
||||
|
||||
private static final String TAG_PACKAGE = "package";
|
||||
private static final String TAG_PACKAGES = "packages";
|
||||
private static final String ATTR_NAME = "name";
|
||||
private static final String ATTR_GAME_MODE = "gameMode";
|
||||
|
||||
private final File mSystemDir;
|
||||
@VisibleForTesting
|
||||
final AtomicFile mSettingsFile;
|
||||
|
||||
// PackageName -> GameMode
|
||||
private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
|
||||
|
||||
Settings(File dataDir) {
|
||||
mSystemDir = new File(dataDir, "system");
|
||||
mSystemDir.mkdirs();
|
||||
FileUtils.setPermissions(mSystemDir.toString(),
|
||||
FileUtils.S_IRWXU | FileUtils.S_IRWXG
|
||||
| FileUtils.S_IROTH | FileUtils.S_IXOTH,
|
||||
-1, -1);
|
||||
mSettingsFile = new AtomicFile(new File(mSystemDir, GAME_SERVICE_FILE_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the game mode of a given package.
|
||||
* This operation must be synced with an external lock.
|
||||
*/
|
||||
int getGameModeLocked(String packageName) {
|
||||
if (mGameModes.containsKey(packageName)) {
|
||||
return mGameModes.get(packageName);
|
||||
}
|
||||
return GameManager.GAME_MODE_UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the game mode of a given package.
|
||||
* This operation must be synced with an external lock.
|
||||
*/
|
||||
void setGameModeLocked(String packageName, int gameMode) {
|
||||
mGameModes.put(packageName, gameMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all current game service settings into disk.
|
||||
* This operation must be synced with an external lock.
|
||||
*/
|
||||
void writePersistentDataLocked() {
|
||||
FileOutputStream fstr = null;
|
||||
try {
|
||||
fstr = mSettingsFile.startWrite();
|
||||
|
||||
final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
|
||||
serializer.startDocument(null, true);
|
||||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
|
||||
serializer.startTag(null, TAG_PACKAGES);
|
||||
for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
|
||||
serializer.startTag(null, TAG_PACKAGE);
|
||||
serializer.attribute(null, ATTR_NAME, entry.getKey());
|
||||
serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
|
||||
serializer.endTag(null, TAG_PACKAGE);
|
||||
}
|
||||
serializer.endTag(null, TAG_PACKAGES);
|
||||
|
||||
serializer.endDocument();
|
||||
|
||||
mSettingsFile.finishWrite(fstr);
|
||||
|
||||
FileUtils.setPermissions(mSettingsFile.toString(),
|
||||
FileUtils.S_IRUSR | FileUtils.S_IWUSR
|
||||
| FileUtils.S_IRGRP | FileUtils.S_IWGRP,
|
||||
-1, -1);
|
||||
return;
|
||||
} catch (java.io.IOException e) {
|
||||
mSettingsFile.failWrite(fstr);
|
||||
Slog.wtf(GameManagerService.TAG, "Unable to write game manager service settings, "
|
||||
+ "current changes will be lost at reboot", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read game service settings from the disk.
|
||||
* This operation must be synced with an external lock.
|
||||
*/
|
||||
boolean readPersistentDataLocked() {
|
||||
mGameModes.clear();
|
||||
|
||||
try {
|
||||
final FileInputStream str = mSettingsFile.openRead();
|
||||
|
||||
final TypedXmlPullParser parser = Xml.resolvePullParser(str);
|
||||
int type;
|
||||
while ((type = parser.next()) != XmlPullParser.START_TAG
|
||||
&& type != XmlPullParser.END_DOCUMENT) {
|
||||
}
|
||||
if (type != XmlPullParser.START_TAG) {
|
||||
Slog.wtf(GameManagerService.TAG,
|
||||
"No start tag found in package manager settings");
|
||||
return false;
|
||||
}
|
||||
|
||||
int outerDepth = parser.getDepth();
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
|
||||
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String tagName = parser.getName();
|
||||
if (tagName.equals(TAG_PACKAGE)) {
|
||||
readPackage(parser);
|
||||
} else {
|
||||
Slog.w(GameManagerService.TAG, "Unknown element: " + parser.getName());
|
||||
XmlUtils.skipCurrentTag(parser);
|
||||
}
|
||||
}
|
||||
} catch (XmlPullParserException | java.io.IOException e) {
|
||||
Slog.wtf(GameManagerService.TAG, "Error reading package manager settings", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
|
||||
IOException {
|
||||
String name = null;
|
||||
int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
|
||||
try {
|
||||
name = parser.getAttributeValue(null, ATTR_NAME);
|
||||
gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
|
||||
} catch (XmlPullParserException e) {
|
||||
Slog.wtf(GameManagerService.TAG, "Error reading game mode", e);
|
||||
}
|
||||
if (name != null) {
|
||||
mGameModes.put(name, gameMode);
|
||||
} else {
|
||||
XmlUtils.skipCurrentTag(parser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,6 +360,8 @@ public final class SystemServer implements Dumpable {
|
||||
private static final String IP_CONNECTIVITY_METRICS_CLASS =
|
||||
"com.android.server.connectivity.IpConnectivityMetrics";
|
||||
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
|
||||
private static final String GAME_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.graphics.GameManagerService$Lifecycle";
|
||||
|
||||
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
|
||||
|
||||
@@ -2514,6 +2516,10 @@ public final class SystemServer implements Dumpable {
|
||||
}
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("GameManagerService");
|
||||
mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("StartBootPhaseDeviceSpecificServicesReady");
|
||||
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY);
|
||||
t.traceEnd();
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.graphics;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class GameManagerServiceSettingsTests {
|
||||
|
||||
private static final String TAG = "GameServiceSettingsTests";
|
||||
private static final String PACKAGE_NAME_1 = "com.android.app1";
|
||||
private static final String PACKAGE_NAME_2 = "com.android.app2";
|
||||
private static final String PACKAGE_NAME_3 = "com.android.app3";
|
||||
|
||||
private void writeFile(File file, byte[] data) {
|
||||
file.mkdirs();
|
||||
try {
|
||||
AtomicFile aFile = new AtomicFile(file);
|
||||
FileOutputStream fos = aFile.startWrite();
|
||||
fos.write(data);
|
||||
aFile.finishWrite(fos);
|
||||
} catch (IOException ioe) {
|
||||
Log.e(TAG, "Cannot write file " + file.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGameServiceXml() {
|
||||
writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
|
||||
"system/game-manager-service.xml"),
|
||||
("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
|
||||
+ "<packages>\n"
|
||||
+ " <package name=\"com.android.app1\" gameMode=\"1\">\n"
|
||||
+ " </package>\n"
|
||||
+ " <package name=\"com.android.app2\" gameMode=\"2\">\n"
|
||||
+ " </package>\n"
|
||||
+ " <package name=\"com.android.app3\" gameMode=\"3\">\n"
|
||||
+ " </package>\n"
|
||||
+ "</packages>\n").getBytes());
|
||||
}
|
||||
|
||||
private void deleteSystemFolder() {
|
||||
File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system");
|
||||
deleteFolder(systemFolder);
|
||||
}
|
||||
|
||||
private static void deleteFolder(File folder) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
deleteFolder(file);
|
||||
}
|
||||
}
|
||||
folder.delete();
|
||||
}
|
||||
|
||||
private void writeOldFiles() {
|
||||
deleteSystemFolder();
|
||||
writeGameServiceXml();
|
||||
}
|
||||
|
||||
private void verifyGameServiceSettingsData(Settings settings) {
|
||||
assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
|
||||
assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
|
||||
assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
|
||||
}
|
||||
|
||||
/** read in data and verify */
|
||||
@Test
|
||||
public void testReadGameServiceSettings() {
|
||||
/* write out files and read */
|
||||
writeOldFiles();
|
||||
final Context context = InstrumentationRegistry.getContext();
|
||||
Settings settings = new Settings(context.getFilesDir());
|
||||
assertThat(settings.readPersistentDataLocked(), is(true));
|
||||
verifyGameServiceSettingsData(settings);
|
||||
}
|
||||
|
||||
/** read in data, write it out, and read it back in. Verify same. */
|
||||
@Test
|
||||
public void testWriteGameServiceSettings() {
|
||||
// write out files and read
|
||||
writeOldFiles();
|
||||
final Context context = InstrumentationRegistry.getContext();
|
||||
Settings settings = new Settings(context.getFilesDir());
|
||||
assertThat(settings.readPersistentDataLocked(), is(true));
|
||||
|
||||
// write out, read back in and verify the same
|
||||
settings.writePersistentDataLocked();
|
||||
assertThat(settings.readPersistentDataLocked(), is(true));
|
||||
verifyGameServiceSettingsData(settings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.graphics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.graphics.GameManager;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class GameManagerServiceTests {
|
||||
|
||||
private static final String TAG = "GameServiceTests";
|
||||
private static final String PACKAGE_NAME_0 = "com.android.app0";
|
||||
private static final String PACKAGE_NAME_1 = "com.android.app1";
|
||||
private static final int USER_ID_1 = 1001;
|
||||
private static final int USER_ID_2 = 1002;
|
||||
|
||||
/**
|
||||
* By default game mode is not supported.
|
||||
*/
|
||||
@Test
|
||||
public void testGameModeDefaultValue() {
|
||||
GameManagerService gameManagerService =
|
||||
new GameManagerService(InstrumentationRegistry.getContext());
|
||||
gameManagerService.onUserStarting(USER_ID_1);
|
||||
|
||||
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
|
||||
gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the default behaviour for a nonexistent user.
|
||||
*/
|
||||
@Test
|
||||
public void testDefaultValueForNonexistentUser() {
|
||||
GameManagerService gameManagerService =
|
||||
new GameManagerService(InstrumentationRegistry.getContext());
|
||||
gameManagerService.onUserStarting(USER_ID_1);
|
||||
|
||||
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2);
|
||||
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
|
||||
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getter and setter of game modes.
|
||||
*/
|
||||
@Test
|
||||
public void testGameMode() {
|
||||
GameManagerService gameManagerService =
|
||||
new GameManagerService(InstrumentationRegistry.getContext());
|
||||
gameManagerService.onUserStarting(USER_ID_1);
|
||||
|
||||
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
|
||||
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
|
||||
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
|
||||
assertEquals(GameManager.GAME_MODE_STANDARD,
|
||||
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
|
||||
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE,
|
||||
USER_ID_1);
|
||||
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
|
||||
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user