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

Commit 5f0e28fb authored by Bryce Lee's avatar Bryce Lee
Browse files

Properly handle start results when using wait flag.

Previously, delivering an intent to the foreground activity was
inccorectly classified as START_TASK_TO_FRONT, rather than
START_DELIVERED_TO_TOP. In the direct case, this occurs when the
target activity is already in front. In the indirect case, this
can occur when the target activity is in the background and
launched by a trampoline in the same task. The trampoline launch
will bring forward the task. However, since it is not focusable
(finishing at the time the second launch occurs), the target
activity is already on top.

This changelist rectifies this issue by first handling
START_DELIVERED_TO_TOP in the direct wait case. This includes
setting the who value of result to the started real activity. This
changelist addresses the indirect case by reporting delivered to
top in the case there are waiting activities after a result.

Fixes: 72654906
Test: atest CtsActivityManagerDeviceTestCases:ActivityManagerAmStartOptionsTests
Test: atest FrameworksServicesTests:com.android.server.am.ActivityStackSupervisorTests
Change-Id: I65043e35ef8a20f90bf7cccbf4f1caf110cca246
parent 609017b2
Loading
Loading
Loading
Loading
+19 −6
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
@@ -1133,19 +1134,31 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
        }
    }

    void reportTaskToFrontNoLaunch(ActivityRecord r) {
    void reportWaitingActivityLaunchedIfNeeded(ActivityRecord r, int result) {
        if (mWaitingActivityLaunched.isEmpty()) {
            return;
        }

        if (result != START_DELIVERED_TO_TOP && result != START_TASK_TO_FRONT) {
            return;
        }

        boolean changed = false;

        for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
            WaitResult w = mWaitingActivityLaunched.remove(i);
            if (w.who == null) {
                changed = true;
                // Set result to START_TASK_TO_FRONT so that startActivityMayWait() knows that
                // the starting activity ends up moving another activity to front, and it should
                // wait for this new activity to become visible instead.
                // Do not modify other fields.
                w.result = START_TASK_TO_FRONT;
                w.result = result;

                // Unlike START_TASK_TO_FRONT, When an intent is delivered to top, there
                // will be no followup launch signals. Assign the result and launched component.
                if (result == START_DELIVERED_TO_TOP) {
                    w.who = r.realActivity;
                }
            }
        }

        if (changed) {
            mService.notifyAll();
        }
+44 −32
Original line number Diff line number Diff line
@@ -879,11 +879,11 @@ class ActivityStarter {
        }

        // We're waiting for an activity launch to finish, but that activity simply
        // brought another activity to front. Let startActivityMayWait() know about
        // this, so it waits for the new activity to become visible instead.
        if (result == START_TASK_TO_FRONT && !mSupervisor.mWaitingActivityLaunched.isEmpty()) {
            mSupervisor.reportTaskToFrontNoLaunch(mStartActivity);
        }
        // brought another activity to front. We must also handle the case where the task is already
        // in the front as a result of the trampoline activity being in the same task (it will be
        // considered focused as the trampoline will be finished). Let startActivityMayWait() know
        // about this, so it waits for the new activity to become visible instead.
        mSupervisor.reportWaitingActivityLaunchedIfNeeded(r, result);

        ActivityStack startedActivityStack = null;
        final ActivityStack currentStack = r.getStack();
@@ -1076,7 +1076,11 @@ class ActivityStarter {

            if (outResult != null) {
                outResult.result = res;
                if (res == ActivityManager.START_SUCCESS) {

                final ActivityRecord r = outRecord[0];

                switch(res) {
                    case START_SUCCESS: {
                        mSupervisor.mWaitingActivityLaunched.add(outResult);
                        do {
                            try {
@@ -1088,12 +1092,18 @@ class ActivityStarter {
                        if (outResult.result == START_TASK_TO_FRONT) {
                            res = START_TASK_TO_FRONT;
                        }
                        break;
                    }
                if (res == START_TASK_TO_FRONT) {
                    final ActivityRecord r = outRecord[0];

                    // ActivityRecord may represent a different activity, but it should not be in
                    // the resumed state.
                    case START_DELIVERED_TO_TOP: {
                        outResult.timeout = false;
                        outResult.who = r.realActivity;
                        outResult.totalTime = 0;
                        outResult.thisTime = 0;
                        break;
                    }
                    case START_TASK_TO_FRONT: {
                        // ActivityRecord may represent a different activity, but it should not be
                        // in the resumed state.
                        if (r.nowVisible && r.state == RESUMED) {
                            outResult.timeout = false;
                            outResult.who = r.realActivity;
@@ -1110,6 +1120,8 @@ class ActivityStarter {
                                }
                            } while (!outResult.timeout && outResult.who == null);
                        }
                        break;
                    }
                }
            }

+35 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.am;

import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -25,6 +27,8 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;

import android.app.ActivityManager;
import android.app.WaitResult;
import android.content.ComponentName;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
@@ -45,7 +49,7 @@ import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS
 * Tests for the {@link ActivityStackSupervisor} class.
 *
 * Build/Install/Run:
 *  bit FrameworksServicesTests:com.android.server.am.ActivityStackSupervisorTests
 *  atest FrameworksServicesTests:com.android.server.am.ActivityStackSupervisorTests
 */
@MediumTest
@Presubmit
@@ -146,4 +150,34 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase {

        assertFalse(mSupervisor.mStoppingActivities.contains(firstActivity));
    }

    /**
     * Ensures that waiting results are notified of launches.
     */
    @Test
    public void testReportWaitingActivityLaunchedIfNeeded() throws Exception {
        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
                .setStack(mFullscreenStack).build();

        // #notifyAll will be called on the ActivityManagerService. we must hold the object lock
        // when this happens.
        synchronized (mSupervisor.mService) {
            final WaitResult taskToFrontWait = new WaitResult();
            mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait);
            mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, START_TASK_TO_FRONT);

            assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty());
            assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT);
            assertEquals(taskToFrontWait.who, null);

            final WaitResult deliverToTopWait = new WaitResult();
            mSupervisor.mWaitingActivityLaunched.add(deliverToTopWait);
            mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity,
                    START_DELIVERED_TO_TOP);

            assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty());
            assertEquals(deliverToTopWait.result, START_DELIVERED_TO_TOP);
            assertEquals(deliverToTopWait.who, firstActivity.realActivity);
        }
    }
}