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.annotation.SystemApi;
|
||||||
import android.app.ActivityTaskManager;
|
import android.app.ActivityTaskManager;
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
@@ -511,14 +510,11 @@ public abstract class GameSession {
|
|||||||
callback.onActivityResult(result.getResultCode(), result.getData());
|
callback.onActivityResult(result.getResultCode(), result.getData());
|
||||||
}, executor);
|
}, executor);
|
||||||
|
|
||||||
final Intent trampolineIntent = new Intent();
|
final Intent trampolineIntent =
|
||||||
trampolineIntent.setComponent(
|
GameSessionTrampolineActivity.createIntent(
|
||||||
new ComponentName(
|
intent,
|
||||||
"android", "android.service.games.GameSessionTrampolineActivity"));
|
options,
|
||||||
trampolineIntent.putExtra(GameSessionTrampolineActivity.INTENT_KEY, intent);
|
future);
|
||||||
trampolineIntent.putExtra(GameSessionTrampolineActivity.OPTIONS_KEY, options);
|
|
||||||
trampolineIntent.putExtra(
|
|
||||||
GameSessionTrampolineActivity.FUTURE_KEY, future);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int result = ActivityTaskManager.getService().startActivityFromGameSession(
|
int result = ActivityTaskManager.getService().startActivityFromGameSession(
|
||||||
|
|||||||
@@ -22,8 +22,12 @@ import android.content.Intent;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
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 =
|
public static final Creator<GameSessionActivityResult> CREATOR =
|
||||||
new Creator<GameSessionActivityResult>() {
|
new Creator<GameSessionActivityResult>() {
|
||||||
@@ -44,17 +48,17 @@ final class GameSessionActivityResult implements Parcelable {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private final Intent mData;
|
private final Intent mData;
|
||||||
|
|
||||||
GameSessionActivityResult(int resultCode, @Nullable Intent data) {
|
public GameSessionActivityResult(int resultCode, @Nullable Intent data) {
|
||||||
mResultCode = resultCode;
|
mResultCode = resultCode;
|
||||||
mData = data;
|
mData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getResultCode() {
|
public int getResultCode() {
|
||||||
return mResultCode;
|
return mResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Intent getData() {
|
public Intent getData() {
|
||||||
return mData;
|
return mData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
package android.service.games;
|
package android.service.games;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.infra.AndroidFuture;
|
import com.android.internal.infra.AndroidFuture;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -35,6 +38,7 @@ import java.util.concurrent.Executor;
|
|||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
public final class GameSessionTrampolineActivity extends Activity {
|
public final class GameSessionTrampolineActivity extends Activity {
|
||||||
private static final String TAG = "GameSessionTrampoline";
|
private static final String TAG = "GameSessionTrampoline";
|
||||||
private static final int REQUEST_CODE = 1;
|
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 FUTURE_KEY = "GameSessionTrampolineActivity.future";
|
||||||
static final String INTENT_KEY = "GameSessionTrampolineActivity.intent";
|
static final String INTENT_KEY = "GameSessionTrampolineActivity.intent";
|
||||||
static final String OPTIONS_KEY = "GameSessionTrampolineActivity.options";
|
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
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
mHasLaunchedIntent = savedInstanceState.getBoolean(HAS_LAUNCHED_INTENT_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mHasLaunchedIntent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHasLaunchedIntent = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivityAsCaller(
|
startActivityAsCaller(
|
||||||
getIntent().getParcelableExtra(INTENT_KEY),
|
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
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (requestCode != REQUEST_CODE) {
|
if (requestCode != REQUEST_CODE) {
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ android_test {
|
|||||||
static_libs: [
|
static_libs: [
|
||||||
"androidx.test.core",
|
"androidx.test.core",
|
||||||
"androidx.test.runner",
|
"androidx.test.runner",
|
||||||
|
"androidx.test.espresso.core",
|
||||||
|
"androidx.test.espresso.contrib",
|
||||||
"androidx.test.ext.truth",
|
"androidx.test.ext.truth",
|
||||||
"frameworks-base-testutils",
|
"frameworks-base-testutils",
|
||||||
"hamcrest-library",
|
"hamcrest-library",
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
|
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.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 -->
|
<!-- needed by MasterClearReceiverTest to display a system dialog -->
|
||||||
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
|
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
|
||||||
@@ -39,6 +41,8 @@
|
|||||||
<application android:testOnly="true"
|
<application android:testOnly="true"
|
||||||
android:debuggable="true">
|
android:debuggable="true">
|
||||||
<uses-library android:name="android.test.runner" />
|
<uses-library android:name="android.test.runner" />
|
||||||
|
<activity
|
||||||
|
android:name="android.service.games.GameSessionTrampolineActivityTest$TestActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<instrumentation
|
<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