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

Commit 19519e58 authored by David Samuelson's avatar David Samuelson
Browse files

Add support for rendering a View ontop of the GameSession's task.

Test: Manual e2e testing
Bug: 202414447
Bug: 202417255
Bug: 204504596
CTS-Coverage-Bug: 206128693
Change-Id: Ie3d0d74511bede4cffc18fdbfe558491797bc2d2
parent 9162499b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10896,6 +10896,7 @@ package android.service.games {
    ctor public GameSession();
    method public void onCreate();
    method public void onDestroy();
    method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams);
  }
  public abstract class GameSessionService extends android.app.Service {
+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 CreateGameSessionResult;
+84 −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.Hide;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.SurfaceControlViewHost;

/**
 * Internal result object that contains the successful creation of a game session.
 *
 * @see IGameSessionService#create(CreateGameSessionRequest, GameSessionViewHostConfiguration,
 * com.android.internal.infra.AndroidFuture)
 * @hide
 */
@Hide
public final class CreateGameSessionResult implements Parcelable {

    @NonNull
    public static final Parcelable.Creator<CreateGameSessionResult> CREATOR =
            new Parcelable.Creator<CreateGameSessionResult>() {
                @Override
                public CreateGameSessionResult createFromParcel(Parcel source) {
                    return new CreateGameSessionResult(
                            IGameSession.Stub.asInterface(source.readStrongBinder()),
                            source.readParcelable(
                                    SurfaceControlViewHost.SurfacePackage.class.getClassLoader(),
                                    SurfaceControlViewHost.SurfacePackage.class));
                }

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

    private final IGameSession mGameSession;
    private final SurfaceControlViewHost.SurfacePackage mSurfacePackage;

    public CreateGameSessionResult(
            @NonNull IGameSession gameSession,
            @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
        mGameSession = gameSession;
        mSurfacePackage = surfacePackage;
    }

    @NonNull
    public IGameSession getGameSession() {
        return mGameSession;
    }

    @NonNull
    public SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
        return mSurfacePackage;
    }

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

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeStrongBinder(mGameSession.asBinder());
        dest.writeParcelable(mSurfacePackage, flags);
    }
}
+73 −2
Original line number Diff line number Diff line
@@ -16,8 +16,17 @@

package android.service.games;

import android.annotation.Hide;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import com.android.internal.util.function.pooled.PooledLambda;

@@ -41,12 +50,29 @@ public abstract class GameSession {
        }
    };

    private GameSessionRootView mGameSessionRootView;
    private SurfaceControlViewHost mSurfaceControlViewHost;

    @Hide
    void attach(
            @NonNull Context context,
            @NonNull SurfaceControlViewHost surfaceControlViewHost,
            int widthPx,
            int heightPx) {
        mSurfaceControlViewHost = surfaceControlViewHost;
        mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost);
        surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx);
    }

    @Hide
    void doCreate() {
        onCreate();
    }

    @Hide
    void doDestroy() {
        onDestroy();
        mSurfaceControlViewHost.release();
    }

    /**
@@ -54,12 +80,57 @@ public abstract class GameSession {
     *
     * This should be used perform any setup required now that the game session is created.
     */
    public void onCreate() {}
    public void onCreate() {
    }

    /**
     * Finalizer called when the game session is ending.
     *
     * This should be used to perform any cleanup before the game session is destroyed.
     */
    public void onDestroy() {}
    public void onDestroy() {
    }


    /**
     * Sets the task overlay content to an explicit view. This view is placed directly into the game
     * session's task overlay view hierarchy. It can itself be a complex view hierarchy. The size
     * the task overlay view will always match the dimensions of the associated task's window. The
     * {@code View} may not be cleared once set, but may be replaced by invoking
     * {@link #setTaskOverlayView(View, ViewGroup.LayoutParams)} again.
     *
     * @param view         The desired content to display.
     * @param layoutParams Layout parameters for the view.
     */
    public void setTaskOverlayView(
            @NonNull View view,
            @NonNull ViewGroup.LayoutParams layoutParams) {
        mGameSessionRootView.removeAllViews();
        mGameSessionRootView.addView(view, layoutParams);
    }

    /**
     * Root view of the {@link SurfaceControlViewHost} associated with the {@link GameSession}
     * instance. It is responsible for observing changes in the size of the window and resizing
     * itself to match.
     */
    private static final class GameSessionRootView extends FrameLayout {
        private final SurfaceControlViewHost mSurfaceControlViewHost;

        GameSessionRootView(@NonNull Context context,
                SurfaceControlViewHost surfaceControlViewHost) {
            super(context);
            mSurfaceControlViewHost = surfaceControlViewHost;
        }

        @Override
        protected void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);

            // TODO(b/204504596): Investigate skipping the relayout in cases where the size has
            // not changed.
            Rect bounds = newConfig.windowConfiguration.getBounds();
            mSurfaceControlViewHost.relayout(bounds.width(), bounds.height());
        }
    }
}
+43 −4
Original line number Diff line number Diff line
@@ -22,8 +22,12 @@ import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.view.Display;
import android.view.SurfaceControlViewHost;

import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -62,15 +66,26 @@ public abstract class GameSessionService extends Service {

    private final IGameSessionService mInterface = new IGameSessionService.Stub() {
        @Override
        public void create(CreateGameSessionRequest createGameSessionRequest,
        public void create(
                CreateGameSessionRequest createGameSessionRequest,
                GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
                AndroidFuture gameSessionFuture) {
            Handler.getMain().post(PooledLambda.obtainRunnable(
                    GameSessionService::doCreate, GameSessionService.this,
                    createGameSessionRequest,
                    gameSessionViewHostConfiguration,
                    gameSessionFuture));
        }
    };

    private DisplayManager mDisplayManager;

    @Override
    public void onCreate() {
        super.onCreate();
        mDisplayManager = this.getSystemService(DisplayManager.class);
    }

    @Override
    @Nullable
    public final IBinder onBind(@Nullable Intent intent) {
@@ -85,12 +100,36 @@ public abstract class GameSessionService extends Service {
        return mInterface.asBinder();
    }

    private void doCreate(CreateGameSessionRequest createGameSessionRequest,
            AndroidFuture<IBinder> gameSessionFuture) {
    private void doCreate(
            CreateGameSessionRequest createGameSessionRequest,
            GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
            AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) {
        GameSession gameSession = onNewSession(createGameSessionRequest);
        Objects.requireNonNull(gameSession);

        gameSessionFuture.complete(gameSession.mInterface.asBinder());
        Display display = mDisplayManager.getDisplay(gameSessionViewHostConfiguration.mDisplayId);
        if (display == null) {
            createGameSessionResultFuture.completeExceptionally(
                    new IllegalStateException("No display found for id: "
                            + gameSessionViewHostConfiguration.mDisplayId));
            return;
        }

        IBinder hostToken = new Binder();
        SurfaceControlViewHost surfaceControlViewHost =
                new SurfaceControlViewHost(this, display, hostToken);

        gameSession.attach(this,
                surfaceControlViewHost,
                gameSessionViewHostConfiguration.mWidthPx,
                gameSessionViewHostConfiguration.mHeightPx);

        CreateGameSessionResult createGameSessionResult =
                new CreateGameSessionResult(gameSession.mInterface,
                        surfaceControlViewHost.getSurfacePackage());

        createGameSessionResultFuture.complete(createGameSessionResult);


        gameSession.doCreate();
    }
Loading