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

Commit 0f5e540c authored by shannonchen's avatar shannonchen Committed by Shannon Chen
Browse files

Reevaluate game service when provider changes

The goal is to have the system server be responsive
to changes in the game service provider package
and reevaluate the GameService and GameSessionService
accordingly. So, for example, if the GameService
component is disabled and then becomes enabled,
the system server should detect that and bind
to the newly enabled GameService. This is a
likely real-world scenario if a game service
provider uses a server-side flag to enable
or disable the GameService component after
install time.

This change also fixes an issue where
GameService#disconnect() would never be called
because the service connector was always unbound
before the posted disconnect() call could complete.

Bug: 217215722
Test: atest CtsGameServiceTestCases GameServiceControllerTest GameServiceProviderSelectorImplTest
Change-Id: I5d0a603b98f341b956ea6abe82930fa13f4f7d0e
Merged-In: I5d0a603b98f341b956ea6abe82930fa13f4f7d0e
parent 4769e90c
Loading
Loading
Loading
Loading
+6 −5
Original line number Diff line number Diff line
@@ -159,7 +159,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
            mGameServiceController = new GameServiceController(
                    BackgroundThread.getExecutor(),
                    context, BackgroundThread.getExecutor(),
                    new GameServiceProviderSelectorImpl(
                            context.getResources(),
                            context.getPackageManager()),
@@ -376,6 +376,7 @@ public final class GameManagerService extends IGameManagerService.Stub {

    /**
     * Called by games to communicate the current state to the platform.
     *
     * @param packageName The client package name.
     * @param gameState   An object set to the current state.
     * @param userId      The user associated with this state.
+154 −0
Original line number Diff line number Diff line
/*
 * 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.app;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.UserHandle;
import android.text.TextUtils;

import java.util.Objects;

/**
 * Representation of a {@link android.service.games.GameService} provider configuration.
 */
final class GameServiceConfiguration {
    private final String mPackageName;
    @Nullable
    private final GameServiceComponentConfiguration mGameServiceComponentConfiguration;

    GameServiceConfiguration(
            @NonNull String packageName,
            @Nullable GameServiceComponentConfiguration gameServiceComponentConfiguration) {
        Objects.requireNonNull(packageName);

        mPackageName = packageName;
        mGameServiceComponentConfiguration = gameServiceComponentConfiguration;
    }

    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    @Nullable
    public GameServiceComponentConfiguration getGameServiceComponentConfiguration() {
        return mGameServiceComponentConfiguration;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (!(o instanceof GameServiceConfiguration)) {
            return false;
        }

        GameServiceConfiguration that = (GameServiceConfiguration) o;
        return TextUtils.equals(mPackageName, that.mPackageName)
                && Objects.equals(mGameServiceComponentConfiguration,
                that.mGameServiceComponentConfiguration);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mPackageName, mGameServiceComponentConfiguration);
    }

    @Override
    public String toString() {
        return "GameServiceConfiguration{"
                + "packageName="
                + mPackageName
                + ", gameServiceComponentConfiguration="
                + mGameServiceComponentConfiguration
                + '}';
    }

    static final class GameServiceComponentConfiguration {
        private final UserHandle mUserHandle;
        private final ComponentName mGameServiceComponentName;
        private final ComponentName mGameSessionServiceComponentName;

        GameServiceComponentConfiguration(
                @NonNull UserHandle userHandle, @NonNull ComponentName gameServiceComponentName,
                @NonNull ComponentName gameSessionServiceComponentName) {
            Objects.requireNonNull(userHandle);
            Objects.requireNonNull(gameServiceComponentName);
            Objects.requireNonNull(gameSessionServiceComponentName);

            mUserHandle = userHandle;
            mGameServiceComponentName = gameServiceComponentName;
            mGameSessionServiceComponentName = gameSessionServiceComponentName;
        }

        @NonNull
        public UserHandle getUserHandle() {
            return mUserHandle;
        }

        @NonNull
        public ComponentName getGameServiceComponentName() {
            return mGameServiceComponentName;
        }

        @NonNull
        public ComponentName getGameSessionServiceComponentName() {
            return mGameSessionServiceComponentName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }

            if (!(o instanceof GameServiceComponentConfiguration)) {
                return false;
            }

            GameServiceComponentConfiguration that =
                    (GameServiceComponentConfiguration) o;
            return mUserHandle.equals(that.mUserHandle) && mGameServiceComponentName.equals(
                    that.mGameServiceComponentName)
                    && mGameSessionServiceComponentName.equals(
                    that.mGameSessionServiceComponentName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mUserHandle,
                    mGameServiceComponentName,
                    mGameSessionServiceComponentName);
        }

        @Override
        public String toString() {
            return "GameServiceComponentConfiguration{"
                    + "userHandle="
                    + mUserHandle
                    + ", gameServiceComponentName="
                    + mGameServiceComponentName
                    + ", gameSessionServiceComponentName="
                    + mGameSessionServiceComponentName
                    + "}";
        }
    }
}
+85 −15
Original line number Diff line number Diff line
@@ -19,10 +19,17 @@ package com.android.server.app;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PatternMatcher;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;

