diff options
| author | 2021-01-29 23:47:19 +0000 | |
|---|---|---|
| committer | 2021-01-29 23:47:19 +0000 | |
| commit | 72036e758c2428ad8f0f635b55bfd08aed3d29a7 (patch) | |
| tree | 1aebe2ca1a7367eb720735bbc49c4d8cbac3bdcf | |
| parent | d8a9174f85f2eaddd331e00895e733b08f6cb534 (diff) | |
| parent | 32f6ff7e156820d3c06a9f9366381636825504eb (diff) | |
Merge "Add GameManager service." into sc-dev
13 files changed, 947 insertions, 0 deletions
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d5f01499793c..1498dae764a9 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -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 diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 0c50446e0a4e..6a2329e5235f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5418,6 +5418,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. * diff --git a/core/tests/GameManagerTests/Android.bp b/core/tests/GameManagerTests/Android.bp new file mode 100644 index 000000000000..e1787762e067 --- /dev/null +++ b/core/tests/GameManagerTests/Android.bp @@ -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"], +} diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml new file mode 100644 index 000000000000..da6636b7b192 --- /dev/null +++ b/core/tests/GameManagerTests/AndroidManifest.xml @@ -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> diff --git a/core/tests/GameManagerTests/AndroidTest.xml b/core/tests/GameManagerTests/AndroidTest.xml new file mode 100644 index 000000000000..bfb9802ba1ad --- /dev/null +++ b/core/tests/GameManagerTests/AndroidTest.xml @@ -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> diff --git a/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java b/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java new file mode 100644 index 000000000000..d861a894500a --- /dev/null +++ b/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java @@ -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); + } + } +} diff --git a/graphics/java/android/graphics/GameManager.java b/graphics/java/android/graphics/GameManager.java new file mode 100644 index 000000000000..a58aeb4298d6 --- /dev/null +++ b/graphics/java/android/graphics/GameManager.java @@ -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(); + } + } +} diff --git a/graphics/java/android/graphics/IGameManagerService.aidl b/graphics/java/android/graphics/IGameManagerService.aidl new file mode 100644 index 000000000000..7d3a4fba562e --- /dev/null +++ b/graphics/java/android/graphics/IGameManagerService.aidl @@ -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); +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/graphics/GameManagerService.java b/services/core/java/com/android/server/graphics/GameManagerService.java new file mode 100644 index 000000000000..876f02f38536 --- /dev/null +++ b/services/core/java/com/android/server/graphics/GameManagerService.java @@ -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; + } +} diff --git a/services/core/java/com/android/server/graphics/Settings.java b/services/core/java/com/android/server/graphics/Settings.java new file mode 100644 index 000000000000..bbd84d037d18 --- /dev/null +++ b/services/core/java/com/android/server/graphics/Settings.java @@ -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); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d28c3cc8e2b8..544eedf99c9e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -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(); diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java new file mode 100644 index 000000000000..6fc6b9eb3da3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java @@ -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); + } +} diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java new file mode 100644 index 000000000000..22df645f7619 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java @@ -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)); + } +} |