diff options
Diffstat (limited to 'libs')
29 files changed, 626 insertions, 247 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 02af9160301c..724a50fbdb04 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; +import static android.window.TaskFragmentOrganizer.getTransitionType; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; @@ -109,7 +110,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final Handler mHandler; - private final Object mLock = new Object(); + final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; public SplitController() { @@ -209,8 +210,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - // Notify the server, and the server should apply the WindowContainerTransaction. - mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct); + // Notify the server, and the server should apply and merge the + // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction. + mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct, + getTransitionType(wct), false /* shouldApplyIndependently */); updateCallbackIfNecessary(); } } @@ -221,6 +224,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is created. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @@ -245,6 +251,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is changed. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @@ -430,6 +439,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * transaction operation. * @param exception exception from the server side. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @@ -571,7 +583,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } if (!isOnReparent && getContainerWithActivity(activity) == null - && getInitialTaskFragmentToken(activity) != null) { + && getTaskFragmentTokenFromActivityClientRecord(activity) != null) { // We can't find the new launched activity in any recorded container, but it is // currently placed in an embedded TaskFragment. This can happen in two cases: // 1. the activity is embedded in another app. @@ -792,6 +804,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Checks if there is a rule to split the two activities. If there is one, puts them into split * and returns {@code true}. Otherwise, returns {@code false}. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { @@ -851,11 +866,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @VisibleForTesting + @GuardedBy("mLock") void onActivityDestroyed(@NonNull Activity activity) { // Remove any pending appeared activity, as the server won't send finished activity to the // organizer. for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - mTaskContainers.valueAt(i).cleanupPendingAppearedActivity(activity); + mTaskContainers.valueAt(i).onActivityDestroyed(activity); } // We didn't trigger the callback if there were any pending appeared activities, so check // again after the pending is removed. @@ -866,23 +882,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Called when we have been waiting too long for the TaskFragment to become non-empty after * creation. */ + @GuardedBy("mLock") void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) { - synchronized (mLock) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - onTaskFragmentAppearEmptyTimeout(wct, container); - mPresenter.applyTransaction(wct); - } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + onTaskFragmentAppearEmptyTimeout(wct, container); + // Can be applied independently as a timeout callback. + mPresenter.applyTransaction(wct, getTransitionType(wct), + true /* shouldApplyIndependently */); } /** * Called when we have been waiting too long for the TaskFragment to become non-empty after * creation. */ + @GuardedBy("mLock") void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { - synchronized (mLock) { - mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); - } + mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } /** @@ -1590,15 +1606,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** - * Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it - * after creation because the activity could be reparented. + * Gets the token of the TaskFragment that embedded this activity. It is available as soon as + * the activity is created and attached, so it can be used during {@link #onActivityCreated} + * before the server notifies the organizer to avoid racing condition. */ @VisibleForTesting @Nullable - IBinder getInitialTaskFragmentToken(@NonNull Activity activity) { + IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) { final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread() .getActivityClient(activity.getActivityToken()); - return record != null ? record.mInitialTaskFragmentToken : null; + return record != null ? record.mTaskFragmentToken : null; } /** @@ -1676,7 +1693,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Nullable Bundle savedInstanceState) { synchronized (mLock) { final IBinder activityToken = activity.getActivityToken(); - final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity); + final IBinder initialTaskFragmentToken = + getTaskFragmentTokenFromActivityClientRecord(activity); // If the activity is not embedded, then it will not have an initial task fragment // token so no further action is needed. if (initialTaskFragmentToken == null) { @@ -1711,7 +1729,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final WindowContainerTransaction wct = new WindowContainerTransaction(); SplitController.this.onActivityCreated(wct, activity); - mPresenter.applyTransaction(wct); + // The WCT should be applied and merged to the activity launch transition. + mPresenter.applyTransaction(wct, getTransitionType(wct), + false /* shouldApplyIndependently */); } } @@ -1720,7 +1740,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final WindowContainerTransaction wct = new WindowContainerTransaction(); SplitController.this.onActivityConfigurationChanged(wct, activity); - mPresenter.applyTransaction(wct); + // The WCT should be applied and merged to the Task change transition so that the + // placeholder is launched in the same transition. + mPresenter.applyTransaction(wct, getTransitionType(wct), + false /* shouldApplyIndependently */); } } @@ -1772,7 +1795,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, launchingActivity); if (launchedInTaskFragment != null) { - mPresenter.applyTransaction(wct); + // Make sure the WCT is applied immediately instead of being queued so that the + // TaskFragment will be ready before activity attachment. + mPresenter.applyTransaction(wct, getTransitionType(wct), + false /* shouldApplyIndependently */); // Amend the request to let the WM know that the activity should be placed in // the dedicated container. options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 2b069d72e46f..2ef8e4c64855 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -38,6 +38,7 @@ import android.view.WindowInsets; import android.view.WindowMetrics; import android.window.WindowContainerTransaction; +import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -171,6 +172,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * created and the activity will be re-parented to it. * @param rule The split rule to be applied to the container. */ + @GuardedBy("mController.mLock") void createNewSplitContainer(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) { @@ -187,8 +189,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( secondaryActivity); TaskFragmentContainer containerToAvoid = primaryContainer; - if (rule.shouldClearTop() && curSecondaryContainer != null) { - // Do not reuse the current TaskFragment if the rule is to clear top. + if (curSecondaryContainer != null + && (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) { + // Do not reuse the current TaskFragment if the rule is to clear top, or if it is below + // the primary TaskFragment. containerToAvoid = curSecondaryContainer; } final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 77e26c07f304..b5636777568e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -137,6 +137,13 @@ class TaskContainer { return mContainers.isEmpty() && mFinishedContainer.isEmpty(); } + /** Called when the activity is destroyed. */ + void onActivityDestroyed(@NonNull Activity activity) { + for (TaskFragmentContainer container : mContainers) { + container.onActivityDestroyed(activity); + } + } + /** Removes the pending appeared activity from all TaskFragments in this Task. */ void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { for (TaskFragmentContainer container : mContainers) { @@ -162,4 +169,8 @@ class TaskContainer { } return null; } + + int indexOf(@NonNull TaskFragmentContainer child) { + return mContainers.indexOf(child); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 11c0db320646..626e0d990a6d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; +import android.app.ActivityThread; import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; import android.graphics.Rect; @@ -28,6 +29,7 @@ import android.util.Size; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -189,6 +191,19 @@ class TaskFragmentContainer { // Remove the pending activity from other TaskFragments. mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity); mPendingAppearedActivities.add(pendingAppearedActivity); + updateActivityClientRecordTaskFragmentToken(pendingAppearedActivity); + } + + /** + * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the + * activity. This makes sure the token is up-to-date if the activity is relaunched later. + */ + private void updateActivityClientRecordTaskFragmentToken(@NonNull Activity activity) { + final ActivityThread.ActivityClientRecord record = ActivityThread + .currentActivityThread().getActivityClient(activity.getActivityToken()); + if (record != null) { + record.mTaskFragmentToken = mToken; + } } void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { @@ -196,8 +211,29 @@ class TaskFragmentContainer { } void clearPendingAppearedActivities() { + final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities); + // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the + // current TaskFragment. mPendingAppearedActivities.clear(); mPendingAppearedIntent = null; + + // For removed pending activities, we need to update the them to their previous containers. + for (Activity activity : cleanupActivities) { + final TaskFragmentContainer curContainer = mController.getContainerWithActivity( + activity); + if (curContainer != null) { + curContainer.updateActivityClientRecordTaskFragmentToken(activity); + } + } + } + + /** Called when the activity is destroyed. */ + void onActivityDestroyed(@NonNull Activity activity) { + removePendingAppearedActivity(activity); + if (mInfo != null) { + // Remove the activity now because there can be a delay before the server callback. + mInfo.getActivities().remove(activity.getActivityToken()); + } } @Nullable @@ -251,6 +287,7 @@ class TaskFragmentContainer { return mInfo; } + @GuardedBy("mController.mLock") void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) { if (!mIsFinished && mInfo == null && info.isEmpty()) { // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no @@ -258,10 +295,12 @@ class TaskFragmentContainer { // it is still empty after timeout. if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) { mAppearEmptyTimeout = () -> { - mAppearEmptyTimeout = null; - // Call without the pass-in wct when timeout. We need to applyWct directly - // in this case. - mController.onTaskFragmentAppearEmptyTimeout(this); + synchronized (mController.mLock) { + mAppearEmptyTimeout = null; + // Call without the pass-in wct when timeout. We need to applyWct directly + // in this case. + mController.onTaskFragmentAppearEmptyTimeout(this); + } }; mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS); } else { @@ -501,6 +540,18 @@ class TaskFragmentContainer { return new Size(maxMinWidth, maxMinHeight); } + /** Whether the current TaskFragment is above the {@code other} TaskFragment. */ + boolean isAbove(@NonNull TaskFragmentContainer other) { + if (mTaskContainer != other.mTaskContainer) { + throw new IllegalArgumentException( + "Trying to compare two TaskFragments in different Task."); + } + if (this == other) { + throw new IllegalArgumentException("Trying to compare a TaskFragment with itself."); + } + return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other); + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index d0eaf34274aa..58a627bafa16 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -33,7 +32,6 @@ import static org.mockito.Mockito.never; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; -import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; @@ -67,10 +65,7 @@ public class JetpackTaskFragmentOrganizerTest { private WindowContainerTransaction mTransaction; @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; - @Mock private SplitController mSplitController; - @Mock - private Handler mHandler; private JetpackTaskFragmentOrganizer mOrganizer; @Before @@ -78,8 +73,9 @@ public class JetpackTaskFragmentOrganizerTest { MockitoAnnotations.initMocks(this); mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback); mOrganizer.registerOrganizer(); + mSplitController = new SplitController(); spyOn(mOrganizer); - doReturn(mHandler).when(mSplitController).getHandler(); + spyOn(mSplitController); } @Test diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index f7436108d3e3..58870a66feea 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -127,7 +127,7 @@ public class SplitControllerTest { mSplitPresenter = mSplitController.mPresenter; spyOn(mSplitController); spyOn(mSplitPresenter); - doNothing().when(mSplitPresenter).applyTransaction(any()); + doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean()); final Configuration activityConfig = new Configuration(); activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); @@ -930,7 +930,8 @@ public class SplitControllerTest { @Test public void testResolveActivityToContainer_inUnknownTaskFragment() { - doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity); + doReturn(new Binder()).when(mSplitController) + .getTaskFragmentTokenFromActivityClientRecord(mActivity); // No need to handle when the new launched activity is in an unknown TaskFragment. assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity, @@ -1000,7 +1001,8 @@ public class SplitControllerTest { mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentAppeared(any(), eq(info)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1014,7 +1016,8 @@ public class SplitControllerTest { mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentInfoChanged(any(), eq(info)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1028,7 +1031,8 @@ public class SplitControllerTest { mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentVanished(any(), eq(info)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1043,7 +1047,8 @@ public class SplitControllerTest { verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID), eq(taskConfig)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1062,7 +1067,8 @@ public class SplitControllerTest { verify(mSplitController).onTaskFragmentError(any(), eq(errorToken), eq(info), eq(opType), eq(exception)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1078,7 +1084,8 @@ public class SplitControllerTest { verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent), eq(activityToken)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } /** Creates a mock activity in the organizer process. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index da724d9d9311..25f0e25eec75 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -39,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -247,6 +248,26 @@ public class SplitPresenterTest { verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken()); } + @Test + public void testCreateNewSplitContainer_secondaryAbovePrimary() { + final Activity secondaryActivity = createMockActivity(); + final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID); + final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); + final SplitPairRule rule = new SplitPairRule.Builder(pair -> + pair.first == mActivity && pair.second == secondaryActivity, pair -> false, + metrics -> true) + .setShouldClearTop(false) + .build(); + + mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule); + + assertEquals(primaryTf, mController.getContainerWithActivity(mActivity)); + final TaskFragmentContainer secondaryTf = mController.getContainerWithActivity( + secondaryActivity); + assertNotEquals(bottomTf, secondaryTf); + assertTrue(secondaryTf.isAbove(primaryTf)); + } + private Activity createMockActivity() { final Activity activity = mock(Activity.class); final Configuration activityConfig = new Configuration(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 6cbecff81be5..082774e048a9 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -288,6 +288,18 @@ public class TaskFragmentContainerTest { } @Test + public void testIsAbove() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController); + final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController); + + assertTrue(container1.isAbove(container0)); + assertFalse(container0.isAbove(container1)); + } + + @Test public void testGetBottomMostActivity() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, @@ -304,6 +316,25 @@ public class TaskFragmentContainerTest { assertEquals(activity, container.getBottomMostActivity()); } + @Test + public void testOnActivityDestroyed() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController); + container.addPendingAppearedActivity(mActivity); + final List<IBinder> activities = new ArrayList<>(); + activities.add(mActivity.getActivityToken()); + doReturn(activities).when(mInfo).getActivities(); + container.setInfo(mTransaction, mInfo); + + assertTrue(container.hasActivity(mActivity.getActivityToken())); + + taskContainer.onActivityDestroyed(mActivity); + + // It should not contain the destroyed Activity. + assertFalse(container.hasActivity(mActivity.getActivityToken())); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { final Activity activity = mock(Activity.class); diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml index 6fcd1de892a3..ddfb5c27e701 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml @@ -15,15 +15,12 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> + android:width="@dimen/letterbox_education_dialog_title_icon_width" + android:height="@dimen/letterbox_education_dialog_title_icon_height" + android:viewportWidth="45" + android:viewportHeight="44"> + <path android:fillColor="@color/letterbox_education_accent_primary" - android:fillType="evenOdd" - android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" /> - <path - android:fillColor="@color/letterbox_education_accent_primary" - android:pathData="M 17 14 L 31 14 Q 32 14 32 15 L 32 33 Q 32 34 31 34 L 17 34 Q 16 34 16 33 L 16 15 Q 16 14 17 14 Z" /> + android:pathData="M11 40H19C19 42.2 17.2 44 15 44C12.8 44 11 42.2 11 40ZM7 38L23 38V34L7 34L7 38ZM30 19C30 26.64 24.68 30.72 22.46 32L7.54 32C5.32 30.72 0 26.64 0 19C0 10.72 6.72 4 15 4C23.28 4 30 10.72 30 19ZM26 19C26 12.94 21.06 8 15 8C8.94 8 4 12.94 4 19C4 23.94 6.98 26.78 8.7 28L21.3 28C23.02 26.78 26 23.94 26 19ZM39.74 14.74L37 16L39.74 17.26L41 20L42.26 17.26L45 16L42.26 14.74L41 12L39.74 14.74ZM35 12L36.88 7.88L41 6L36.88 4.12L35 0L33.12 4.12L29 6L33.12 7.88L35 12Z" /> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml index cbfcfd06e3b7..22a8f39ca687 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml @@ -15,18 +15,16 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> + android:width="@dimen/letterbox_education_dialog_icon_width" + android:height="@dimen/letterbox_education_dialog_icon_height" + android:viewportWidth="40" + android:viewportHeight="32"> + <path android:fillColor="@color/letterbox_education_text_secondary" android:fillType="evenOdd" - android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" /> + android:pathData="M4 0C1.79086 0 0 1.79086 0 4V28C0 30.2091 1.79086 32 4 32H36C38.2091 32 40 30.2091 40 28V4C40 1.79086 38.2091 0 36 0H4ZM36 4H4V28H36V4Z" /> <path android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M 14 22 H 30 V 26 H 14 V 22 Z" /> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M26 16L34 24L26 32V16Z" /> + android:pathData="M19.98 8L17.16 10.82L20.32 14L12 14V18H20.32L17.14 21.18L19.98 24L28 16.02L19.98 8Z" /> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml deleted file mode 100644 index 469eb1e14849..000000000000 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:fillType="evenOdd" - android:pathData="M22.56 2H26C37.02 2 46 10.98 46 22H42C42 14.44 36.74 8.1 29.7 6.42L31.74 10L28.26 12L22.56 2ZM22 46H25.44L19.74 36L16.26 38L18.3 41.58C11.26 39.9 6 33.56 6 26H2C2 37.02 10.98 46 22 46ZM20.46 12L36 27.52L27.54 36L12 20.48L20.46 12ZM17.64 9.18C18.42 8.4 19.44 8 20.46 8C21.5 8 22.52 8.4 23.3 9.16L38.84 24.7C40.4 26.26 40.4 28.78 38.84 30.34L30.36 38.82C29.58 39.6 28.56 40 27.54 40C26.52 40 25.5 39.6 24.72 38.82L9.18 23.28C7.62 21.72 7.62 19.2 9.18 17.64L17.64 9.18Z" /> -</vector> diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml index dcb8aed05c9c..15e65f716b20 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml @@ -15,18 +15,12 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> + android:width="@dimen/letterbox_education_dialog_icon_width" + android:height="@dimen/letterbox_education_dialog_icon_height" + android:viewportWidth="40" + android:viewportHeight="32"> + <path android:fillColor="@color/letterbox_education_text_secondary" - android:fillType="evenOdd" - android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" /> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M6 16C6 14.8954 6.89543 14 8 14H21C22.1046 14 23 14.8954 23 16V32C23 33.1046 22.1046 34 21 34H8C6.89543 34 6 33.1046 6 32V16Z" /> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M25 16C25 14.8954 25.8954 14 27 14H40C41.1046 14 42 14.8954 42 16V32C42 33.1046 41.1046 34 40 34H27C25.8954 34 25 33.1046 25 32V16Z" /> + android:pathData="M40 28L40 4C40 1.8 38.2 -7.86805e-08 36 -1.74846e-07L26 -6.11959e-07C23.8 -7.08124e-07 22 1.8 22 4L22 28C22 30.2 23.8 32 26 32L36 32C38.2 32 40 30.2 40 28ZM14 28L4 28L4 4L14 4L14 28ZM18 28L18 4C18 1.8 16.2 -1.04033e-06 14 -1.1365e-06L4 -1.57361e-06C1.8 -1.66978e-06 -7.86805e-08 1.8 -1.74846e-07 4L-1.22392e-06 28C-1.32008e-06 30.2 1.8 32 4 32L14 32C16.2 32 18 30.2 18 28Z" /> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml index cd1d99ae58b0..c65f24d84e37 100644 --- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml +++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml @@ -26,7 +26,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_marginBottom="12dp"/> + android:layout_marginBottom="20dp"/> <TextView android:id="@+id/letterbox_education_dialog_action_text" diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml index 95923763d889..3a44eb9089dd 100644 --- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml +++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml @@ -50,13 +50,16 @@ android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" - android:padding="24dp"> + android:paddingTop="32dp" + android:paddingBottom="32dp" + android:paddingLeft="56dp" + android:paddingRight="56dp"> <ImageView - android:layout_width="@dimen/letterbox_education_dialog_icon_size" - android:layout_height="@dimen/letterbox_education_dialog_icon_size" - android:layout_marginBottom="12dp" - android:src="@drawable/letterbox_education_ic_letterboxed_app"/> + android:layout_width="@dimen/letterbox_education_dialog_title_icon_width" + android:layout_height="@dimen/letterbox_education_dialog_title_icon_height" + android:layout_marginBottom="17dp" + android:src="@drawable/letterbox_education_ic_light_bulb"/> <TextView android:id="@+id/letterbox_education_dialog_title" @@ -68,16 +71,6 @@ android:textColor="@color/compat_controls_text" android:textSize="24sp"/> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:lineSpacingExtra="4sp" - android:text="@string/letterbox_education_dialog_subtext" - android:textAlignment="center" - android:textColor="@color/letterbox_education_text_secondary" - android:textSize="14sp"/> - <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -88,16 +81,16 @@ <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - app:icon="@drawable/letterbox_education_ic_screen_rotation" - app:text="@string/letterbox_education_screen_rotation_text"/> + app:icon="@drawable/letterbox_education_ic_reposition" + app:text="@string/letterbox_education_reposition_text"/> <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart= "@dimen/letterbox_education_dialog_space_between_actions" - app:icon="@drawable/letterbox_education_ic_reposition" - app:text="@string/letterbox_education_reposition_text"/> + app:icon="@drawable/letterbox_education_ic_split_screen" + app:text="@string/letterbox_education_split_screen_text"/> </LinearLayout> @@ -105,7 +98,7 @@ android:id="@+id/letterbox_education_dialog_dismiss_button" android:layout_width="match_parent" android:layout_height="56dp" - android:layout_marginTop="48dp" + android:layout_marginTop="40dp" android:background= "@drawable/letterbox_education_dismiss_button_background_ripple" android:text="@string/letterbox_education_got_it" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index e24934ba2b0a..5696b8dfa8e3 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -249,8 +249,17 @@ <!-- The corner radius of the letterbox education dialog. --> <dimen name="letterbox_education_dialog_corner_radius">28dp</dimen> - <!-- The size of an icon in the letterbox education dialog. --> - <dimen name="letterbox_education_dialog_icon_size">48dp</dimen> + <!-- The width of the top icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_title_icon_width">45dp</dimen> + + <!-- The height of the top icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_title_icon_height">44dp</dimen> + + <!-- The width of an icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_icon_width">40dp</dimen> + + <!-- The height of an icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_icon_height">32dp</dimen> <!-- The fixed width of the dialog if there is enough space in the parent. --> <dimen name="letterbox_education_dialog_width">472dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 96778a985700..1d1162daf249 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -172,16 +172,13 @@ <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string> <!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_dialog_title">Some apps work best in portrait</string> + <string name="letterbox_education_dialog_title">See and do more</string> - <!-- The subtext of the letterbox education dialog. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_dialog_subtext">Try one of these options to make the most of your space</string> - - <!-- Description of the rotate screen action. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_screen_rotation_text">Rotate your device to go full screen</string> + <!-- Description of the split screen action. [CHAR LIMIT=NONE] --> + <string name="letterbox_education_split_screen_text">Drag in another app for split-screen</string> <!-- Description of the reposition app action. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_reposition_text">Double-tap next to an app to reposition it</string> + <string name="letterbox_education_reposition_text">Double-tap outside an app to reposition it</string> <!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] --> <string name="letterbox_education_got_it">Got it</string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java new file mode 100644 index 000000000000..d2760022a015 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java @@ -0,0 +1,112 @@ +/* + * 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 com.android.wm.shell; + +import com.android.wm.shell.protolog.ShellProtoLogImpl; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellInit; + +import java.io.PrintWriter; +import java.util.Arrays; + +/** + * Controls the {@link ShellProtoLogImpl} in WMShell via adb shell commands. + * + * Use with {@code adb shell dumpsys activity service SystemUIService WMShell protolog ...}. + */ +public class ProtoLogController implements ShellCommandHandler.ShellCommandActionHandler { + private final ShellCommandHandler mShellCommandHandler; + private final ShellProtoLogImpl mShellProtoLog; + + public ProtoLogController(ShellInit shellInit, + ShellCommandHandler shellCommandHandler) { + shellInit.addInitCallback(this::onInit, this); + mShellCommandHandler = shellCommandHandler; + mShellProtoLog = ShellProtoLogImpl.getSingleInstance(); + } + + void onInit() { + mShellCommandHandler.addCommandCallback("protolog", this, this); + } + + @Override + public boolean onShellCommand(String[] args, PrintWriter pw) { + switch (args[0]) { + case "status": { + pw.println(mShellProtoLog.getStatus()); + return true; + } + case "start": { + mShellProtoLog.startProtoLog(pw); + return true; + } + case "stop": { + mShellProtoLog.stopProtoLog(pw, true /* writeToFile */); + return true; + } + case "enable-text": { + String[] groups = Arrays.copyOfRange(args, 1, args.length); + int result = mShellProtoLog.startTextLogging(groups, pw); + if (result == 0) { + pw.println("Starting logging on groups: " + Arrays.toString(groups)); + return true; + } + return false; + } + case "disable-text": { + String[] groups = Arrays.copyOfRange(args, 1, args.length); + int result = mShellProtoLog.stopTextLogging(groups, pw); + if (result == 0) { + pw.println("Stopping logging on groups: " + Arrays.toString(groups)); + return true; + } + return false; + } + case "enable": { + String[] groups = Arrays.copyOfRange(args, 1, args.length); + return mShellProtoLog.startTextLogging(groups, pw) == 0; + } + case "disable": { + String[] groups = Arrays.copyOfRange(args, 1, args.length); + return mShellProtoLog.stopTextLogging(groups, pw) == 0; + } + default: { + pw.println("Invalid command: " + args[0]); + printShellCommandHelp(pw, ""); + return false; + } + } + } + + @Override + public void printShellCommandHelp(PrintWriter pw, String prefix) { + pw.println(prefix + "status"); + pw.println(prefix + " Get current ProtoLog status."); + pw.println(prefix + "start"); + pw.println(prefix + " Start proto logging."); + pw.println(prefix + "stop"); + pw.println(prefix + " Stop proto logging and flush to file."); + pw.println(prefix + "enable [group...]"); + pw.println(prefix + " Enable proto logging for given groups."); + pw.println(prefix + "disable [group...]"); + pw.println(prefix + " Disable proto logging for given groups."); + pw.println(prefix + "enable-text [group...]"); + pw.println(prefix + " Enable logcat logging for given groups."); + pw.println(prefix + "disable-text [group...]"); + pw.println(prefix + " Disable logcat logging for given groups."); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b7959eb629c1..4c85d2090ec5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -80,7 +80,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public static final int PARALLAX_DISMISSING = 1; public static final int PARALLAX_ALIGN_CENTER = 2; - private static final int FLING_ANIMATION_DURATION = 250; + private static final int FLING_RESIZE_DURATION = 250; + private static final int FLING_SWITCH_DURATION = 350; + private static final int FLING_ENTER_DURATION = 350; + private static final int FLING_EXIT_DURATION = 350; private final int mDividerWindowWidth; private final int mDividerInsets; @@ -93,6 +96,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private final Rect mBounds1 = new Rect(); // Bounds2 final position should be always at bottom or right private final Rect mBounds2 = new Rect(); + // The temp bounds outside of display bounds for side stage when split screen inactive to avoid + // flicker next time active split screen. + private final Rect mInvisibleBounds = new Rect(); private final Rect mWinBounds1 = new Rect(); private final Rect mWinBounds2 = new Rect(); private final SplitLayoutHandler mSplitLayoutHandler; @@ -141,6 +147,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange resetDividerPosition(); mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide); + + updateInvisibleRect(); } private int getDividerInsets(Resources resources, Display display) { @@ -239,6 +247,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange rect.offset(-mRootBounds.left, -mRootBounds.top); } + /** Gets bounds size equal to root bounds but outside of screen, used for position side stage + * when split inactive to avoid flicker when next time active. */ + public void getInvisibleBounds(Rect rect) { + rect.set(mInvisibleBounds); + } + /** Returns leash of the current divider bar. */ @Nullable public SurfaceControl getDividerLeash() { @@ -258,6 +272,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange : (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom)); } + private void updateInvisibleRect() { + mInvisibleBounds.set(mRootBounds.left, mRootBounds.top, + isLandscape() ? mRootBounds.right / 2 : mRootBounds.right, + isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2); + mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0, + isLandscape() ? 0 : mRootBounds.bottom); + } + /** Applies new configuration, returns {@code false} if there's no effect to the layout. */ public boolean updateConfiguration(Configuration configuration) { // Update the split bounds when necessary. Besides root bounds changed, split bounds need to @@ -283,6 +305,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRotation = rotation; mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); initDividerPosition(mTempRect); + updateInvisibleRect(); return true; } @@ -405,6 +428,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mFreezeDividerWindow = freezeDividerWindow; } + /** Update current layout as divider put on start or end position. */ + public void setDividerAtBorder(boolean start) { + final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position + : mDividerSnapAlgorithm.getDismissEndTarget().position; + setDividePosition(pos, false /* applyLayoutChange */); + } + /** * Updates bounds with the passing position. Usually used to update recording bounds while * performing animation or dragging divider bar to resize the splits. @@ -449,17 +479,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { switch (snapTarget.flag) { case FLAG_DISMISS_START: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; case FLAG_DISMISS_END: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; default: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)); break; } @@ -514,12 +544,20 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void flingDividerToDismiss(boolean toEnd, int reason) { final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position : mDividerSnapAlgorithm.getDismissStartTarget().position; - flingDividePosition(getDividePosition(), target, + flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason)); } + /** Fling divider from current position to center position. */ + public void flingDividerToCenter() { + final int pos = mDividerSnapAlgorithm.getMiddleTarget().position; + flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION, + () -> setDividePosition(pos, true /* applyLayoutChange */)); + } + @VisibleForTesting - void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) { + void flingDividePosition(int from, int to, int duration, + @Nullable Runnable flingFinishedCallback) { if (from == to) { // No animation run, still callback to stop resizing. mSplitLayoutHandler.onLayoutSizeChanged(this); @@ -529,7 +567,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } ValueAnimator animator = ValueAnimator .ofInt(from, to) - .setDuration(FLING_ANIMATION_DURATION); + .setDuration(duration); animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); animator.addUpdateListener( animation -> updateDivideBounds((int) animation.getAnimatedValue())); @@ -586,7 +624,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange AnimatorSet set = new AnimatorSet(); set.playTogether(animator1, animator2, animator3); - set.setDuration(FLING_ANIMATION_DURATION); + set.setDuration(FLING_SWITCH_DURATION); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 7a736ccab5d1..c39602032170 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -27,6 +27,7 @@ import android.view.IWindowManager; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.ProtoLogController; import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; @@ -699,6 +700,7 @@ public abstract class WMShellBaseModule { Optional<ActivityEmbeddingController> activityEmbeddingOptional, Transitions transitions, StartingWindowController startingWindow, + ProtoLogController protoLogController, @ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) { return new Object(); } @@ -714,4 +716,12 @@ public abstract class WMShellBaseModule { static ShellCommandHandler provideShellCommandHandler() { return new ShellCommandHandler(); } + + @WMSingleton + @Provides + static ProtoLogController provideProtoLogController( + ShellInit shellInit, + ShellCommandHandler shellCommandHandler) { + return new ProtoLogController(shellInit, shellCommandHandler); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index dd50fa0817c2..fd4c85fad77f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -92,6 +92,12 @@ public class FreeformTaskTransitionHandler @Override + public void startRemoveTransition(WindowContainerTransaction wct) { + final int type = WindowManager.TRANSIT_CLOSE; + mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this)); + } + + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java index c947cf1b8cd1..8da4c6ab4b36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java @@ -40,4 +40,12 @@ public interface FreeformTaskTransitionStarter { * */ void startMinimizedModeTransition(WindowContainerTransaction wct); + + /** + * Starts close window transition + * + * @param wct the {@link WindowContainerTransaction} that closes the task + * + */ + void startRemoveTransition(WindowContainerTransaction wct); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index f1465f421c5b..e9f9bb5d7327 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -287,6 +287,9 @@ public class FullscreenTaskListener<T extends AutoCloseable> } private void releaseWindowDecor(T windowDecor) { + if (windowDecor == null) { + return; + } try { windowDecor.close(); } catch (Exception e) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 51921e747f1a..3714fe713288 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import android.app.PendingIntent; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.os.Bundle; import android.os.UserHandle; import android.view.RemoteAnimationAdapter; @@ -89,7 +90,7 @@ interface ISplitScreen { float splitRatio, in RemoteAnimationAdapter adapter) = 11; /** - * Start a pair of intent and task using legacy transition system. + * Starts a pair of intent and task using legacy transition system. */ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions, @@ -108,4 +109,11 @@ interface ISplitScreen { * does not expect split to currently be running. */ RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14; + + /** + * Starts a pair of shortcut and task using legacy transition system. + */ + oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId, + in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio, + in RemoteAnimationAdapter adapter) = 15; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 2117b69ebc2e..169e17b770f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -40,6 +40,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Bundle; import android.os.RemoteException; @@ -788,6 +789,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override + public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, + int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions, + @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { + executeRemoteCallWithTaskPermission(mController, + "startShortcutAndTaskWithLegacyTransition", (controller) -> + controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition( + shortcutInfo, taskId, mainOptions, sideOptions, sidePosition, + splitRatio, adapter)); + } + + @Override public void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 7e83d2fa0a0b..8d405f4f6fcf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -68,11 +69,11 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.WindowConfiguration; import android.content.Context; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; @@ -84,7 +85,6 @@ import android.util.Log; import android.util.Slog; import android.view.Choreographer; import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; @@ -488,13 +488,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); - // If split still not active, apply windows bounds first to avoid surface reset to - // wrong pos by SurfaceAnimator from wms. - // TODO(b/223325631): check is it still necessary after improve enter transition done. - if (!mMainStage.isActive()) { - updateWindowBounds(mSplitLayout, wct); - } - wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } @@ -519,6 +512,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDivideRatio(splitRatio); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); // Make sure the launch options will put tasks in the corresponding split roots addActivityOptions(mainOptions, mMainStage); @@ -536,132 +530,135 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { - startWithLegacyTransition(mainTaskId, sideTaskId, null /* pendingIntent */, - null /* fillInIntent */, mainOptions, sideOptions, sidePosition, splitRatio, - adapter); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (sideOptions == null) sideOptions = new Bundle(); + addActivityOptions(sideOptions, mSideStage); + wct.startTask(sideTaskId, sideOptions); + + startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter); } /** Start an intent and a task ordered by {@code intentFirst}. */ void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { - startWithLegacyTransition(taskId, INVALID_TASK_ID, pendingIntent, fillInIntent, - mainOptions, sideOptions, sidePosition, splitRatio, adapter); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (sideOptions == null) sideOptions = new Bundle(); + addActivityOptions(sideOptions, mSideStage); + wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions); + + startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter); } - private void startWithLegacyTransition(int mainTaskId, int sideTaskId, - @Nullable PendingIntent pendingIntent, @Nullable Intent fillInIntent, - @Nullable Bundle mainOptions, @Nullable Bundle sideOptions, + void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, + int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { - final boolean withIntent = pendingIntent != null && fillInIntent != null; + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (sideOptions == null) sideOptions = new Bundle(); + addActivityOptions(sideOptions, mSideStage); + wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions); + + startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter); + } + + private void startWithLegacyTransition(WindowContainerTransaction sideWct, int mainTaskId, + @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio, + RemoteAnimationAdapter adapter) { // Init divider first to make divider leash for remote animation target. mSplitLayout.init(); + mSplitLayout.setDivideRatio(splitRatio); + // Set false to avoid record new bounds with old task still on top; mShouldUpdateRecents = false; mIsDividerRemoteAnimating = true; - final WindowContainerTransaction wct = new WindowContainerTransaction(); - final WindowContainerTransaction evictWct = new WindowContainerTransaction(); - prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct); - prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct); - // Need to add another wrapper here in shell so that we can inject the divider bar - // and also manage the process elevation via setRunningRemote - IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { + + LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() { @Override - public void onAnimationStart(@WindowManager.TransitionOldType int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - final IRemoteAnimationFinishedCallback finishedCallback) { - RemoteAnimationTarget[] augmentedNonApps = - new RemoteAnimationTarget[nonApps.length + 1]; - for (int i = 0; i < nonApps.length; ++i) { - augmentedNonApps[i] = nonApps[i]; + public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback, + SurfaceControl.Transaction t) { + if (apps == null || apps.length == 0) { + onRemoteAnimationFinished(apps); + t.apply(); + try { + adapter.getRunner().onAnimationCancelled(mKeyguardShowing); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + return; } - augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget(); + + // The surfaces of splitting tasks were placed with window bounds when preparing the + // transition, so update divider surface separately. + final RemoteAnimationTarget dividerTarget = getDividerBarLegacyTarget(); + mSplitLayout.getRefDividerBounds(mTempRect1); + t.setLayer(dividerTarget.leash, Integer.MAX_VALUE) + .setPosition(dividerTarget.leash, mTempRect1.left, mTempRect1.top); + setDividerVisibility(true, t); + + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) { + t.show(apps[i].leash); + } + } + t.apply(); IRemoteAnimationFinishedCallback wrapCallback = new IRemoteAnimationFinishedCallback.Stub() { @Override public void onAnimationFinished() throws RemoteException { - onRemoteAnimationFinishedOrCancelled(false /* cancel */, evictWct); + onRemoteAnimationFinished(apps); finishedCallback.onAnimationFinished(); } }; Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication()); try { adapter.getRunner().onAnimationStart(transit, apps, wallpapers, - augmentedNonApps, wrapCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Error starting remote animation", e); - } - } - - @Override - public void onAnimationCancelled(boolean isKeyguardOccluded) { - onRemoteAnimationFinishedOrCancelled(true /* cancel */, evictWct); - try { - adapter.getRunner().onAnimationCancelled(isKeyguardOccluded); + ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps, + dividerTarget), wrapCallback); } catch (RemoteException e) { Slog.e(TAG, "Error starting remote animation", e); } } }; - RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter( - wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay()); - if (mainOptions == null) { - mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle(); - } else { - ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions); - mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); - mainOptions = mainActivityOptions.toBundle(); - } - - sideOptions = sideOptions != null ? sideOptions : new Bundle(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); setSideStagePosition(sidePosition, wct); - - mSplitLayout.setDivideRatio(splitRatio); if (!mMainStage.isActive()) { - // Build a request WCT that will launch both apps such that task 0 is on the main stage - // while task 1 is on the side stage. mMainStage.activate(wct, false /* reparent */); } - updateWindowBounds(mSplitLayout, wct); - wct.reorder(mRootTaskInfo.token, true); - // Make sure the launch options will put tasks in the corresponding split roots + if (mainOptions == null) mainOptions = new Bundle(); addActivityOptions(mainOptions, mMainStage); - addActivityOptions(sideOptions, mSideStage); - - // Add task launch requests wct.startTask(mainTaskId, mainOptions); - if (withIntent) { - wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions); - } else { - wct.startTask(sideTaskId, sideOptions); - } - // Using legacy transitions, so we can't use blast sync since it conflicts. - mTaskOrganizer.applyTransaction(wct); - mSyncQueue.runInSync(t -> { - setDividerVisibility(true, t); - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); - }); + wct.merge(sideWct, true); + + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); + + mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } - private void onRemoteAnimationFinishedOrCancelled(boolean cancel, - WindowContainerTransaction evictWct) { + private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) { mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; - // If any stage has no child after animation finished, it means that split will display - // nothing, such status will happen if task and intent is same app but not support - // multi-instance, we should exit split and expand that app as full screen. - if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) { - mMainExecutor.execute(() -> - exitSplitScreen(mMainStage.getChildCount() == 0 - ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); - } else { - mSyncQueue.queue(evictWct); + if (apps == null || apps.length == 0) return; + + // If any stage has no child after finished animation, that side of the split will display + // nothing. This might happen if starting the same app on the both sides while not + // supporting multi-instance. Exit the split screen and expand that app to full screen. + if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { + mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 + ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + return; } + + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct); + prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct); + mSyncQueue.queue(evictWct); } /** @@ -893,10 +890,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mShouldUpdateRecents = false; mIsDividerRemoteAnimating = false; + mSplitLayout.getInvisibleBounds(mTempRect1); if (childrenToTop == null) { mSideStage.removeAllTasks(wct, false /* toTop */); mMainStage.deactivate(wct, false /* toTop */); wct.reorder(mRootTaskInfo.token, false /* onTop */); + wct.setForceTranslucent(mRootTaskInfo.token, true); + wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); onTransitionAnimationComplete(); } else { // Expand to top side split as full screen for fading out decor animation and dismiss @@ -907,27 +907,32 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ? mSideStage : mMainStage; tempFullStage.resetBounds(wct); wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token, - mRootTaskInfo.configuration.smallestScreenWidthDp); + SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); dismissStage.dismiss(wct, false /* toTop */); } mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { t.setWindowCrop(mMainStage.mRootLeash, null) .setWindowCrop(mSideStage.mRootLeash, null); - t.setPosition(mMainStage.mRootLeash, 0, 0) - .setPosition(mSideStage.mRootLeash, 0, 0); t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer); setDividerVisibility(false, t); - // In this case, exit still under progress, fade out the split decor after first WCT - // done and do remaining WCT after animation finished. - if (childrenToTop != null) { + if (childrenToTop == null) { + t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); + } else { + // In this case, exit still under progress, fade out the split decor after first WCT + // done and do remaining WCT after animation finished. childrenToTop.fadeOutDecor(() -> { WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); mIsExiting = false; childrenToTop.dismiss(finishedWCT, true /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); - mTaskOrganizer.applyTransaction(finishedWCT); + finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); + finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + mSyncQueue.queue(finishedWCT); + mSyncQueue.runInSync(at -> { + at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); + }); onTransitionAnimationComplete(); }); } @@ -996,6 +1001,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(wct, true /* includingTopTask */); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); } void finishEnterSplitScreen(SurfaceControl.Transaction t) { @@ -1221,7 +1227,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); - mTaskOrganizer.applyTransaction(wct); + wct.setForceTranslucent(mRootTaskInfo.token, true); + mSplitLayout.getInvisibleBounds(mTempRect1); + wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); + }); } private void onRootTaskVanished() { @@ -1377,10 +1389,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // TODO (b/238697912) : Add the validation to prevent entering non-recovered status final WindowContainerTransaction wct = new WindowContainerTransaction(); mSplitLayout.init(); - prepareEnterSplitScreen(wct); + mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); + mMainStage.activate(wct, true /* includingTopTask */); + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */)); + mSyncQueue.runInSync(t -> { + mSplitLayout.flingDividerToCenter(); + }); } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; @@ -1822,6 +1839,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // properly for the animation itself. mSplitLayout.release(); mSplitLayout.resetDividerPosition(); + mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index ad539568e3eb..83aa539d24d6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -148,7 +148,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption public void onClick(View v) { final int id = v.getId(); if (id == R.id.close_window) { - mActivityTaskManager.removeTask(mTaskId); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.removeTask(mTaskToken); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitionStarter.startRemoveTransition(wct); + } else { + mSyncQueue.queue(wct); + } } else if (id == R.id.maximize_window) { WindowContainerTransaction wct = new WindowContainerTransaction(); RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 5e64a06e0326..2f41aa932d05 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -89,7 +89,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> SurfaceControl mDecorationContainerSurface; SurfaceControl mTaskBackgroundSurface; - private final CaptionWindowManager mCaptionWindowManager; + SurfaceControl mCaptionContainerSurface; + private CaptionWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; private final Rect mCaptionInsetsRect = new Rect(); @@ -127,11 +128,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration()); - - // Put caption under task surface because ViewRootImpl sets the destination frame of - // windowless window layers and BLASTBufferQueue#update() doesn't support offset. - mCaptionWindowManager = - new CaptionWindowManager(mTaskInfo.getConfiguration(), mTaskSurface); } /** @@ -213,6 +209,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> startT.setPosition( mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY) .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) + // TODO(b/244455401): Change the z-order when it's better organized .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1) .show(mDecorationContainerSurface); @@ -234,12 +231,35 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) .setShadowRadius(mTaskBackgroundSurface, shadowRadius) .setColor(mTaskBackgroundSurface, mTmpColor) + // TODO(b/244455401): Change the z-order when it's better organized .setLayer(mTaskBackgroundSurface, -1) .show(mTaskBackgroundSurface); + // CaptionContainerSurface, CaptionWindowManager + if (mCaptionContainerSurface == null) { + final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); + mCaptionContainerSurface = builder + .setName("Caption container of Task=" + mTaskInfo.taskId) + .setContainerLayer() + .setParent(mDecorationContainerSurface) + .build(); + } + + final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity); + startT.setPosition( + mCaptionContainerSurface, -decorContainerOffsetX, -decorContainerOffsetY) + .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight) + .show(mCaptionContainerSurface); + + if (mCaptionWindowManager == null) { + // Put caption under a container surface because ViewRootImpl sets the destination frame + // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. + mCaptionWindowManager = new CaptionWindowManager( + mTaskInfo.getConfiguration(), mCaptionContainerSurface); + } + // Caption view mCaptionWindowManager.setConfiguration(taskConfig); - final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity); final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(taskBounds.width(), captionHeight, WindowManager.LayoutParams.TYPE_APPLICATION, @@ -262,7 +282,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight; wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES); } else { - outResult.mRootView.setVisibility(View.GONE); + startT.hide(mCaptionContainerSurface); } // Task surface itself @@ -298,6 +318,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mViewHost = null; } + mCaptionWindowManager = null; + + if (mCaptionContainerSurface != null) { + mCaptionContainerSurface.release(); + mCaptionContainerSurface = null; + } + if (mDecorationContainerSurface != null) { mDecorationContainerSurface.release(); mDecorationContainerSurface = null; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 95725bbfd855..695550dd8fa5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -159,7 +159,8 @@ public class SplitLayoutTests extends ShellTestCase { } private void waitDividerFlingFinished() { - verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture()); + verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(), + mRunnableCaptor.capture()); mRunnableCaptor.getValue().run(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index e11be31aa40e..1b74d7d0e982 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -123,6 +123,10 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder taskBackgroundSurfaceBuilder = createMockSurfaceControlBuilder(taskBackgroundSurface); mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = new ActivityManager.TaskDescription.Builder() @@ -147,6 +151,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(decorContainerSurfaceBuilder, never()).build(); verify(taskBackgroundSurfaceBuilder, never()).build(); + verify(captionContainerSurfaceBuilder, never()).build(); verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any()); verify(mMockSurfaceControlFinishT).hide(taskSurface); @@ -168,6 +173,10 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder taskBackgroundSurfaceBuilder = createMockSurfaceControlBuilder(taskBackgroundSurface); mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = new ActivityManager.TaskDescription.Builder() @@ -205,6 +214,12 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1); verify(mMockSurfaceControlStartT).show(taskBackgroundSurface); + verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); + verify(captionContainerSurfaceBuilder).setContainerLayer(); + verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); + verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); + verify(mMockSurfaceControlStartT).show(captionContainerSurface); + verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); verify(mMockSurfaceControlViewHost) .setView(same(mMockView), @@ -245,6 +260,10 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder taskBackgroundSurfaceBuilder = createMockSurfaceControlBuilder(taskBackgroundSurface); mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = new ActivityManager.TaskDescription.Builder() @@ -270,6 +289,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlViewHost, never()).release(); verify(decorContainerSurface, never()).release(); verify(taskBackgroundSurface, never()).release(); + verify(captionContainerSurface, never()).release(); verify(mMockWindowContainerTransaction, never()) .removeInsetsProvider(eq(taskInfo.token), any()); @@ -279,6 +299,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlViewHost).release(); verify(decorContainerSurface).release(); verify(taskBackgroundSurface).release(); + verify(captionContainerSurface).release(); verify(mMockWindowContainerTransaction).removeInsetsProvider(eq(taskInfo.token), any()); } |