import java.util.Objects;
import java.util.concurrent.Executor;
@@ -36,8 +43,8 @@ import java.util.concurrent.Executor;
final class GameServiceController {
    private static final String TAG = "GameServiceController";


    private final Object mLock = new Object();
    private final Context mContext;
    private final Executor mBackgroundExecutor;
    private final GameServiceProviderSelector mGameServiceProviderSelector;
    private final GameServiceProviderInstanceFactory mGameServiceProviderInstanceFactory;
@@ -46,18 +53,24 @@ final class GameServiceController {
    @Nullable
    private volatile String mGameServiceProviderOverride;
    @Nullable
    private BroadcastReceiver mGameServicePackageChangedReceiver;
    @Nullable
    private volatile SystemService.TargetUser mCurrentForegroundUser;
    @GuardedBy("mLock")
    @Nullable
    private volatile GameServiceProviderConfiguration mActiveGameServiceProviderConfiguration;
    private volatile GameServiceComponentConfiguration mActiveGameServiceComponentConfiguration;
    @GuardedBy("mLock")
    @Nullable
    private volatile GameServiceProviderInstance mGameServiceProviderInstance;
    @GuardedBy("mLock")
    @Nullable
    private volatile String mActiveGameServiceProviderPackage;

    GameServiceController(
            @NonNull Executor backgroundExecutor,
            @NonNull Context context, @NonNull Executor backgroundExecutor,
            @NonNull GameServiceProviderSelector gameServiceProviderSelector,
            @NonNull GameServiceProviderInstanceFactory gameServiceProviderInstanceFactory) {
        mContext = context;
        mGameServiceProviderInstanceFactory = gameServiceProviderInstanceFactory;
        mBackgroundExecutor = backgroundExecutor;
        mGameServiceProviderSelector = gameServiceProviderSelector;
@@ -139,35 +152,92 @@ final class GameServiceController {
        }

        synchronized (mLock) {
            GameServiceProviderConfiguration selectedGameServiceProviderConfiguration =
            final GameServiceConfiguration selectedGameServiceConfiguration =
                    mGameServiceProviderSelector.get(mCurrentForegroundUser,
                            mGameServiceProviderOverride);

            boolean didActiveGameServiceProviderChanged =
                    !Objects.equals(selectedGameServiceProviderConfiguration,
                            mActiveGameServiceProviderConfiguration);
            if (!didActiveGameServiceProviderChanged) {
            final String gameServicePackage =
                    selectedGameServiceConfiguration == null ? null :
                            selectedGameServiceConfiguration.getPackageName();
            final GameServiceComponentConfiguration gameServiceComponentConfiguration =
                    selectedGameServiceConfiguration == null ? null
                            : selectedGameServiceConfiguration
                                    .getGameServiceComponentConfiguration();

            evaluateGameServiceProviderPackageChangedListenerLocked(gameServicePackage);

            boolean didActiveGameServiceProviderChange =
                    !Objects.equals(gameServiceComponentConfiguration,
                            mActiveGameServiceComponentConfiguration);
            if (!didActiveGameServiceProviderChange) {
                return;
            }

            if (mGameServiceProviderInstance != null) {
                Slog.i(TAG, "Stopping Game Service provider: "
                        + mActiveGameServiceProviderConfiguration);
                        + mActiveGameServiceComponentConfiguration);
                mGameServiceProviderInstance.stop();
                mGameServiceProviderInstance = null;
            }

            mActiveGameServiceProviderConfiguration = selectedGameServiceProviderConfiguration;

            if (mActiveGameServiceProviderConfiguration == null) {
            mActiveGameServiceComponentConfiguration = gameServiceComponentConfiguration;
            if (mActiveGameServiceComponentConfiguration == null) {
                return;
            }

            Slog.i(TAG,
                    "Starting Game Service provider: " + mActiveGameServiceProviderConfiguration);
                    "Starting Game Service provider: " + mActiveGameServiceComponentConfiguration);
            mGameServiceProviderInstance =
                    mGameServiceProviderInstanceFactory.create(
                            mActiveGameServiceProviderConfiguration);
                            mActiveGameServiceComponentConfiguration);
            mGameServiceProviderInstance.start();
        }
    }

