1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.games;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SdkConstant;
22 import android.annotation.SystemApi;
23 import android.app.Service;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.hardware.display.DisplayManager;
27 import android.os.Binder;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.view.Display;
31 import android.view.SurfaceControlViewHost;
32 import android.view.WindowManager;
33 
34 import com.android.internal.infra.AndroidFuture;
35 import com.android.internal.util.function.pooled.PooledLambda;
36 
37 import java.util.Objects;
38 
39 /**
40  * Service that hosts active game sessions.
41  *
42  * This service should be in a separate process from the {@link GameService}. This
43  * allows it to perform the heavyweight operations associated with rendering a game
44  * session overlay while games are running and release these resources (by allowing
45  * the process to be killed) when games are not running.
46  *
47  * Game Service providers must extend {@link GameSessionService} and declare the service in their
48  * Manifest. The service must require the {@link android.Manifest.permission#BIND_GAME_SERVICE} so
49  * that other application can not abuse it. This service is used to create instances of
50  * {@link GameSession} via {@link #onNewSession(CreateGameSessionRequest)} and will remain bound to
51  * so long as at least one {@link GameSession} is running.
52  *
53  * @hide
54  */
55 @SystemApi
56 public abstract class GameSessionService extends Service {
57     /**
58      * The {@link Intent} action used when binding to the service.
59      * To be supported, the service must require the
60      * {@link android.Manifest.permission#BIND_GAME_SERVICE} permission so
61      * that other applications can not abuse it.
62      */
63     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
64     public static final String ACTION_GAME_SESSION_SERVICE =
65             "android.service.games.action.GAME_SESSION_SERVICE";
66 
67     private final IGameSessionService mInterface = new IGameSessionService.Stub() {
68         @Override
69         public void create(
70                 IGameSessionController gameSessionController,
71                 CreateGameSessionRequest createGameSessionRequest,
72                 GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
73                 AndroidFuture gameSessionFuture) {
74             Handler.getMain().post(PooledLambda.obtainRunnable(
75                     GameSessionService::doCreate, GameSessionService.this,
76                     gameSessionController,
77                     createGameSessionRequest,
78                     gameSessionViewHostConfiguration,
79                     gameSessionFuture));
80         }
81     };
82 
83     private DisplayManager mDisplayManager;
84 
85     @Override
onCreate()86     public void onCreate() {
87         super.onCreate();
88         mDisplayManager = this.getSystemService(DisplayManager.class);
89     }
90 
91     @Override
92     @Nullable
onBind(@ullable Intent intent)93     public final IBinder onBind(@Nullable Intent intent) {
94         if (intent == null) {
95             return null;
96         }
97 
98         if (!ACTION_GAME_SESSION_SERVICE.equals(intent.getAction())) {
99             return null;
100         }
101 
102         return mInterface.asBinder();
103     }
104 
doCreate( IGameSessionController gameSessionController, CreateGameSessionRequest createGameSessionRequest, GameSessionViewHostConfiguration gameSessionViewHostConfiguration, AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture)105     private void doCreate(
106             IGameSessionController gameSessionController,
107             CreateGameSessionRequest createGameSessionRequest,
108             GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
109             AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) {
110         GameSession gameSession = onNewSession(createGameSessionRequest);
111         Objects.requireNonNull(gameSession);
112 
113         Display display = mDisplayManager.getDisplay(gameSessionViewHostConfiguration.mDisplayId);
114         if (display == null) {
115             createGameSessionResultFuture.completeExceptionally(
116                     new IllegalStateException("No display found for id: "
117                             + gameSessionViewHostConfiguration.mDisplayId));
118             return;
119         }
120 
121         IBinder hostToken = new Binder();
122 
123         // Use a WindowContext so that views attached to the SurfaceControlViewHost will receive
124         // configuration changes (rather than always perceiving the global configuration).
125         final Context windowContext = createWindowContext(display,
126                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, /*options=*/ null);
127         SurfaceControlViewHost surfaceControlViewHost =
128                 new SurfaceControlViewHost(windowContext, display, hostToken, "GameSessionService");
129 
130         gameSession.attach(
131                 gameSessionController,
132                 createGameSessionRequest.getTaskId(),
133                 windowContext,
134                 surfaceControlViewHost,
135                 gameSessionViewHostConfiguration.mWidthPx,
136                 gameSessionViewHostConfiguration.mHeightPx);
137 
138         CreateGameSessionResult createGameSessionResult =
139                 new CreateGameSessionResult(gameSession.mInterface,
140                         surfaceControlViewHost.getSurfacePackage());
141 
142         createGameSessionResultFuture.complete(createGameSessionResult);
143 
144         gameSession.doCreate();
145     }
146 
147     /**
148      * Request to create a new {@link GameSession}.
149      */
150     @NonNull
onNewSession( @onNull CreateGameSessionRequest createGameSessionRequest)151     public abstract GameSession onNewSession(
152             @NonNull CreateGameSessionRequest createGameSessionRequest);
153 }
154