Merge "Ensure GameSessionTrampolineActivity launches intent once." into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
eeebac455b
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user