diff options
6 files changed, 352 insertions, 220 deletions
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index c9a568815fb1..84302dd19097 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -32,8 +32,10 @@ import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.util.SparseArray; import android.view.RemoteAnimationDefinition; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -72,6 +74,12 @@ public class TaskFragmentOrganizer extends WindowOrganizer { */ private final Executor mExecutor; + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release. + /** Map from Task id to client tokens of TaskFragments in the Task. */ + private final SparseArray<List<IBinder>> mTaskIdToFragmentTokens = new SparseArray<>(); + /** Map from Task id to Task configuration. */ + private final SparseArray<Configuration> mTaskIdToConfigurations = new SparseArray<>(); + public TaskFragmentOrganizer(@NonNull Executor executor) { mExecutor = executor; } @@ -161,6 +169,27 @@ public class TaskFragmentOrganizer extends WindowOrganizer { @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {} /** + * 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 onTaskFragmentParentInfoChanged with new + * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override + * bounds. + * @hide + */ + public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release. + final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId); + if (tokens == null || tokens.isEmpty()) { + return; + } + for (int i = tokens.size() - 1; i >= 0; i--) { + onTaskFragmentParentInfoChanged(tokens.get(i), parentConfig); + } + } + + /** * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. * @@ -221,34 +250,43 @@ public class TaskFragmentOrganizer extends WindowOrganizer { final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); for (TaskFragmentTransaction.Change change : changes) { // TODO(b/240519866): apply all changes in one WCT. + final int taskId = change.getTaskId(); switch (change.getType()) { case TYPE_TASK_FRAGMENT_APPEARED: - onTaskFragmentAppeared(change.getTaskFragmentInfo()); - if (change.getTaskConfiguration() != null) { - // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the - // same Task - onTaskFragmentParentInfoChanged( - change.getTaskFragmentToken(), - change.getTaskConfiguration()); + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next + // release. + if (!mTaskIdToFragmentTokens.contains(taskId)) { + mTaskIdToFragmentTokens.put(taskId, new ArrayList<>()); } + mTaskIdToFragmentTokens.get(taskId).add(change.getTaskFragmentToken()); + onTaskFragmentParentInfoChanged(change.getTaskFragmentToken(), + mTaskIdToConfigurations.get(taskId)); + + onTaskFragmentAppeared(change.getTaskFragmentInfo()); break; case TYPE_TASK_FRAGMENT_INFO_CHANGED: - if (change.getTaskConfiguration() != null) { - // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the - // same Task - onTaskFragmentParentInfoChanged( - change.getTaskFragmentToken(), - change.getTaskConfiguration()); - } onTaskFragmentInfoChanged(change.getTaskFragmentInfo()); break; case TYPE_TASK_FRAGMENT_VANISHED: + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next + // release. + if (mTaskIdToFragmentTokens.contains(taskId)) { + final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId); + tokens.remove(change.getTaskFragmentToken()); + if (tokens.isEmpty()) { + mTaskIdToFragmentTokens.remove(taskId); + mTaskIdToConfigurations.remove(taskId); + } + } + onTaskFragmentVanished(change.getTaskFragmentInfo()); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: - onTaskFragmentParentInfoChanged( - change.getTaskFragmentToken(), - change.getTaskConfiguration()); + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next + // release. + mTaskIdToConfigurations.put(taskId, change.getTaskConfiguration()); + + onTaskFragmentParentInfoChanged(taskId, change.getTaskConfiguration()); break; case TYPE_TASK_FRAGMENT_ERROR: final Bundle errorBundle = change.getErrorBundle(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 0dba6b0049c0..d42fca244120 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -51,12 +51,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @VisibleForTesting final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); - /** - * Mapping from the client assigned unique token to the TaskFragment parent - * {@link Configuration}. - */ - final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>(); - private final TaskFragmentCallback mCallback; @VisibleForTesting TaskFragmentAnimationController mAnimationController; @@ -68,8 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo); - void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, - @NonNull Configuration parentConfig); + void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig); void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken); void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType); @@ -300,7 +293,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @Override public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); - mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken()); if (mCallback != null) { mCallback.onTaskFragmentVanished(taskFragmentInfo); @@ -308,12 +300,9 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } @Override - public void onTaskFragmentParentInfoChanged( - @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) { - mFragmentParentConfigs.put(fragmentToken, parentConfig); - + public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { if (mCallback != null) { - mCallback.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig); + mCallback.onTaskFragmentParentInfoChanged(taskId, parentConfig); } } 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 c688080ad929..dad07394e3fb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -155,6 +155,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(taskFragmentInfo); if (container.isFinished()) { mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + } else { + // Update with the latest Task configuration. + mPresenter.updateContainer(container); } updateCallbackIfNecessary(); } @@ -233,19 +236,30 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, - @NonNull Configuration parentConfig) { + public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { synchronized (mLock) { - final TaskFragmentContainer container = getContainer(fragmentToken); - if (container != null) { - onTaskConfigurationChanged(container.getTaskId(), parentConfig); - if (isInPictureInPicture(parentConfig)) { - // No need to update presentation in PIP until the Task exit PIP. - return; + onTaskConfigurationChanged(taskId, parentConfig); + if (isInPictureInPicture(parentConfig)) { + // No need to update presentation in PIP until the Task exit PIP. + return; + } + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer == null || taskContainer.isEmpty()) { + Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId); + return; + } + // Update all TaskFragments in the Task. Make a copy of the list since some may be + // removed on updating. + final List<TaskFragmentContainer> containers = + new ArrayList<>(taskContainer.mContainers); + for (int i = containers.size() - 1; i >= 0; i--) { + final TaskFragmentContainer container = containers.get(i); + // Wait until onTaskFragmentAppeared to update new container. + if (!container.isFinished() && !container.isWaitingActivityAppear()) { + mPresenter.updateContainer(container); } - mPresenter.updateContainer(container); - updateCallbackIfNecessary(); } + updateCallbackIfNecessary(); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 84740683f6aa..8878944e142e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -887,11 +887,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { logIfTransactionTooLarge(r.intent, r.getSavedState()); - if (r.isEmbedded()) { + final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment(); + if (organizedTaskFragment != null) { // Sending TaskFragmentInfo to client to ensure the info is updated before // the activity creation. mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent( - r.getOrganizedTaskFragment()); + organizedTaskFragment); } // Create activity launch transaction. diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index a4d6f70b011e..6ca564833d41 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; @@ -28,6 +29,8 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; +import static java.util.Objects.requireNonNull; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -38,6 +41,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.view.RemoteAnimationDefinition; @@ -51,6 +55,7 @@ import com.android.internal.protolog.common.ProtoLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.WeakHashMap; @@ -70,15 +75,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ArrayMap<IBinder, TaskFragmentOrganizerState> mTaskFragmentOrganizerState = new ArrayMap<>(); /** - * A List which manages the TaskFragment pending event {@link PendingTaskFragmentEvent} + * Map from {@link ITaskFragmentOrganizer} to a list of related {@link PendingTaskFragmentEvent} */ - private final ArrayList<PendingTaskFragmentEvent> mPendingTaskFragmentEvents = - new ArrayList<>(); - - /** Map from {@link ITaskFragmentOrganizer} to {@link TaskFragmentTransaction}. */ - private final ArrayMap<IBinder, TaskFragmentTransaction> mTmpOrganizerToTransactionMap = + private final ArrayMap<IBinder, List<PendingTaskFragmentEvent>> mPendingTaskFragmentEvents = new ArrayMap<>(); - private final ArrayList<ITaskFragmentOrganizer> mTmpOrganizerList = new ArrayList<>(); + + private final ArraySet<Task> mTmpTaskSet = new ArraySet<>(); TaskFragmentOrganizerController(ActivityTaskManagerService atm) { mAtmService = atm; @@ -94,10 +96,30 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ITaskFragmentOrganizer mOrganizer; private final int mOrganizerPid; private final int mOrganizerUid; + + /** + * Map from {@link TaskFragment} to the last {@link TaskFragmentInfo} sent to the + * organizer. + */ private final Map<TaskFragment, TaskFragmentInfo> mLastSentTaskFragmentInfos = new WeakHashMap<>(); - private final Map<TaskFragment, Configuration> mLastSentTaskFragmentParentConfigs = - new WeakHashMap<>(); + + /** + * Map from {@link TaskFragment} to its leaf {@link Task#mTaskId}. Embedded + * {@link TaskFragment} will not be reparented until it is removed. + */ + private final Map<TaskFragment, Integer> mTaskFragmentTaskIds = new WeakHashMap<>(); + + /** + * Map from {@link Task#mTaskId} to the last Task {@link Configuration} sent to the + * organizer. + */ + private final SparseArray<Configuration> mLastSentTaskFragmentParentConfigs = + new SparseArray<>(); + + /** + * Map from temporary activity token to the corresponding {@link ActivityRecord}. + */ private final Map<IBinder, ActivityRecord> mTemporaryActivityTokens = new WeakHashMap<>(); @@ -161,21 +183,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr TaskFragmentTransaction.Change prepareTaskFragmentAppeared(@NonNull TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); final TaskFragmentInfo info = tf.getTaskFragmentInfo(); + final int taskId = tf.getTask().mTaskId; tf.mTaskFragmentAppearedSent = true; mLastSentTaskFragmentInfos.put(tf, info); - final TaskFragmentTransaction.Change change = - new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_APPEARED) - .setTaskFragmentToken(tf.getFragmentToken()) - .setTaskFragmentInfo(info); - if (shouldSendTaskFragmentParentInfoChanged(tf)) { - // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task - final Task task = tf.getTask(); - mLastSentTaskFragmentParentConfigs - .put(tf, new Configuration(task.getConfiguration())); - change.setTaskId(task.mTaskId) - .setTaskConfiguration(task.getConfiguration()); - } - return change; + mTaskFragmentTaskIds.put(tf, taskId); + return new TaskFragmentTransaction.Change( + TYPE_TASK_FRAGMENT_APPEARED) + .setTaskFragmentToken(tf.getFragmentToken()) + .setTaskFragmentInfo(info) + .setTaskId(taskId); } @NonNull @@ -183,10 +199,24 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName()); tf.mTaskFragmentAppearedSent = false; mLastSentTaskFragmentInfos.remove(tf); - mLastSentTaskFragmentParentConfigs.remove(tf); + + // Cleanup TaskFragmentParentConfig if this is the last TaskFragment in the Task. + final int taskId; + if (mTaskFragmentTaskIds.containsKey(tf)) { + taskId = mTaskFragmentTaskIds.remove(tf); + if (!mTaskFragmentTaskIds.containsValue(taskId)) { + // No more TaskFragment in the Task. + mLastSentTaskFragmentParentConfigs.remove(taskId); + } + } else { + // This can happen if the appeared wasn't sent before remove. + taskId = INVALID_TASK_ID; + } + return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_VANISHED) .setTaskFragmentToken(tf.getFragmentToken()) - .setTaskFragmentInfo(tf.getTaskFragmentInfo()); + .setTaskFragmentInfo(tf.getTaskFragmentInfo()) + .setTaskId(taskId); } @Nullable @@ -197,65 +227,39 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf); if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer( info.getConfiguration(), lastInfo.getConfiguration())) { - // Parent config may have changed. The controller will check if there is any - // important config change for the organizer. - return prepareTaskFragmentParentInfoChanged(tf); + return null; } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s", tf.getName()); mLastSentTaskFragmentInfos.put(tf, info); - final TaskFragmentTransaction.Change change = - new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_INFO_CHANGED) - .setTaskFragmentToken(tf.getFragmentToken()) - .setTaskFragmentInfo(info); - if (shouldSendTaskFragmentParentInfoChanged(tf)) { - // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task - // at once. - // Parent config may have changed. The controller will check if there is any - // important config change for the organizer. - final Task task = tf.getTask(); - mLastSentTaskFragmentParentConfigs - .put(tf, new Configuration(task.getConfiguration())); - change.setTaskId(task.mTaskId) - .setTaskConfiguration(task.getConfiguration()); - } - return change; + return new TaskFragmentTransaction.Change( + TYPE_TASK_FRAGMENT_INFO_CHANGED) + .setTaskFragmentToken(tf.getFragmentToken()) + .setTaskFragmentInfo(info) + .setTaskId(tf.getTask().mTaskId); } @Nullable TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged( - @NonNull TaskFragment tf) { - if (!shouldSendTaskFragmentParentInfoChanged(tf)) { + @NonNull Task task) { + final int taskId = task.mTaskId; + // Check if the parent info is different from the last reported parent info. + final Configuration taskConfig = task.getConfiguration(); + final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(taskId); + if (configurationsAreEqualForOrganizer(taskConfig, lastParentConfig) + && taskConfig.windowConfiguration.getWindowingMode() + == lastParentConfig.windowConfiguration.getWindowingMode()) { return null; } - final Task parent = tf.getTask(); - final Configuration parentConfig = parent.getConfiguration(); ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment parent info changed name=%s parentTaskId=%d", - tf.getName(), parent.mTaskId); - mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig)); + task.getName(), taskId); + mLastSentTaskFragmentParentConfigs.put(taskId, new Configuration(taskConfig)); return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) - .setTaskFragmentToken(tf.getFragmentToken()) - .setTaskId(parent.mTaskId) - .setTaskConfiguration(parent.getConfiguration()); - } - - /** Whether the system should report TaskFragment parent info changed to the organizer. */ - private boolean shouldSendTaskFragmentParentInfoChanged(@NonNull TaskFragment tf) { - final Task parent = tf.getTask(); - if (parent == null) { - // The TaskFragment is not attached. - mLastSentTaskFragmentParentConfigs.remove(tf); - return false; - } - // Check if the parent info is different from the last reported parent info. - final Configuration parentConfig = parent.getConfiguration(); - final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf); - return !configurationsAreEqualForOrganizer(parentConfig, lastParentConfig) - || parentConfig.windowConfiguration.getWindowingMode() - != lastParentConfig.windowConfiguration.getWindowingMode(); + .setTaskId(taskId) + .setTaskConfiguration(taskConfig); } @NonNull @@ -345,6 +349,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } mTaskFragmentOrganizerState.put(organizer.asBinder(), new TaskFragmentOrganizerState(organizer, pid, uid)); + mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>()); } } @@ -434,6 +439,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr void onTaskFragmentAppeared(@NonNull ITaskFragmentOrganizer organizer, @NonNull TaskFragment taskFragment) { + if (taskFragment.getTask() == null) { + Slog.w(TAG, "onTaskFragmentAppeared failed because it is not attached tf=" + + taskFragment); + return; + } final TaskFragmentOrganizerState state = validateAndGetState(organizer); if (!state.addTaskFragment(taskFragment)) { return; @@ -441,28 +451,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr PendingTaskFragmentEvent pendingEvent = getPendingTaskFragmentEvent(taskFragment, PendingTaskFragmentEvent.EVENT_APPEARED); if (pendingEvent == null) { - pendingEvent = new PendingTaskFragmentEvent.Builder( + addPendingEvent(new PendingTaskFragmentEvent.Builder( PendingTaskFragmentEvent.EVENT_APPEARED, organizer) .setTaskFragment(taskFragment) - .build(); - mPendingTaskFragmentEvents.add(pendingEvent); + .build()); } } void onTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, @NonNull TaskFragment taskFragment) { - handleTaskFragmentInfoChanged(organizer, taskFragment, - PendingTaskFragmentEvent.EVENT_INFO_CHANGED); - } - - void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, - @NonNull TaskFragment taskFragment) { - handleTaskFragmentInfoChanged(organizer, taskFragment, - PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED); - } - - private void handleTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, - @NonNull TaskFragment taskFragment, int eventType) { validateAndGetState(organizer); if (!taskFragment.mTaskFragmentAppearedSent) { // Skip if TaskFragment still not appeared. @@ -470,45 +467,41 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } PendingTaskFragmentEvent pendingEvent = getLastPendingLifecycleEvent(taskFragment); if (pendingEvent == null) { - pendingEvent = new PendingTaskFragmentEvent.Builder(eventType, organizer) - .setTaskFragment(taskFragment) - .build(); + pendingEvent = new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_INFO_CHANGED, organizer) + .setTaskFragment(taskFragment) + .build(); } else { if (pendingEvent.mEventType == PendingTaskFragmentEvent.EVENT_VANISHED) { // Skipped the info changed event if vanished event is pending. return; } // Remove and add for re-ordering. - mPendingTaskFragmentEvents.remove(pendingEvent); + removePendingEvent(pendingEvent); // Reset the defer time when TaskFragment is changed, so that it can check again if // the event should be sent to the organizer, for example the TaskFragment may become // empty. pendingEvent.mDeferTime = 0; } - mPendingTaskFragmentEvents.add(pendingEvent); + addPendingEvent(pendingEvent); } void onTaskFragmentVanished(@NonNull ITaskFragmentOrganizer organizer, @NonNull TaskFragment taskFragment) { final TaskFragmentOrganizerState state = validateAndGetState(organizer); - for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { - PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); - if (taskFragment == entry.mTaskFragment) { - mPendingTaskFragmentEvents.remove(i); - if (entry.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED) { - // If taskFragment appeared callback is pending, ignore the vanished request. - return; - } + final List<PendingTaskFragmentEvent> pendingEvents = mPendingTaskFragmentEvents + .get(organizer.asBinder()); + // Remove any pending events since this TaskFragment is being removed. + for (int i = pendingEvents.size() - 1; i >= 0; i--) { + final PendingTaskFragmentEvent event = pendingEvents.get(i); + if (taskFragment == event.mTaskFragment) { + pendingEvents.remove(i); } } - if (!taskFragment.mTaskFragmentAppearedSent) { - return; - } - final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder( + addPendingEvent(new PendingTaskFragmentEvent.Builder( PendingTaskFragmentEvent.EVENT_VANISHED, organizer) .setTaskFragment(taskFragment) - .build(); - mPendingTaskFragmentEvents.add(pendingEvent); + .build()); state.removeTaskFragment(taskFragment); } @@ -517,14 +510,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr int opType, @NonNull Throwable exception) { validateAndGetState(organizer); Slog.w(TAG, "onTaskFragmentError ", exception); - final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder( + addPendingEvent(new PendingTaskFragmentEvent.Builder( PendingTaskFragmentEvent.EVENT_ERROR, organizer) .setErrorCallbackToken(errorCallbackToken) .setTaskFragment(taskFragment) .setException(exception) .setOpType(opType) - .build(); - mPendingTaskFragmentEvents.add(pendingEvent); + .build()); // Make sure the error event will be dispatched if there are no other changes. mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal(); } @@ -554,11 +546,18 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists"); return; } - final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder( + addPendingEvent(new PendingTaskFragmentEvent.Builder( PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK, organizer) .setActivity(activity) - .build(); - mPendingTaskFragmentEvents.add(pendingEvent); + .build()); + } + + private void addPendingEvent(@NonNull PendingTaskFragmentEvent event) { + mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).add(event); + } + + private void removePendingEvent(@NonNull PendingTaskFragmentEvent event) { + mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).remove(event); } boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) { @@ -570,12 +569,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr // remove all of the children of the organized TaskFragment state.dispose(); // Remove any pending event of this organizer. - for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { - final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); - if (event.mTaskFragmentOrg.asBinder().equals(organizer.asBinder())) { - mPendingTaskFragmentEvents.remove(i); - } - } + mPendingTaskFragmentEvents.remove(organizer.asBinder()); mTaskFragmentOrganizerState.remove(organizer.asBinder()); } @@ -631,6 +625,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final Throwable mException; @Nullable private final ActivityRecord mActivity; + @Nullable + private final Task mTask; // Set when the event is deferred due to the host task is invisible. The defer time will // be the last active time of the host task. private long mDeferTime; @@ -642,6 +638,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr @Nullable IBinder errorCallbackToken, @Nullable Throwable exception, @Nullable ActivityRecord activity, + @Nullable Task task, int opType) { mEventType = eventType; mTaskFragmentOrg = taskFragmentOrg; @@ -649,6 +646,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mErrorCallbackToken = errorCallbackToken; mException = exception; mActivity = activity; + mTask = task; mOpType = opType; } @@ -680,11 +678,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private Throwable mException; @Nullable private ActivityRecord mActivity; + @Nullable + private Task mTask; private int mOpType; - Builder(@EventType int eventType, ITaskFragmentOrganizer taskFragmentOrg) { + Builder(@EventType int eventType, @NonNull ITaskFragmentOrganizer taskFragmentOrg) { mEventType = eventType; - mTaskFragmentOrg = taskFragmentOrg; + mTaskFragmentOrg = requireNonNull(taskFragmentOrg); } Builder setTaskFragment(@Nullable TaskFragment taskFragment) { @@ -697,13 +697,18 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return this; } - Builder setException(@Nullable Throwable exception) { - mException = exception; + Builder setException(@NonNull Throwable exception) { + mException = requireNonNull(exception); return this; } - Builder setActivity(@Nullable ActivityRecord activity) { - mActivity = activity; + Builder setActivity(@NonNull ActivityRecord activity) { + mActivity = requireNonNull(activity); + return this; + } + + Builder setTask(@NonNull Task task) { + mTask = requireNonNull(task); return this; } @@ -714,17 +719,20 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr PendingTaskFragmentEvent build() { return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment, - mErrorCallbackToken, mException, mActivity, mOpType); + mErrorCallbackToken, mException, mActivity, mTask, mOpType); } } } @Nullable private PendingTaskFragmentEvent getLastPendingLifecycleEvent(@NonNull TaskFragment tf) { - for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { - PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); - if (tf == entry.mTaskFragment && entry.isLifecycleEvent()) { - return entry; + final ITaskFragmentOrganizer organizer = tf.getTaskFragmentOrganizer(); + final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents + .get(organizer.asBinder()); + for (int i = events.size() - 1; i >= 0; i--) { + final PendingTaskFragmentEvent event = events.get(i); + if (tf == event.mTaskFragment && event.isLifecycleEvent()) { + return event; } } return null; @@ -733,10 +741,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr @Nullable private PendingTaskFragmentEvent getPendingTaskFragmentEvent(@NonNull TaskFragment taskFragment, int type) { - for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { - PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); - if (taskFragment == entry.mTaskFragment && type == entry.mEventType) { - return entry; + final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer(); + final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents + .get(organizer.asBinder()); + for (int i = events.size() - 1; i >= 0; i--) { + final PendingTaskFragmentEvent event = events.get(i); + if (taskFragment == event.mTaskFragment && type == event.mEventType) { + return event; } } return null; @@ -762,12 +773,25 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr || mPendingTaskFragmentEvents.isEmpty()) { return; } + final int organizerNum = mPendingTaskFragmentEvents.size(); + for (int i = 0; i < organizerNum; i++) { + final ITaskFragmentOrganizer organizer = mTaskFragmentOrganizerState.get( + mPendingTaskFragmentEvents.keyAt(i)).mOrganizer; + dispatchPendingEvents(organizer, mPendingTaskFragmentEvents.valueAt(i)); + } + } + + void dispatchPendingEvents(@NonNull ITaskFragmentOrganizer organizer, + @NonNull List<PendingTaskFragmentEvent> pendingEvents) { + if (pendingEvents.isEmpty()) { + return; + } final ArrayList<Task> visibleTasks = new ArrayList<>(); final ArrayList<Task> invisibleTasks = new ArrayList<>(); final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>(); - for (int i = 0, n = mPendingTaskFragmentEvents.size(); i < n; i++) { - final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); + for (int i = 0, n = pendingEvents.size(); i < n; i++) { + final PendingTaskFragmentEvent event = pendingEvents.get(i); final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; if (task != null && (task.lastActiveTime <= event.mDeferTime || !(isTaskVisible(task, visibleTasks, invisibleTasks) @@ -783,27 +807,26 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return; } - mTmpOrganizerToTransactionMap.clear(); - mTmpOrganizerList.clear(); + mTmpTaskSet.clear(); + final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); for (int i = 0; i < numEvents; i++) { final PendingTaskFragmentEvent event = candidateEvents.get(i); - if (!mTmpOrganizerToTransactionMap.containsKey(event.mTaskFragmentOrg.asBinder())) { - mTmpOrganizerToTransactionMap.put(event.mTaskFragmentOrg.asBinder(), - new TaskFragmentTransaction()); - mTmpOrganizerList.add(event.mTaskFragmentOrg); - } - mTmpOrganizerToTransactionMap.get(event.mTaskFragmentOrg.asBinder()) - .addChange(prepareChange(event)); - } - final int numOrganizers = mTmpOrganizerList.size(); - for (int i = 0; i < numOrganizers; i++) { - final ITaskFragmentOrganizer organizer = mTmpOrganizerList.get(i); - dispatchTransactionInfo(organizer, - mTmpOrganizerToTransactionMap.get(organizer.asBinder())); - } - mPendingTaskFragmentEvents.removeAll(candidateEvents); - mTmpOrganizerToTransactionMap.clear(); - mTmpOrganizerList.clear(); + if (event.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED + || event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) { + final Task task = event.mTaskFragment.getTask(); + if (mTmpTaskSet.add(task)) { + // Make sure the organizer know about the Task config. + transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer) + .setTask(task) + .build())); + } + } + transaction.addChange(prepareChange(event)); + } + mTmpTaskSet.clear(); + dispatchTransactionInfo(organizer, transaction); + pendingEvents.removeAll(candidateEvents); } private static boolean isTaskVisible(@NonNull Task task, @@ -831,10 +854,16 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return; } + final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer(); final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); + // Make sure the organizer know about the Task config. + transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer) + .setTask(taskFragment.getTask()) + .build())); transaction.addChange(prepareChange(event)); dispatchTransactionInfo(event.mTaskFragmentOrg, transaction); - mPendingTaskFragmentEvents.remove(event); + mPendingTaskFragmentEvents.get(organizer.asBinder()).remove(event); } private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer, @@ -867,7 +896,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr case PendingTaskFragmentEvent.EVENT_INFO_CHANGED: return state.prepareTaskFragmentInfoChanged(taskFragment); case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED: - return state.prepareTaskFragmentParentInfoChanged(taskFragment); + return state.prepareTaskFragmentParentInfoChanged(event.mTask); case PendingTaskFragmentEvent.EVENT_ERROR: return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment, event.mOpType, event.mException); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index f38731b2f739..8b3cff8039d4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -75,6 +75,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Build/Install/Run: @@ -92,7 +94,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private TaskFragmentOrganizerToken mOrganizerToken; private ITaskFragmentOrganizer mIOrganizer; private TaskFragment mTaskFragment; - private TaskFragmentInfo mTaskFragmentInfo; private IBinder mFragmentToken; private WindowContainerTransaction mTransaction; private WindowContainerToken mFragmentWindowToken; @@ -100,14 +101,19 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private IBinder mErrorToken; private Rect mTaskFragBounds; + @Mock + private TaskFragmentInfo mTaskFragmentInfo; + @Mock + private Task mTask; + @Before public void setup() { + MockitoAnnotations.initMocks(this); mWindowOrganizerController = mAtm.mWindowOrganizerController; mController = mWindowOrganizerController.mTaskFragmentOrganizerController; mOrganizer = new TaskFragmentOrganizer(Runnable::run); mOrganizerToken = mOrganizer.getOrganizerToken(); mIOrganizer = ITaskFragmentOrganizer.Stub.asInterface(mOrganizerToken.asBinder()); - mTaskFragmentInfo = mock(TaskFragmentInfo.class); mFragmentToken = new Binder(); mTaskFragment = new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */); @@ -131,6 +137,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testCallTaskFragmentCallbackWithoutRegister_throwsException() { + doReturn(mTask).when(mTaskFragment).getTask(); + assertThrows(IllegalArgumentException.class, () -> mController .onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment)); @@ -140,16 +148,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertThrows(IllegalArgumentException.class, () -> mController .onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment)); - - assertThrows(IllegalArgumentException.class, () -> mController - .onTaskFragmentParentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(), - mTaskFragment)); } @Test public void testOnTaskFragmentAppeared() { mController.registerOrganizer(mIOrganizer); + // No-op when the TaskFragment is not attached. + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onTaskFragmentAppeared(any()); + + // Send callback when the TaskFragment is attached. + setupMockParent(mTaskFragment, mTask); + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); @@ -159,9 +172,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentInfoChanged() { mController.registerOrganizer(mIOrganizer); + setupMockParent(mTaskFragment, mTask); + + // No-op if onTaskFragmentAppeared is not called yet. + mController.onTaskFragmentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(), + mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + + // Call onTaskFragmentAppeared first. mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); + verify(mOrganizer).onTaskFragmentAppeared(any()); + // No callback if the info is not changed. doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); @@ -194,49 +219,74 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testOnTaskFragmentVanished_clearUpRemaining() { + mController.registerOrganizer(mIOrganizer); + setupMockParent(mTaskFragment, mTask); + + // Not trigger onTaskFragmentAppeared. + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onTaskFragmentAppeared(any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); + verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo); + + // Not trigger onTaskFragmentInfoChanged. + // Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged. + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + clearInvocations(mOrganizer); + doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); + mController.onTaskFragmentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(), + mTaskFragment); + mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onTaskFragmentAppeared(any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); + verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo); + } + + @Test public void testOnTaskFragmentParentInfoChanged() { mController.registerOrganizer(mIOrganizer); - final Task parent = mock(Task.class); - final Configuration parentConfig = new Configuration(); - parentConfig.smallestScreenWidthDp = 10; - doReturn(parent).when(mTaskFragment).getTask(); - doReturn(parentConfig).when(parent).getConfiguration(); - // Task needs to be visible - parent.lastActiveTime = 100; - doReturn(true).when(parent).shouldBeVisible(any()); + setupMockParent(mTaskFragment, mTask); + mTask.getConfiguration().smallestScreenWidthDp = 10; - mTaskFragment.mTaskFragmentAppearedSent = true; - mController.onTaskFragmentParentInfoChanged( + mController.onTaskFragmentAppeared( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any()); + verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any()); // No extra callback if the info is not changed. clearInvocations(mOrganizer); - mController.onTaskFragmentParentInfoChanged( + mController.onTaskFragmentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), any()); + verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); // Trigger callback if the size is changed. - parentConfig.smallestScreenWidthDp = 100; - mController.onTaskFragmentParentInfoChanged( + mTask.getConfiguration().smallestScreenWidthDp = 100; + mController.onTaskFragmentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any()); + verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any()); // Trigger callback if the windowing mode is changed. clearInvocations(mOrganizer); - parentConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); - mController.onTaskFragmentParentInfoChanged( + mTask.getConfiguration().windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); + mController.onTaskFragmentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any()); + verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any()); } @Test @@ -1091,4 +1141,15 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .put(mFragmentToken, mTaskFragment); mTaskFragment.getTask().setWindowingMode(WINDOWING_MODE_PINNED); } + + /** Setups the mock Task as the parent of the given TaskFragment. */ + private static void setupMockParent(TaskFragment taskFragment, Task mockParent) { + doReturn(mockParent).when(taskFragment).getTask(); + final Configuration taskConfig = new Configuration(); + doReturn(taskConfig).when(mockParent).getConfiguration(); + + // Task needs to be visible + mockParent.lastActiveTime = 100; + doReturn(true).when(mockParent).shouldBeVisible(any()); + } } |