1 /*
2  * Copyright (C) 2022 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.app.Activity;
22 import android.content.ComponentName;
23 import android.content.Intent;
24 import android.os.Bundle;
25 import android.util.Slog;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.infra.AndroidFuture;
29 
30 import java.util.concurrent.Executor;
31 
32 /**
33  * Trampoline activity that enables the
34  * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor,
35  * GameSessionActivityCallback)} API by reusing existing activity result infrastructure in the
36  * {@link Activity} class. This activity forwards activity results back to the calling
37  * {@link GameSession} via {@link AndroidFuture}.
38  *
39  * @hide
40  */
41 @VisibleForTesting
42 public final class GameSessionTrampolineActivity extends Activity {
43     private static final String TAG = "GameSessionTrampoline";
44     private static final int REQUEST_CODE = 1;
45 
46     static final String FUTURE_KEY = "GameSessionTrampolineActivity.future";
47     static final String INTENT_KEY = "GameSessionTrampolineActivity.intent";
48     static final String OPTIONS_KEY = "GameSessionTrampolineActivity.options";
49     private static final String HAS_LAUNCHED_INTENT_KEY =
50             "GameSessionTrampolineActivity.hasLaunchedIntent";
51     private boolean mHasLaunchedIntent = false;
52 
53     /**
54      * Create an {@link Intent} for the {@link GameSessionTrampolineActivity} with the given
55      * parameters.
56      *
57      * @param targetIntent the forwarded {@link Intent} that is associated with the Activity that
58      *                     will be launched by the {@link GameSessionTrampolineActivity}.
59      * @param options      Activity options. See {@link #startActivity(Intent, Bundle)}.
60      * @param resultFuture the {@link AndroidFuture} that will complete with the activity results of
61      *                     {@code targetIntent} launched.
62      * @return the Intent that will launch the {@link GameSessionTrampolineActivity} with the given
63      * parameters.
64      * @hide
65      */
66     @VisibleForTesting
createIntent( @onNull Intent targetIntent, @Nullable Bundle options, @NonNull AndroidFuture<GameSessionActivityResult> resultFuture)67     public static Intent createIntent(
68             @NonNull Intent targetIntent,
69             @Nullable Bundle options,
70             @NonNull AndroidFuture<GameSessionActivityResult> resultFuture) {
71         final Intent trampolineIntent = new Intent();
72         trampolineIntent.setComponent(
73                 new ComponentName(
74                         "android", "android.service.games.GameSessionTrampolineActivity"));
75         trampolineIntent.putExtra(INTENT_KEY, targetIntent);
76         trampolineIntent.putExtra(OPTIONS_KEY, options);
77         trampolineIntent.putExtra(FUTURE_KEY, resultFuture);
78 
79         return trampolineIntent;
80     }
81 
82     @Override
onCreate(@ullable Bundle savedInstanceState)83     protected void onCreate(@Nullable Bundle savedInstanceState) {
84         super.onCreate(savedInstanceState);
85 
86         if (savedInstanceState != null) {
87             mHasLaunchedIntent = savedInstanceState.getBoolean(HAS_LAUNCHED_INTENT_KEY);
88         }
89 
90         if (mHasLaunchedIntent) {
91             return;
92         }
93         mHasLaunchedIntent = true;
94 
95         try {
96             startActivityAsCaller(
97                     getIntent().getParcelableExtra(INTENT_KEY, android.content.Intent.class),
98                     getIntent().getBundleExtra(OPTIONS_KEY),
99                     false,
100                     getUserId(),
101                     REQUEST_CODE);
102         } catch (Exception e) {
103             Slog.w(TAG, "Unable to launch activity from game session");
104             AndroidFuture<GameSessionActivityResult> future = getIntent().getParcelableExtra(
105                     FUTURE_KEY, com.android.internal.infra.AndroidFuture.class);
106             future.completeExceptionally(e);
107             finish();
108             overridePendingTransition(0, 0);
109         }
110     }
111 
112     @Override
onSaveInstanceState(Bundle outState)113     protected void onSaveInstanceState(Bundle outState) {
114         super.onSaveInstanceState(outState);
115         outState.putBoolean(HAS_LAUNCHED_INTENT_KEY, mHasLaunchedIntent);
116     }
117 
118     @Override
onActivityResult(int requestCode, int resultCode, Intent data)119     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
120         if (requestCode != REQUEST_CODE) {
121             // Something went very wrong if we hit this code path, and we should bail.
122             throw new IllegalStateException("Unexpected request code: " + requestCode);
123         }
124 
125         AndroidFuture<GameSessionActivityResult> future = getIntent().getParcelableExtra(
126                 FUTURE_KEY, com.android.internal.infra.AndroidFuture.class);
127         future.complete(new GameSessionActivityResult(resultCode, data));
128         finish();
129         overridePendingTransition(0, 0);
130     }
131 }
132