From 37db6025c6998d47d09e79bf04547722a5028705 Mon Sep 17 00:00:00 2001 From: David Samuelson Date: Mon, 18 Apr 2022 14:59:58 +0000 Subject: Ensure GameSessionTrampolineActivity launches intent once. Test: Manually & passes newly added GameSessionTrampolineActivityTest Bug: 229394334 Bug: 202414447 Bug: 202417255 Change-Id: I15ea072805439531fc89ee4f3038acc57f8c49ea --- core/java/android/service/games/GameSession.java | 14 +- .../service/games/GameSessionActivityResult.java | 12 +- .../games/GameSessionTrampolineActivity.java | 51 +++++ services/tests/mockingservicestests/Android.bp | 2 + .../tests/mockingservicestests/AndroidManifest.xml | 4 + .../games/GameSessionTrampolineActivityTest.java | 212 +++++++++++++++++++++ 6 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java 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 CREATOR = new Creator() { @@ -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 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), @@ -63,6 +108,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) { 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 @@ + + @@ -39,6 +41,8 @@ + unusedResultFuture = + startTestActivityViaGameSessionTrampolineActivity(); + + TestActivityPage.assertPageIsLaunched(); + } + + @Test + public void launch_targetActivityFinishesSuccessfully_futureCompletedWithSameResults() { + AndroidFuture 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 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 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 + startTestActivityViaGameSessionTrampolineActivity() { + Intent testActivityIntent = new Intent(); + testActivityIntent.setClass(getInstrumentation().getTargetContext(), TestActivity.class); + + return startGameSessionTrampolineActivity(testActivityIntent); + } + + private static AndroidFuture startGameSessionTrampolineActivity( + Intent targetIntent) { + AndroidFuture 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); + } + } +} -- cgit v1.2.3-59-g8ed1b