Merge "Prevent waiting if no new activity instance launched" into tm-qpr-dev am: f2a5491fda
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19098653 Change-Id: I392f246a4ad0a3e42fc13936239da2241b9e14ef Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -783,6 +783,17 @@ public class Instrumentation {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called after starting an Activity and provides the result code that defined in
|
||||||
|
* {@link ActivityManager}, like {@link ActivityManager#START_SUCCESS}.
|
||||||
|
*
|
||||||
|
* @param result the result code that returns after starting an Activity.
|
||||||
|
* @param bOptions the bundle generated from {@link ActivityOptions} that originally
|
||||||
|
* being used to start the Activity.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void onStartActivityResult(int result, @NonNull Bundle bOptions) {}
|
||||||
|
|
||||||
final boolean match(Context who,
|
final boolean match(Context who,
|
||||||
Activity activity,
|
Activity activity,
|
||||||
Intent intent) {
|
Intent intent) {
|
||||||
@@ -1344,6 +1355,28 @@ public class Instrumentation {
|
|||||||
return apk.getAppFactory();
|
return apk.getAppFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be called before {@link #checkStartActivityResult(int, Object)}, because
|
||||||
|
* exceptions might be thrown while checking the results.
|
||||||
|
*/
|
||||||
|
private void notifyStartActivityResult(int result, @Nullable Bundle options) {
|
||||||
|
if (mActivityMonitors == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (mSync) {
|
||||||
|
final int size = mActivityMonitors.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
||||||
|
if (am.ignoreMatchingSpecificIntents()) {
|
||||||
|
if (options == null) {
|
||||||
|
options = ActivityOptions.makeBasic().toBundle();
|
||||||
|
}
|
||||||
|
am.onStartActivityResult(result, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void prePerformCreate(Activity activity) {
|
private void prePerformCreate(Activity activity) {
|
||||||
if (mWaitingActivities != null) {
|
if (mWaitingActivities != null) {
|
||||||
synchronized (mSync) {
|
synchronized (mSync) {
|
||||||
@@ -1802,6 +1835,7 @@ public class Instrumentation {
|
|||||||
who.getOpPackageName(), who.getAttributionTag(), intent,
|
who.getOpPackageName(), who.getAttributionTag(), intent,
|
||||||
intent.resolveTypeIfNeeded(who.getContentResolver()), token,
|
intent.resolveTypeIfNeeded(who.getContentResolver()), token,
|
||||||
target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
|
target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
|
||||||
|
notifyStartActivityResult(result, options);
|
||||||
checkStartActivityResult(result, intent);
|
checkStartActivityResult(result, intent);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException("Failure from system", e);
|
throw new RuntimeException("Failure from system", e);
|
||||||
@@ -1876,6 +1910,7 @@ public class Instrumentation {
|
|||||||
int result = ActivityTaskManager.getService().startActivities(whoThread,
|
int result = ActivityTaskManager.getService().startActivities(whoThread,
|
||||||
who.getOpPackageName(), who.getAttributionTag(), intents, resolvedTypes,
|
who.getOpPackageName(), who.getAttributionTag(), intents, resolvedTypes,
|
||||||
token, options, userId);
|
token, options, userId);
|
||||||
|
notifyStartActivityResult(result, options);
|
||||||
checkStartActivityResult(result, intents[0]);
|
checkStartActivityResult(result, intents[0]);
|
||||||
return result;
|
return result;
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
@@ -1947,6 +1982,7 @@ public class Instrumentation {
|
|||||||
who.getOpPackageName(), who.getAttributionTag(), intent,
|
who.getOpPackageName(), who.getAttributionTag(), intent,
|
||||||
intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,
|
intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,
|
||||||
requestCode, 0, null, options);
|
requestCode, 0, null, options);
|
||||||
|
notifyStartActivityResult(result, options);
|
||||||
checkStartActivityResult(result, intent);
|
checkStartActivityResult(result, intent);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException("Failure from system", e);
|
throw new RuntimeException("Failure from system", e);
|
||||||
@@ -2017,6 +2053,7 @@ public class Instrumentation {
|
|||||||
who.getOpPackageName(), who.getAttributionTag(), intent,
|
who.getOpPackageName(), who.getAttributionTag(), intent,
|
||||||
intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho,
|
intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho,
|
||||||
requestCode, 0, null, options, user.getIdentifier());
|
requestCode, 0, null, options, user.getIdentifier());
|
||||||
|
notifyStartActivityResult(result, options);
|
||||||
checkStartActivityResult(result, intent);
|
checkStartActivityResult(result, intent);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException("Failure from system", e);
|
throw new RuntimeException("Failure from system", e);
|
||||||
@@ -2068,6 +2105,7 @@ public class Instrumentation {
|
|||||||
token, target != null ? target.mEmbeddedID : null,
|
token, target != null ? target.mEmbeddedID : null,
|
||||||
requestCode, 0, null, options,
|
requestCode, 0, null, options,
|
||||||
ignoreTargetSecurity, userId);
|
ignoreTargetSecurity, userId);
|
||||||
|
notifyStartActivityResult(result, options);
|
||||||
checkStartActivityResult(result, intent);
|
checkStartActivityResult(result, intent);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException("Failure from system", e);
|
throw new RuntimeException("Failure from system", e);
|
||||||
@@ -2115,6 +2153,7 @@ public class Instrumentation {
|
|||||||
int result = appTask.startActivity(whoThread.asBinder(), who.getOpPackageName(),
|
int result = appTask.startActivity(whoThread.asBinder(), who.getOpPackageName(),
|
||||||
who.getAttributionTag(), intent,
|
who.getAttributionTag(), intent,
|
||||||
intent.resolveTypeIfNeeded(who.getContentResolver()), options);
|
intent.resolveTypeIfNeeded(who.getContentResolver()), options);
|
||||||
|
notifyStartActivityResult(result, options);
|
||||||
checkStartActivityResult(result, intent);
|
checkStartActivityResult(result, intent);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException("Failure from system", e);
|
throw new RuntimeException("Failure from system", e);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package androidx.window.extensions.embedding;
|
package androidx.window.extensions.embedding;
|
||||||
|
|
||||||
|
import static android.app.ActivityManager.START_SUCCESS;
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||||
|
|
||||||
@@ -97,6 +98,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
|
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
|
private final ActivityStartMonitor mActivityStartMonitor;
|
||||||
|
|
||||||
public SplitController() {
|
public SplitController() {
|
||||||
final MainThreadExecutor executor = new MainThreadExecutor();
|
final MainThreadExecutor executor = new MainThreadExecutor();
|
||||||
@@ -108,7 +110,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
new LifecycleCallbacks());
|
new LifecycleCallbacks());
|
||||||
// Intercept activity starts to route activities to new containers if necessary.
|
// Intercept activity starts to route activities to new containers if necessary.
|
||||||
Instrumentation instrumentation = activityThread.getInstrumentation();
|
Instrumentation instrumentation = activityThread.getInstrumentation();
|
||||||
instrumentation.addMonitor(new ActivityStartMonitor());
|
mActivityStartMonitor = new ActivityStartMonitor();
|
||||||
|
instrumentation.addMonitor(mActivityStartMonitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates the embedding rules applied to future activity launches. */
|
/** Updates the embedding rules applied to future activity launches. */
|
||||||
@@ -1385,6 +1388,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
return ActivityThread.currentActivityThread().getActivity(activityToken);
|
return ActivityThread.currentActivityThread().getActivity(activityToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ActivityStartMonitor getActivityStartMonitor() {
|
||||||
|
return mActivityStartMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it
|
* Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it
|
||||||
* after creation because the activity could be reparented.
|
* after creation because the activity could be reparented.
|
||||||
@@ -1536,7 +1544,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
* A monitor that intercepts all activity start requests originating in the client process and
|
* A monitor that intercepts all activity start requests originating in the client process and
|
||||||
* can amend them to target a specific task fragment to form a split.
|
* can amend them to target a specific task fragment to form a split.
|
||||||
*/
|
*/
|
||||||
private class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
|
@VisibleForTesting
|
||||||
|
class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
|
||||||
|
@VisibleForTesting
|
||||||
|
Intent mCurrentIntent;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
|
public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
|
||||||
@@ -1564,11 +1575,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
// the dedicated container.
|
// the dedicated container.
|
||||||
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
||||||
launchedInTaskFragment.getTaskFragmentToken());
|
launchedInTaskFragment.getTaskFragmentToken());
|
||||||
|
mCurrentIntent = intent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onStartActivity(who, intent, options);
|
return super.onStartActivity(who, intent, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartActivityResult(int result, @NonNull Bundle bOptions) {
|
||||||
|
super.onStartActivityResult(result, bOptions);
|
||||||
|
if (mCurrentIntent != null && result != START_SUCCESS) {
|
||||||
|
// Clear the pending appeared intent if the activity was not started successfully.
|
||||||
|
final IBinder token = bOptions.getBinder(
|
||||||
|
ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
|
||||||
|
if (token != null) {
|
||||||
|
final TaskFragmentContainer container = getContainer(token);
|
||||||
|
if (container != null) {
|
||||||
|
container.clearPendingAppearedIntentIfNeeded(mCurrentIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCurrentIntent = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -198,6 +198,22 @@ class TaskFragmentContainer {
|
|||||||
return mPendingAppearedIntent;
|
return mPendingAppearedIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPendingAppearedIntent(@Nullable Intent intent) {
|
||||||
|
mPendingAppearedIntent = intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the
|
||||||
|
* pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has
|
||||||
|
* running activities).
|
||||||
|
*/
|
||||||
|
void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) {
|
||||||
|
if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mPendingAppearedIntent = null;
|
||||||
|
}
|
||||||
|
|
||||||
boolean hasActivity(@NonNull IBinder token) {
|
boolean hasActivity(@NonNull IBinder token) {
|
||||||
if (mInfo != null && mInfo.getActivities().contains(token)) {
|
if (mInfo != null && mInfo.getActivities().contains(token)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -230,13 +246,18 @@ class TaskFragmentContainer {
|
|||||||
|
|
||||||
void setInfo(@NonNull TaskFragmentInfo info) {
|
void setInfo(@NonNull TaskFragmentInfo info) {
|
||||||
if (!mIsFinished && mInfo == null && info.isEmpty()) {
|
if (!mIsFinished && mInfo == null && info.isEmpty()) {
|
||||||
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if it is
|
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
|
||||||
// still empty after timeout.
|
// pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
|
||||||
|
// it is still empty after timeout.
|
||||||
mAppearEmptyTimeout = () -> {
|
mAppearEmptyTimeout = () -> {
|
||||||
mAppearEmptyTimeout = null;
|
mAppearEmptyTimeout = null;
|
||||||
mController.onTaskFragmentAppearEmptyTimeout(this);
|
mController.onTaskFragmentAppearEmptyTimeout(this);
|
||||||
};
|
};
|
||||||
|
if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
|
||||||
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
|
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
|
||||||
|
} else {
|
||||||
|
mAppearEmptyTimeout.run();
|
||||||
|
}
|
||||||
} else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
|
} else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
|
||||||
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
|
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
|
||||||
mAppearEmptyTimeout = null;
|
mAppearEmptyTimeout = null;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package androidx.window.extensions.embedding;
|
package androidx.window.extensions.embedding;
|
||||||
|
|
||||||
|
import static android.app.ActivityManager.START_CANCELED;
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||||
|
|
||||||
@@ -292,6 +293,26 @@ public class SplitControllerTest {
|
|||||||
verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
|
verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStartActivityResultError() {
|
||||||
|
final Intent intent = new Intent();
|
||||||
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
|
intent, taskContainer, mSplitController);
|
||||||
|
final SplitController.ActivityStartMonitor monitor =
|
||||||
|
mSplitController.getActivityStartMonitor();
|
||||||
|
|
||||||
|
container.setPendingAppearedIntent(intent);
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
||||||
|
container.getTaskFragmentToken());
|
||||||
|
monitor.mCurrentIntent = intent;
|
||||||
|
doReturn(container).when(mSplitController).getContainer(any());
|
||||||
|
|
||||||
|
monitor.onStartActivityResult(START_CANCELED, bundle);
|
||||||
|
assertNull(container.getPendingAppearedIntent());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOnActivityCreated() {
|
public void testOnActivityCreated() {
|
||||||
mSplitController.onActivityCreated(mActivity);
|
mSplitController.onActivityCreated(mActivity);
|
||||||
|
|||||||
@@ -209,21 +209,21 @@ public class TaskFragmentContainerTest {
|
|||||||
|
|
||||||
assertNull(container.mAppearEmptyTimeout);
|
assertNull(container.mAppearEmptyTimeout);
|
||||||
|
|
||||||
// Not set if it is not appeared empty.
|
|
||||||
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
|
|
||||||
doReturn(new ArrayList<>()).when(info).getActivities();
|
|
||||||
doReturn(false).when(info).isEmpty();
|
|
||||||
container.setInfo(info);
|
|
||||||
|
|
||||||
assertNull(container.mAppearEmptyTimeout);
|
|
||||||
|
|
||||||
// Set timeout if the first info set is empty.
|
// Set timeout if the first info set is empty.
|
||||||
|
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
|
||||||
container.mInfo = null;
|
container.mInfo = null;
|
||||||
doReturn(true).when(info).isEmpty();
|
doReturn(true).when(info).isEmpty();
|
||||||
container.setInfo(info);
|
container.setInfo(info);
|
||||||
|
|
||||||
assertNotNull(container.mAppearEmptyTimeout);
|
assertNotNull(container.mAppearEmptyTimeout);
|
||||||
|
|
||||||
|
// Not set if it is not appeared empty.
|
||||||
|
doReturn(new ArrayList<>()).when(info).getActivities();
|
||||||
|
doReturn(false).when(info).isEmpty();
|
||||||
|
container.setInfo(info);
|
||||||
|
|
||||||
|
assertNull(container.mAppearEmptyTimeout);
|
||||||
|
|
||||||
// Remove timeout after the container becomes non-empty.
|
// Remove timeout after the container becomes non-empty.
|
||||||
doReturn(false).when(info).isEmpty();
|
doReturn(false).when(info).isEmpty();
|
||||||
container.setInfo(info);
|
container.setInfo(info);
|
||||||
@@ -232,6 +232,7 @@ public class TaskFragmentContainerTest {
|
|||||||
|
|
||||||
// Running the timeout will call into SplitController.onTaskFragmentAppearEmptyTimeout.
|
// Running the timeout will call into SplitController.onTaskFragmentAppearEmptyTimeout.
|
||||||
container.mInfo = null;
|
container.mInfo = null;
|
||||||
|
container.setPendingAppearedIntent(mIntent);
|
||||||
doReturn(true).when(info).isEmpty();
|
doReturn(true).when(info).isEmpty();
|
||||||
container.setInfo(info);
|
container.setInfo(info);
|
||||||
container.mAppearEmptyTimeout.run();
|
container.mAppearEmptyTimeout.run();
|
||||||
|
|||||||
Reference in New Issue
Block a user