summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author David Samuelson <samuelsond@google.com> 2022-04-27 22:22:59 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-04-27 22:22:59 +0000
commiteeebac455b0501c4efbcec388539c5ca4f3048a1 (patch)
treedd076330e8abe037b7788f7f53031f153d68480c
parent30fdd9646cecbd47af35c5aa1fbc2672f03d0ff6 (diff)
parent37db6025c6998d47d09e79bf04547722a5028705 (diff)
Merge "Ensure GameSessionTrampolineActivity launches intent once." into tm-dev
-rw-r--r--core/java/android/service/games/GameSession.java14
-rw-r--r--core/java/android/service/games/GameSessionActivityResult.java12
-rw-r--r--core/java/android/service/games/GameSessionTrampolineActivity.java51
-rw-r--r--services/tests/mockingservicestests/Android.bp2
-rw-r--r--services/tests/mockingservicestests/AndroidManifest.xml4
-rw-r--r--services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java212
6 files changed, 282 insertions, 13 deletions
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index 01152943efe3..e8d53d351795 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -25,7 +25,6 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.ActivityTaskManager;
import android.app.Instrumentation;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -511,14 +510,11 @@ public abstract class GameSession {
callback.onActivityResult(result.getResultCode(), result.getData());
}, executor);
- final Intent trampolineIntent = new Intent();
- trampolineIntent.setComponent(
- new ComponentName(
- "android", "android.service.games.GameSessionTrampolineActivity"));
- trampolineIntent.putExtra(GameSessionTrampolineActivity.INTENT_KEY, intent);
- trampolineIntent.putExtra(GameSessionTrampolineActivity.OPTIONS_KEY, options);
- trampolineIntent.putExtra(
- GameSessionTrampolineActivity.FUTURE_KEY, future);
+ final Intent trampolineIntent =
+ GameSessionTrampolineActivity.createIntent(
+ intent,
+ options,
+ future);
try {
int result = ActivityTaskManager.getService().startActivityFromGameSession(
diff --git a/core/java/android/service/games/GameSessionActivityResult.java b/core/java/android/service/games/GameSessionActivityResult.java
index a2ec6ada010c..c8099e6e5eff 100644
--- a/core/java/android/service/games/GameSessionActivityResult.java
+++ b/core/java/android/service/games/GameSessionActivityResult.java
@@ -22,8 +22,12 @@ import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
-final class GameSessionActivityResult implements Parcelable {
+
+/** @hide */
+@VisibleForTesting
+public final class GameSessionActivityResult implements Parcelable {
public static final Creator<GameSessionActivityResult> CREATOR =
new Creator<GameSessionActivityResult>() {
@@ -44,17 +48,17 @@ final class GameSessionActivityResult implements Parcelable {
@Nullable
private final Intent mData;
- GameSessionActivityResult(int resultCode, @Nullable Intent data) {
+ public GameSessionActivityResult(int resultCode, @Nullable Intent data) {
mResultCode = resultCode;
mData = data;
}
- int getResultCode() {
+ public int getResultCode() {
return mResultCode;
}
@Nullable
- Intent getData() {
+ public Intent getData() {
return mData;
}
diff --git a/core/java/android/service/games/GameSessionTrampolineActivity.java b/core/java/android/service/games/GameSessionTrampolineActivity.java
index 3d97d0f59b33..e890876df3e7 100644
--- a/core/java/android/service/games/GameSessionTrampolineActivity.java
+++ b/core/java/android/service/games/GameSessionTrampolineActivity.java
@@ -16,12 +16,15 @@
package android.service.games;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import java.util.concurrent.Executor;
@@ -35,6 +38,7 @@ import java.util.concurrent.Executor;
*
* @hide
*/
+@VisibleForTesting
public final class GameSessionTrampolineActivity extends Activity {
private static final String TAG = "GameSessionTrampoline";
private static final int REQUEST_CODE = 1;
@@ -42,11 +46,52 @@ public final class GameSessionTrampolineActivity extends Activity {
static final String FUTURE_KEY = "GameSessionTrampolineActivity.future";
static final String INTENT_KEY = "GameSessionTrampolineActivity.intent";
static final String OPTIONS_KEY = "GameSessionTrampolineActivity.options";
+ private static final String HAS_LAUNCHED_INTENT_KEY =
+ "GameSessionTrampolineActivity.hasLaunchedIntent";
+ private boolean mHasLaunchedIntent = false;
+
+ /**
+ * Create an {@link Intent} for the {@link GameSessionTrampolineActivity} with the given
+ * parameters.
+ *
+ * @param targetIntent the forwarded {@link Intent} that is associated with the Activity that
+ * will be launched by the {@link GameSessionTrampolineActivity}.
+ * @param options Activity options. See {@link #startActivity(Intent, Bundle)}.
+ * @param resultFuture the {@link AndroidFuture} that will complete with the activity results of
+ * {@code targetIntent} launched.
+ * @return the Intent that will launch the {@link GameSessionTrampolineActivity} with the given
+ * parameters.
+ * @hide
+ */
+ @VisibleForTesting
+ public static Intent createIntent(
+ @NonNull Intent targetIntent,
+ @Nullable Bundle options,
+ @NonNull AndroidFuture<GameSessionActivityResult> resultFuture) {
+ final Intent trampolineIntent = new Intent();
+ trampolineIntent.setComponent(
+ new ComponentName(
+ "android", "android.service.games.GameSessionTrampolineActivity"));
+ trampolineIntent.putExtra(INTENT_KEY, targetIntent);
+ trampolineIntent.putExtra(OPTIONS_KEY, options);
+ trampolineIntent.putExtra(FUTURE_KEY, resultFuture);
+
+ return trampolineIntent;
+ }
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mHasLaunchedIntent = savedInstanceState.getBoolean(HAS_LAUNCHED_INTENT_KEY);
+ }
+
+ if (mHasLaunchedIntent) {
+ return;
+ }
+ mHasLaunchedIntent = true;
+
try {
startActivityAsCaller(
getIntent().getParcelableExtra(INTENT_KEY),
@@ -64,6 +109,12 @@ public final class GameSessionTrampolineActivity extends Activity {
}
@Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(HAS_LAUNCHED_INTENT_KEY, mHasLaunchedIntent);
+ }
+
+ @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != REQUEST_CODE) {
// Something went very wrong if we hit this code path, and we should bail.
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 670c1596e15e..08c68b9a469e 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -42,6 +42,8 @@ android_test {
static_libs: [
"androidx.test.core",
"androidx.test.runner",
+ "androidx.test.espresso.core",
+ "androidx.test.espresso.contrib",
"androidx.test.ext.truth",
"frameworks-base-testutils",
"hamcrest-library",
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 7714cf0ca094..07b763dcd85b 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -32,6 +32,8 @@
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <uses-permission android:name="android.permission.MANAGE_GAME_ACTIVITY" />
+ <uses-permission android:name="android.permission.SET_ALWAYS_FINISH" />
<!-- needed by MasterClearReceiverTest to display a system dialog -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
@@ -39,6 +41,8 @@
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <activity
+ android:name="android.service.games.GameSessionTrampolineActivityTest$TestActivity" />
</application>
<instrumentation
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
new file mode 100644
index 000000000000..d68b517ca8cd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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 static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static androidx.test.ext.truth.content.IntentSubject.assertThat;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.hamcrest.Matchers.allOf;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.test.espresso.NoActivityResumedException;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.infra.AndroidFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for the {@link GameSessionTrampolineActivity}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@Presubmit
+public class GameSessionTrampolineActivityTest {
+
+ @Before
+ public void setUp() {
+ setAlwaysFinishActivities(false);
+ }
+
+ @After
+ public void tearDown() {
+ setAlwaysFinishActivities(false);
+ }
+
+ @Test
+ public void launch_launchesTargetActivity() {
+ AndroidFuture<GameSessionActivityResult> unusedResultFuture =
+ startTestActivityViaGameSessionTrampolineActivity();
+
+ TestActivityPage.assertPageIsLaunched();
+ }
+
+ @Test
+ public void launch_targetActivityFinishesSuccessfully_futureCompletedWithSameResults() {
+ AndroidFuture<GameSessionActivityResult> resultFuture =
+ startTestActivityViaGameSessionTrampolineActivity();
+
+ TestActivityPage.assertPageIsLaunched();
+ TestActivityPage.clickFinish();
+
+ GameSessionActivityResult expectedResult =
+ new GameSessionActivityResult(Activity.RESULT_OK, TestActivity.RESULT_INTENT);
+
+ assertEquals(resultFuture, expectedResult);
+
+ TestActivityPage.assertPageIsNotLaunched();
+ }
+
+ @Test
+ public void launch_trampolineActivityProcessDeath_futureCompletedWithSameResults() {
+ setAlwaysFinishActivities(true);
+
+ AndroidFuture<GameSessionActivityResult> resultFuture =
+ startTestActivityViaGameSessionTrampolineActivity();
+
+ TestActivityPage.assertPageIsLaunched();
+ TestActivityPage.clickFinish();
+
+ GameSessionActivityResult expectedResult =
+ new GameSessionActivityResult(Activity.RESULT_OK, TestActivity.RESULT_INTENT);
+
+ assertEquals(resultFuture, expectedResult);
+
+ TestActivityPage.assertPageIsNotLaunched();
+ }
+
+ private static void assertEquals(
+ AndroidFuture<GameSessionActivityResult> actualFuture,
+ GameSessionActivityResult expected) {
+ try {
+ assertEquals(actualFuture.get(20, TimeUnit.SECONDS), expected);
+ } catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ private static void assertEquals(
+ GameSessionActivityResult actual,
+ GameSessionActivityResult expected) {
+ assertThat(actual.getResultCode()).isEqualTo(expected.getResultCode());
+ assertThat(actual.getData()).filtersEquallyTo(actual.getData());
+ }
+
+ private static void setAlwaysFinishActivities(boolean isEnabled) {
+ try {
+ ActivityManager.getService().setAlwaysFinish(isEnabled);
+ } catch (RemoteException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ private static AndroidFuture<GameSessionActivityResult>
+ startTestActivityViaGameSessionTrampolineActivity() {
+ Intent testActivityIntent = new Intent();
+ testActivityIntent.setClass(getInstrumentation().getTargetContext(), TestActivity.class);
+
+ return startGameSessionTrampolineActivity(testActivityIntent);
+ }
+
+ private static AndroidFuture<GameSessionActivityResult> startGameSessionTrampolineActivity(
+ Intent targetIntent) {
+ AndroidFuture<GameSessionActivityResult> resultFuture = new AndroidFuture<>();
+ Intent trampolineActivityIntent = GameSessionTrampolineActivity.createIntent(targetIntent,
+ null, resultFuture);
+ trampolineActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getInstrumentation().getTargetContext().startActivity(trampolineActivityIntent);
+ getInstrumentation().waitForIdleSync();
+
+ return resultFuture;
+ }
+
+
+ private static class TestActivityPage {
+ private TestActivityPage() {}
+
+ public static void assertPageIsLaunched() {
+ onView(withText(TestActivity.PAGE_TITLE_TEXT)).check(matches(isDisplayed()));
+ }
+
+ public static void assertPageIsNotLaunched() {
+ try {
+ onView(withText(TestActivity.PAGE_TITLE_TEXT)).check(doesNotExist());
+ } catch (NoActivityResumedException ex) {
+ // Do nothing
+ }
+ }
+
+ public static void clickFinish() {
+ onView(allOf(withText(TestActivity.FINISH_BUTTON_TEXT), isClickable())).perform(
+ click());
+ getInstrumentation().waitForIdleSync();
+ }
+ }
+
+ public static class TestActivity extends Activity {
+ private static final String PAGE_TITLE_TEXT = "GameSessionTestActivity";
+ private static final String FINISH_BUTTON_TEXT = "Finish Test Activity";
+ private static final Intent RESULT_INTENT = new Intent("com.test.action.VIEW");
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout contentLayout = new LinearLayout(this);
+ contentLayout.setOrientation(LinearLayout.VERTICAL);
+
+ TextView titleTextView = new TextView(this);
+ titleTextView.setText(PAGE_TITLE_TEXT);
+ contentLayout.addView(titleTextView);
+
+ Button finishActivityButton = new Button(this);
+ finishActivityButton.setText(FINISH_BUTTON_TEXT);
+ finishActivityButton.setOnClickListener((unused) -> {
+ setResult(Activity.RESULT_OK, RESULT_INTENT);
+ finish();
+ });
+
+
+ contentLayout.addView(finishActivityButton);
+ setContentView(contentLayout);
+ }
+ }
+}