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

Commit 3d7dc6d0 authored by Tyler Lacey's avatar Tyler Lacey
Browse files

Allow Game Service to trigger the Game Session creation

With this change, the GameServiceProviderInstanceImpl no longer
automatically creates a game session every time a new game task is
started. Instead, the GameServiceProviderInstanceImpl calls the new
GameService#onGameStarted method with details about the game task that
just started.

The GameService can implement onGameStarted with appropriate logic to
determine whether a game session should be created. If the service
determines that a session should be created, then the GameService can
call GameService#createGameSession.

After calling onGameStarted, the GameServiceProviderInstanceImpl will
maintain a GameSessionRecord in the NO_GAME_SESSION_REQUESTED state.
Only games with a record in this state will have a session created in
response to createGameSession being called. When createGameSession
is called for a record in this state, the record will transition to the
GAME_SESSION_REQUESTED state. Once the game session is finally created
and attached to the record, it will switch to the GAME_SESSION_ATTACHED
state.

Test: Manual e2e testing and unit tests
Bug: 207035150
Bug: 202414447
Bug: 202417255
CTS-Coverage-Bug: 206128693
Change-Id: I799f8410da6936ab242fa925d77b0276875cc20f

Change-Id: Id6da59fd3a5951ba5f032c825d1f1f28d3320965
parent 71abd123
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -10853,9 +10853,11 @@ package android.service.games {
  public class GameService extends android.app.Service {
    ctor public GameService();
    method public final void createGameSession(@IntRange(from=0) int);
    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
    method public void onConnected();
    method public void onDisconnected();
    method public void onGameStarted(@NonNull android.service.games.GameStartedEvent);
    field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE";
    field public static final String SERVICE_META_DATA = "android.game_service";
  }
@@ -10873,6 +10875,15 @@ package android.service.games {
    field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE";
  }
  public final class GameStartedEvent implements android.os.Parcelable {
    ctor public GameStartedEvent(@IntRange(from=0) int, @NonNull String);
    method public int describeContents();
    method @NonNull public String getPackageName();
    method @IntRange(from=0) public int getTaskId();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.service.games.GameStartedEvent> CREATOR;
  }
}
package android.service.notification {
+49 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.service.games;

import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -38,6 +40,12 @@ import java.util.Objects;
 * when a game session should begin. It is always kept running by the system.
 * Because of this it should be kept as lightweight as possible.
 *
 * <p> Instead of requiring permissions for sensitive actions (e.g., starting a new game session),
 * this class is provided with an {@link IGameServiceController} instance which exposes the
 * sensitive functionality. This controller is provided by the system server when calling the
 * {@link IGameService#connected(IGameServiceController)} method exposed by this class. The system
 * server does so only when creating the bound game service.
 *
 * <p>Heavyweight operations (such as showing UI) should be implemented in the
 * associated {@link GameSessionService} when a game session is taking place. Its
 * implementation should run in a separate process from the {@link GameService}.
@@ -79,12 +87,13 @@ public class GameService extends Service {
     */
    public static final String SERVICE_META_DATA = "android.game_service";

    private IGameServiceController mGameServiceController;
    private IGameManagerService mGameManagerService;
    private final IGameService mInterface = new IGameService.Stub() {
        @Override
        public void connected() {
        public void connected(IGameServiceController gameServiceController) {
            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
                    GameService::doOnConnected, GameService.this));
                    GameService::doOnConnected, GameService.this, gameServiceController));
        }

        @Override
@@ -92,6 +101,12 @@ public class GameService extends Service {
            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
                    GameService::onDisconnected, GameService.this));
        }

        @Override
        public void gameStarted(GameStartedEvent gameStartedEvent) {
            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
                    GameService::onGameStarted, GameService.this, gameStartedEvent));
        }
    };

    private final IBinder.DeathRecipient mGameManagerServiceDeathRecipient = () -> {
@@ -111,7 +126,7 @@ public class GameService extends Service {
        return null;
    }

    private void doOnConnected() {
    private void doOnConnected(@NonNull IGameServiceController gameServiceController) {
        mGameManagerService =
                IGameManagerService.Stub.asInterface(
                        ServiceManager.getService(Context.GAME_SERVICE));
@@ -122,6 +137,7 @@ public class GameService extends Service {
            Log.w(TAG, "Unable to link to death with system service");
        }

        mGameServiceController = gameServiceController;
        onConnected();
    }

@@ -138,4 +154,34 @@ public class GameService extends Service {
     * The service should clean up any resources that it holds at this point.
     */
    public void onDisconnected() {}

    /**
     * Called when a game task is started. It is the responsibility of the service to determine what
     * action to take (e.g., request that a game session be created).
     *
     * @param gameStartedEvent Contains information about the game being started.
     */
    public void onGameStarted(@NonNull GameStartedEvent gameStartedEvent) {}

    /**
     * Call to create a new game session be created for a game. This method may be called
     * by a game service following {@link #onGameStarted}, using the task ID provided by the
     * provided {@link GameStartedEvent} (using {@link GameStartedEvent#getTaskId}).
     *
     * If a game session already exists for the game task, this call will be ignored and the
     * existing session will continue.
     *
     * @param taskId The taskId of the game.
     */
    public final void createGameSession(@IntRange(from = 0) int taskId) {
        if (mGameServiceController == null) {
            throw new IllegalStateException("Can not call before connected()");
        }

        try {
            mGameServiceController.createGameSession(taskId);
        } catch (RemoteException e) {
            Log.e(TAG, "Request for game session failed", e);
        }
    }
}
+23 −0
Original line number 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.service.games;


/**
 * @hide
 */
parcelable GameStartedEvent;
 No newline at end of file
+119 −0
Original line number 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.service.games;

import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Objects;

/**
 * Event object provided when a game task is started.
 *
 * This is provided to the Game Service via
 * {@link GameService#onGameStarted(GameStartedEvent)}. It includes the game's taskId
 * (see {@link #getTaskId}) that the game's package name (see {@link #getPackageName}).
 *
 * @hide
 */
@SystemApi
public final class GameStartedEvent implements Parcelable {

    @NonNull
    public static final Parcelable.Creator<GameStartedEvent> CREATOR =
            new Parcelable.Creator<GameStartedEvent>() {
                @Override
                public GameStartedEvent createFromParcel(Parcel source) {
                    return new GameStartedEvent(
                            source.readInt(),
                            source.readString());
                }

                @Override
                public GameStartedEvent[] newArray(int size) {
                    return new GameStartedEvent[0];
                }
            };

    private final int mTaskId;
    private final String mPackageName;

    public GameStartedEvent(@IntRange(from = 0) int taskId, @NonNull String packageName) {
        this.mTaskId = taskId;
        this.mPackageName = packageName;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mTaskId);
        dest.writeString(mPackageName);
    }

    /**
     * Unique identifier for the task associated with the game.
     */
    @IntRange(from = 0)
    public int getTaskId() {
        return mTaskId;
    }

    /**
     * The package name for the game.
     */
    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    @Override
    public String toString() {
        return "GameStartedEvent{"
                + "mTaskId="
                + mTaskId
                + ", mPackageName='"
                + mPackageName
                + "\'}";
    }

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

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

        GameStartedEvent that = (GameStartedEvent) o;
        return mTaskId == that.mTaskId
                && Objects.equals(mPackageName, that.mPackageName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mTaskId, mPackageName);
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package android.service.games;

import android.service.games.GameStartedEvent;
import android.service.games.IGameServiceController;

/**
 * @hide
 */
oneway interface IGameService {
    void connected();
    void connected(in IGameServiceController gameServiceController);
    void disconnected();
    void gameStarted(in GameStartedEvent gameStartedEvent);
}
Loading