Loading core/java/android/window/TaskFragmentOrganizer.java +22 −6 Original line number Diff line number Diff line Loading @@ -47,11 +47,24 @@ import java.util.concurrent.Executor; public class TaskFragmentOrganizer extends WindowOrganizer { /** * Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}. * Key to the {@link Throwable} in {@link TaskFragmentTransaction.Change#getErrorBundle()}. * @hide */ public static final String KEY_ERROR_CALLBACK_THROWABLE = "fragment_throwable"; /** * Key to the {@link TaskFragmentInfo} in * {@link TaskFragmentTransaction.Change#getErrorBundle()}. * @hide */ public static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info"; /** * Key to the {@link WindowContainerTransaction.HierarchyOp} in * {@link TaskFragmentTransaction.Change#getErrorBundle()}. * @hide */ private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception"; private static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info"; private static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type"; public static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type"; /** * Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any) Loading @@ -61,7 +74,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception, @Nullable TaskFragmentInfo info, int opType) { final Bundle errorBundle = new Bundle(); errorBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception); errorBundle.putSerializable(KEY_ERROR_CALLBACK_THROWABLE, exception); if (info != null) { errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info); } Loading Loading @@ -342,6 +355,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * @hide */ public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { // TODO(b/240519866): move to SplitController#onTransactionReady to make sure the whole // transaction is handled in one sync block. Keep the implementation below to keep CTS // compatibility. Remove in the next release. final WindowContainerTransaction wct = new WindowContainerTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); for (TaskFragmentTransaction.Change change : changes) { Loading Loading @@ -391,7 +407,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { errorBundle.getParcelable( KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class), errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE), errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION, errorBundle.getSerializable(KEY_ERROR_CALLBACK_THROWABLE, java.lang.Throwable.class)); break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +7 −52 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; Loading @@ -29,6 +28,7 @@ import android.util.ArrayMap; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; Loading Loading @@ -62,18 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * Callback that notifies the controller about changes to task fragments. */ interface TaskFragmentCallback { void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Configuration parentConfig); void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken); void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @Nullable TaskFragmentInfo taskFragmentInfo, int opType); void onTransactionReady(@NonNull TaskFragmentTransaction transaction); } /** Loading Loading @@ -270,50 +259,16 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken()); } @Override public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo); } @Override public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo); void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); } @Override public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); mCallback.onTaskFragmentVanished(wct, taskFragmentInfo); } @Override public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Configuration parentConfig) { mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig); } @Override public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken); } @Override public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception) { if (taskFragmentInfo != null) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); } mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType); public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { mCallback.onTransactionReady(transaction); } } libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +280 −161 Original line number Diff line number Diff line Loading @@ -19,6 +19,15 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 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.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; Loading Loading @@ -53,6 +62,7 @@ import android.util.Pair; import android.util.Size; import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.annotation.GuardedBy; Loading Loading @@ -144,11 +154,78 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } /** * Called when the transaction is ready so that the organizer can update the TaskFragments based * on the changes in transaction. */ @Override public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { synchronized (mLock) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); final WindowContainerTransaction wct = new WindowContainerTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); for (TaskFragmentTransaction.Change change : changes) { final int taskId = change.getTaskId(); final TaskFragmentInfo info = change.getTaskFragmentInfo(); switch (change.getType()) { case TYPE_TASK_FRAGMENT_APPEARED: mPresenter.updateTaskFragmentInfo(info); onTaskFragmentAppeared(wct, info); break; case TYPE_TASK_FRAGMENT_INFO_CHANGED: mPresenter.updateTaskFragmentInfo(info); onTaskFragmentInfoChanged(wct, info); break; case TYPE_TASK_FRAGMENT_VANISHED: mPresenter.removeTaskFragmentInfo(info); onTaskFragmentVanished(wct, info); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration()); break; case TYPE_TASK_FRAGMENT_ERROR: final Bundle errorBundle = change.getErrorBundle(); final IBinder errorToken = change.getErrorCallbackToken(); final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable( KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class); final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE); final Throwable exception = errorBundle.getSerializable( KEY_ERROR_CALLBACK_THROWABLE, Throwable.class); if (errorTaskFragmentInfo != null) { mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo); } onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType, exception); break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: onActivityReparentedToTask( wct, taskId, change.getActivityIntent(), change.getActivityToken()); break; default: throw new IllegalArgumentException( "Unknown TaskFragmentEvent=" + change.getType()); } } // Notify the server, and the server should apply the WindowContainerTransaction. mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct); updateCallbackIfNecessary(); } } /** * Called when a TaskFragment is created and organized by this organizer. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is created. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } Loading @@ -160,15 +237,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Update with the latest Task configuration. updateContainer(wct, container); } updateCallbackIfNecessary(); } } @Override public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, /** * Called when the status of an organized TaskFragment is changed. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is changed. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { synchronized (mLock) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } Loading Loading @@ -209,16 +290,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // needed. updateContainer(wct, container); } updateCallbackIfNecessary(); } } @Override public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, /** * Called when an organized TaskFragment is removed. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is removed. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { synchronized (mLock) { final TaskFragmentContainer container = getContainer( taskFragmentInfo.getFragmentToken()); final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container != null) { // Cleanup if the TaskFragment vanished is not requested by the organizer. removeContainer(container); Loading @@ -228,16 +312,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (newTopContainer != null) { updateContainer(wct, newTopContainer); } updateCallbackIfNecessary(); } cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); } } @Override public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, /** * Called when the parent leaf Task of organized TaskFragments is changed. * When the leaf Task is changed, the organizer may want to update the TaskFragments in one * transaction. * * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged} * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there * can be an override bounds. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskId Id of the parent Task that is changed. * @param parentConfig Config of the parent Task. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Configuration parentConfig) { synchronized (mLock) { onTaskConfigurationChanged(taskId, parentConfig); if (isInPictureInPicture(parentConfig)) { // No need to update presentation in PIP until the Task exit PIP. Loading @@ -259,15 +354,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainer(wct, container); } } updateCallbackIfNecessary(); } } @Override public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, /** * Called when an Activity is reparented to the Task with organized TaskFragment. For example, * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its * original Task. In this case, we need to notify the organizer so that it can check if the * Activity matches any split rule. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskId The Task that the activity is reparented to. * @param activityIntent The intent that the activity is original launched with. * @param activityToken If the activity belongs to the same process as the organizer, this * will be the actual activity token; if the activity belongs to a * different process, the server will generate a temporary token that * the organizer can use to reparent the activity through * {@link WindowContainerTransaction} if needed. */ @VisibleForTesting @GuardedBy("mLock") void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { synchronized (mLock) { // If the activity belongs to the current app process, we treat it as a new activity // launch. final Activity activity = getActivity(activityToken); Loading @@ -281,7 +389,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // like a normal launch. placeActivityInTopContainer(wct, activity); } updateCallbackIfNecessary(); return; } Loading Loading @@ -309,12 +416,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Because the activity does not belong to the organizer process, we wait until // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } } @Override public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @Nullable TaskFragmentInfo taskFragmentInfo, int opType) { synchronized (mLock) { /** * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param errorCallbackToken token set in * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no * TaskFragment created. * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed * transaction operation. * @param exception exception from the server side. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception) { Log.e(TAG, "onTaskFragmentError=" + exception.getMessage()); switch (opType) { case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { Loading @@ -332,8 +453,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(wct, taskFragmentInfo); container.clearPendingAppearedActivities(); if (container.isEmpty()) { mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } break; } Loading @@ -342,7 +462,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen + ", opType = " + opType); } } } /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import android.graphics.Point; import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; Loading Loading @@ -129,6 +130,14 @@ public class JetpackTaskFragmentOrganizerTest { WINDOWING_MODE_UNDEFINED); } @Test public void testOnTransactionReady() { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); mOrganizer.onTransactionReady(transaction); verify(mCallback).onTransactionReady(transaction); } private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +101 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/window/TaskFragmentOrganizer.java +22 −6 Original line number Diff line number Diff line Loading @@ -47,11 +47,24 @@ import java.util.concurrent.Executor; public class TaskFragmentOrganizer extends WindowOrganizer { /** * Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}. * Key to the {@link Throwable} in {@link TaskFragmentTransaction.Change#getErrorBundle()}. * @hide */ public static final String KEY_ERROR_CALLBACK_THROWABLE = "fragment_throwable"; /** * Key to the {@link TaskFragmentInfo} in * {@link TaskFragmentTransaction.Change#getErrorBundle()}. * @hide */ public static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info"; /** * Key to the {@link WindowContainerTransaction.HierarchyOp} in * {@link TaskFragmentTransaction.Change#getErrorBundle()}. * @hide */ private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception"; private static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info"; private static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type"; public static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type"; /** * Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any) Loading @@ -61,7 +74,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception, @Nullable TaskFragmentInfo info, int opType) { final Bundle errorBundle = new Bundle(); errorBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception); errorBundle.putSerializable(KEY_ERROR_CALLBACK_THROWABLE, exception); if (info != null) { errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info); } Loading Loading @@ -342,6 +355,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * @hide */ public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { // TODO(b/240519866): move to SplitController#onTransactionReady to make sure the whole // transaction is handled in one sync block. Keep the implementation below to keep CTS // compatibility. Remove in the next release. final WindowContainerTransaction wct = new WindowContainerTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); for (TaskFragmentTransaction.Change change : changes) { Loading Loading @@ -391,7 +407,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { errorBundle.getParcelable( KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class), errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE), errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION, errorBundle.getSerializable(KEY_ERROR_CALLBACK_THROWABLE, java.lang.Throwable.class)); break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +7 −52 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; Loading @@ -29,6 +28,7 @@ import android.util.ArrayMap; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; Loading Loading @@ -62,18 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * Callback that notifies the controller about changes to task fragments. */ interface TaskFragmentCallback { void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Configuration parentConfig); void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken); void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @Nullable TaskFragmentInfo taskFragmentInfo, int opType); void onTransactionReady(@NonNull TaskFragmentTransaction transaction); } /** Loading Loading @@ -270,50 +259,16 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken()); } @Override public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo); } @Override public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo); void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); } @Override public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); mCallback.onTaskFragmentVanished(wct, taskFragmentInfo); } @Override public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Configuration parentConfig) { mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig); } @Override public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken); } @Override public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception) { if (taskFragmentInfo != null) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); } mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType); public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { mCallback.onTransactionReady(transaction); } }
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +280 −161 Original line number Diff line number Diff line Loading @@ -19,6 +19,15 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 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.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; Loading Loading @@ -53,6 +62,7 @@ import android.util.Pair; import android.util.Size; import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.annotation.GuardedBy; Loading Loading @@ -144,11 +154,78 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } /** * Called when the transaction is ready so that the organizer can update the TaskFragments based * on the changes in transaction. */ @Override public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { synchronized (mLock) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); final WindowContainerTransaction wct = new WindowContainerTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); for (TaskFragmentTransaction.Change change : changes) { final int taskId = change.getTaskId(); final TaskFragmentInfo info = change.getTaskFragmentInfo(); switch (change.getType()) { case TYPE_TASK_FRAGMENT_APPEARED: mPresenter.updateTaskFragmentInfo(info); onTaskFragmentAppeared(wct, info); break; case TYPE_TASK_FRAGMENT_INFO_CHANGED: mPresenter.updateTaskFragmentInfo(info); onTaskFragmentInfoChanged(wct, info); break; case TYPE_TASK_FRAGMENT_VANISHED: mPresenter.removeTaskFragmentInfo(info); onTaskFragmentVanished(wct, info); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration()); break; case TYPE_TASK_FRAGMENT_ERROR: final Bundle errorBundle = change.getErrorBundle(); final IBinder errorToken = change.getErrorCallbackToken(); final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable( KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class); final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE); final Throwable exception = errorBundle.getSerializable( KEY_ERROR_CALLBACK_THROWABLE, Throwable.class); if (errorTaskFragmentInfo != null) { mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo); } onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType, exception); break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: onActivityReparentedToTask( wct, taskId, change.getActivityIntent(), change.getActivityToken()); break; default: throw new IllegalArgumentException( "Unknown TaskFragmentEvent=" + change.getType()); } } // Notify the server, and the server should apply the WindowContainerTransaction. mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct); updateCallbackIfNecessary(); } } /** * Called when a TaskFragment is created and organized by this organizer. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is created. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } Loading @@ -160,15 +237,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Update with the latest Task configuration. updateContainer(wct, container); } updateCallbackIfNecessary(); } } @Override public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, /** * Called when the status of an organized TaskFragment is changed. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is changed. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { synchronized (mLock) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } Loading Loading @@ -209,16 +290,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // needed. updateContainer(wct, container); } updateCallbackIfNecessary(); } } @Override public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, /** * Called when an organized TaskFragment is removed. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is removed. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo taskFragmentInfo) { synchronized (mLock) { final TaskFragmentContainer container = getContainer( taskFragmentInfo.getFragmentToken()); final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container != null) { // Cleanup if the TaskFragment vanished is not requested by the organizer. removeContainer(container); Loading @@ -228,16 +312,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (newTopContainer != null) { updateContainer(wct, newTopContainer); } updateCallbackIfNecessary(); } cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); } } @Override public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, /** * Called when the parent leaf Task of organized TaskFragments is changed. * When the leaf Task is changed, the organizer may want to update the TaskFragments in one * transaction. * * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged} * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there * can be an override bounds. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskId Id of the parent Task that is changed. * @param parentConfig Config of the parent Task. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Configuration parentConfig) { synchronized (mLock) { onTaskConfigurationChanged(taskId, parentConfig); if (isInPictureInPicture(parentConfig)) { // No need to update presentation in PIP until the Task exit PIP. Loading @@ -259,15 +354,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainer(wct, container); } } updateCallbackIfNecessary(); } } @Override public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, /** * Called when an Activity is reparented to the Task with organized TaskFragment. For example, * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its * original Task. In this case, we need to notify the organizer so that it can check if the * Activity matches any split rule. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskId The Task that the activity is reparented to. * @param activityIntent The intent that the activity is original launched with. * @param activityToken If the activity belongs to the same process as the organizer, this * will be the actual activity token; if the activity belongs to a * different process, the server will generate a temporary token that * the organizer can use to reparent the activity through * {@link WindowContainerTransaction} if needed. */ @VisibleForTesting @GuardedBy("mLock") void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { synchronized (mLock) { // If the activity belongs to the current app process, we treat it as a new activity // launch. final Activity activity = getActivity(activityToken); Loading @@ -281,7 +389,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // like a normal launch. placeActivityInTopContainer(wct, activity); } updateCallbackIfNecessary(); return; } Loading Loading @@ -309,12 +416,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Because the activity does not belong to the organizer process, we wait until // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } } @Override public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @Nullable TaskFragmentInfo taskFragmentInfo, int opType) { synchronized (mLock) { /** * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param errorCallbackToken token set in * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no * TaskFragment created. * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed * transaction operation. * @param exception exception from the server side. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception) { Log.e(TAG, "onTaskFragmentError=" + exception.getMessage()); switch (opType) { case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { Loading @@ -332,8 +453,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(wct, taskFragmentInfo); container.clearPendingAppearedActivities(); if (container.isEmpty()) { mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } break; } Loading @@ -342,7 +462,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen + ", opType = " + opType); } } } /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import android.graphics.Point; import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; Loading Loading @@ -129,6 +130,14 @@ public class JetpackTaskFragmentOrganizerTest { WINDOWING_MODE_UNDEFINED); } @Test public void testOnTransactionReady() { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); mOrganizer.onTransactionReady(transaction); verify(mCallback).onTransactionReady(transaction); } private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +101 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes