Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f2a5491f authored by Louis Chang's avatar Louis Chang Committed by Android (Google) Code Review
Browse files

Merge "Prevent waiting if no new activity instance launched" into tm-qpr-dev

parents bc6a6e33 5a36e6bc
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -783,6 +783,17 @@ public class Instrumentation {
            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,
                            Activity activity,
                            Intent intent) {
@@ -1344,6 +1355,28 @@ public class Instrumentation {
        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) {
        if (mWaitingActivities != null) {
            synchronized (mSync) {
@@ -1802,6 +1835,7 @@ public class Instrumentation {
                    who.getOpPackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                    target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
            notifyStartActivityResult(result, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
@@ -1876,6 +1910,7 @@ public class Instrumentation {
            int result = ActivityTaskManager.getService().startActivities(whoThread,
                    who.getOpPackageName(), who.getAttributionTag(), intents, resolvedTypes,
                    token, options, userId);
            notifyStartActivityResult(result, options);
            checkStartActivityResult(result, intents[0]);
            return result;
        } catch (RemoteException e) {
@@ -1947,6 +1982,7 @@ public class Instrumentation {
                    who.getOpPackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,
                    requestCode, 0, null, options);
            notifyStartActivityResult(result, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
@@ -2017,6 +2053,7 @@ public class Instrumentation {
                    who.getOpPackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho,
                    requestCode, 0, null, options, user.getIdentifier());
            notifyStartActivityResult(result, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
@@ -2068,6 +2105,7 @@ public class Instrumentation {
                            token, target != null ? target.mEmbeddedID : null,
                            requestCode, 0, null, options,
                            ignoreTargetSecurity, userId);
            notifyStartActivityResult(result, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
@@ -2115,6 +2153,7 @@ public class Instrumentation {
            int result = appTask.startActivity(whoThread.asBinder(), who.getOpPackageName(),
                    who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), options);
            notifyStartActivityResult(result, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
+31 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

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_UNDEFINED;

@@ -97,6 +98,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
    private final Handler mHandler;
    private final Object mLock = new Object();
    private final ActivityStartMonitor mActivityStartMonitor;

    public SplitController() {
        final MainThreadExecutor executor = new MainThreadExecutor();
@@ -108,7 +110,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                new LifecycleCallbacks());
        // Intercept activity starts to route activities to new containers if necessary.
        Instrumentation instrumentation = activityThread.getInstrumentation();
        instrumentation.addMonitor(new ActivityStartMonitor());
        mActivityStartMonitor = new ActivityStartMonitor();
        instrumentation.addMonitor(mActivityStartMonitor);
    }

    /** Updates the embedding rules applied to future activity launches. */
@@ -1385,6 +1388,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        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
     * 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
     * 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
        public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
@@ -1564,11 +1575,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                    // the dedicated container.
                    options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                            launchedInTaskFragment.getTaskFragmentToken());
                    mCurrentIntent = intent;
                }
            }

            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;
        }
    }

    /**
+24 −3
Original line number Diff line number Diff line
@@ -198,6 +198,22 @@ class TaskFragmentContainer {
        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) {
        if (mInfo != null && mInfo.getActivities().contains(token)) {
            return true;
@@ -230,13 +246,18 @@ class TaskFragmentContainer {

    void setInfo(@NonNull TaskFragmentInfo info) {
        if (!mIsFinished && mInfo == null && info.isEmpty()) {
            // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if it is
            // still empty after timeout.
            // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
            // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
            // it is still empty after timeout.
            mAppearEmptyTimeout = () -> {
                mAppearEmptyTimeout = null;
                mController.onTaskFragmentAppearEmptyTimeout(this);
            };
            if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
                mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
            } else {
                mAppearEmptyTimeout.run();
            }
        } else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
            mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
            mAppearEmptyTimeout = null;
+21 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

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_UNDEFINED;

@@ -292,6 +293,26 @@ public class SplitControllerTest {
        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
    public void testOnActivityCreated() {
        mSplitController.onActivityCreated(mActivity);
+9 −8
Original line number Diff line number Diff line
@@ -209,21 +209,21 @@ public class TaskFragmentContainerTest {

        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.
        final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
        container.mInfo = null;
        doReturn(true).when(info).isEmpty();
        container.setInfo(info);

        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.
        doReturn(false).when(info).isEmpty();
        container.setInfo(info);
@@ -232,6 +232,7 @@ public class TaskFragmentContainerTest {

        // Running the timeout will call into SplitController.onTaskFragmentAppearEmptyTimeout.
        container.mInfo = null;
        container.setPendingAppearedIntent(mIntent);
        doReturn(true).when(info).isEmpty();
        container.setInfo(info);
        container.mAppearEmptyTimeout.run();