    @GuardedBy("mLock")
    private void evaluateGameServiceProviderPackageChangedListenerLocked(
            @Nullable String gameServicePackage) {
        if (TextUtils.equals(mActiveGameServiceProviderPackage, gameServicePackage)) {
            return;
        }

        if (mGameServicePackageChangedReceiver != null) {
            mContext.unregisterReceiver(mGameServicePackageChangedReceiver);
            mGameServicePackageChangedReceiver = null;
        }

        mActiveGameServiceProviderPackage = gameServicePackage;

        if (TextUtils.isEmpty(mActiveGameServiceProviderPackage)) {
            return;
        }

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        intentFilter.addDataScheme("package");
        intentFilter.addDataSchemeSpecificPart(gameServicePackage, PatternMatcher.PATTERN_LITERAL);
        mGameServicePackageChangedReceiver = new PackageChangedBroadcastReceiver(
                gameServicePackage);
        mContext.registerReceiver(
                mGameServicePackageChangedReceiver,
                intentFilter);
    }

    private final class PackageChangedBroadcastReceiver extends BroadcastReceiver {
        private final String mPackageName;

        PackageChangedBroadcastReceiver(String packageName) {
            mPackageName = packageName;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (!TextUtils.equals(intent.getData().getSchemeSpecificPart(), mPackageName)) {
                return;
            }
            mBackgroundExecutor.execute(
                    GameServiceController.this::evaluateActiveGameServiceProvider);
        }
    }
}
+0 −94
Original line number Diff line number Diff line
/*
 * 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.app;

import android.annotation.NonNull;
import android.content.ComponentName;
import android.os.UserHandle;

import java.util.Objects;

/**
 * Representation of a {@link android.service.games.GameService} provider configuration.
 */
final class GameServiceProviderConfiguration {
    private final UserHandle mUserHandle;
    private final ComponentName mGameServiceComponentName;
    private final ComponentName mGameSessionServiceComponentName;

    GameServiceProviderConfiguration(
            @NonNull UserHandle userHandle,
            @NonNull ComponentName gameServiceComponentName,
            @NonNull ComponentName gameSessionServiceComponentName) {
        Objects.requireNonNull(userHandle);
        Objects.requireNonNull(gameServiceComponentName);
        Objects.requireNonNull(gameSessionServiceComponentName);

        this.mUserHandle = userHandle;
        this.mGameServiceComponentName = gameServiceComponentName;
        this.mGameSessionServiceComponentName = gameSessionServiceComponentName;
    }

    @NonNull
    public UserHandle getUserHandle() {
        return mUserHandle;
    }

    @NonNull
    public ComponentName getGameServiceComponentName() {
        return mGameServiceComponentName;
    }

    @NonNull
    public ComponentName getGameSessionServiceComponentName() {
        return mGameSessionServiceComponentName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (!(o instanceof GameServiceProviderConfiguration)) {
            return false;
        }

        GameServiceProviderConfiguration that = (GameServiceProviderConfiguration) o;
        return mUserHandle.equals(that.mUserHandle)
                && mGameServiceComponentName.equals(that.mGameServiceComponentName)
                && mGameSessionServiceComponentName.equals(that.mGameSessionServiceComponentName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mUserHandle, mGameServiceComponentName,
                mGameSessionServiceComponentName);
    }

    @Override
    public String toString() {
        return "GameServiceProviderConfiguration{"
                + "mUserHandle="
                + mUserHandle
                + ", gameServiceComponentName="
                + mGameServiceComponentName
                + ", gameSessionServiceComponentName="
                + mGameSessionServiceComponentName
                + '}';
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -18,12 +18,13 @@ package com.android.server.app;

import android.annotation.NonNull;

import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;

/**
 * Factory for creating {@link GameServiceProviderInstance}.
 */
interface GameServiceProviderInstanceFactory {

    @NonNull
    GameServiceProviderInstance create(@NonNull
            GameServiceProviderConfiguration gameServiceProviderConfiguration);
    GameServiceProviderInstance create(@NonNull GameServiceComponentConfiguration configuration);
}
Loading