summaryrefslogtreecommitdiff
path: root/services/core/java/com/android/server/wm/RecentsAnimation.java
blob: fea52f2f3b8d75fce33cfc296f49ed89d241287b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
/*
 * Copyright (C) 2018 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.server.wm;

import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;

import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove;

import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Slog;
import android.view.IRecentsAnimationRunner;

import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
import com.android.server.wm.TaskDisplayArea.OnRootTaskOrderChangedListener;

/**
 * Manages the recents animation, including the reordering of the root tasks for the transition and
 * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
 */
class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChangedListener {
    private static final String TAG = RecentsAnimation.class.getSimpleName();

    private final ActivityTaskManagerService mService;
    private final ActivityTaskSupervisor mTaskSupervisor;
    private final ActivityStartController mActivityStartController;
    private final WindowManagerService mWindowManager;
    private final TaskDisplayArea mDefaultTaskDisplayArea;
    private final Intent mTargetIntent;
    private final ComponentName mRecentsComponent;
    private final @Nullable String mRecentsFeatureId;
    private final int mRecentsUid;
    private final @Nullable WindowProcessController mCaller;
    private final int mUserId;
    private final int mTargetActivityType;

    /**
     * The activity which has been launched behind. We need to remember the activity because the
     * target root task may have other activities, then we are able to restore the launch-behind
     * state for the exact activity.
     */
    private ActivityRecord mLaunchedTargetActivity;

    // The root task to restore the target root task behind when the animation is finished
    private Task mRestoreTargetBehindRootTask;

    RecentsAnimation(ActivityTaskManagerService atm, ActivityTaskSupervisor taskSupervisor,
            ActivityStartController activityStartController, WindowManagerService wm,
            Intent targetIntent, ComponentName recentsComponent, @Nullable String recentsFeatureId,
            int recentsUid, @Nullable WindowProcessController caller) {
        mService = atm;
        mTaskSupervisor = taskSupervisor;
        mDefaultTaskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
        mActivityStartController = activityStartController;
        mWindowManager = wm;
        mTargetIntent = targetIntent;
        mRecentsComponent = recentsComponent;
        mRecentsFeatureId = recentsFeatureId;
        mRecentsUid = recentsUid;
        mCaller = caller;
        mUserId = atm.getCurrentUserId();
        mTargetActivityType = targetIntent.getComponent() != null
                && recentsComponent.equals(targetIntent.getComponent())
                        ? ACTIVITY_TYPE_RECENTS
                        : ACTIVITY_TYPE_HOME;
    }

    /**
     * Starts the recents activity in background without animation if the record doesn't exist or
     * the client isn't launched. If the recents activity is already alive, ensure its configuration
     * is updated to the current one.
     */
    void preloadRecentsActivity() {
        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Preload recents with %s",
                mTargetIntent);
        Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
                mTargetActivityType);
        ActivityRecord targetActivity = getTargetActivity(targetRootTask);
        if (targetActivity != null) {
            if (targetActivity.mVisibleRequested || targetActivity.isTopRunningActivity()) {
                // The activity is ready.
                return;
            }
            if (targetActivity.attachedToProcess()) {
                // The activity may be relaunched if it cannot handle the current configuration
                // changes. The activity will be paused state if it is relaunched, otherwise it
                // keeps the original stopped state.
                targetActivity.ensureActivityConfiguration(0 /* globalChanges */,
                        false /* preserveWindow */, true /* ignoreVisibility */);
                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Updated config=%s",
                        targetActivity.getConfiguration());
            }
        } else {
            // Create the activity record. Because the activity is invisible, this doesn't really
            // start the client.
            startRecentsActivityInBackground("preloadRecents");
            targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
                    mTargetActivityType);
            targetActivity = getTargetActivity(targetRootTask);
            if (targetActivity == null) {
                Slog.w(TAG, "Cannot start " + mTargetIntent);
                return;
            }
        }

        if (!targetActivity.attachedToProcess()) {
            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Real start recents");
            mTaskSupervisor.startSpecificActivity(targetActivity, false /* andResume */,
                    false /* checkConfig */);
            // Make sure the activity won't be involved in transition.
            if (targetActivity.getDisplayContent() != null) {
                targetActivity.getDisplayContent().mUnknownAppVisibilityController
                        .appRemovedOrHidden(targetActivity);
            }
        }

        // Invisible activity should be stopped. If the recents activity is alive and its doesn't
        // need to relaunch by current configuration, then it may be already in stopped state.
        if (!targetActivity.isState(Task.ActivityState.STOPPING,
                Task.ActivityState.STOPPED)) {
            // Add to stopping instead of stop immediately. So the client has the chance to perform
            // traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
            // things (e.g. the measure can be done earlier). The actual stop will be performed when
            // it reports idle.
            targetActivity.addToStopping(true /* scheduleIdle */, true /* idleDelayed */,
                    "preloadRecents");
        }
    }

    void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner, long eventTime) {
        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");

        // Cancel any existing recents animation running synchronously (do not hold the
        // WM lock) before starting the newly requested recents animation as they can not coexist
        if (mWindowManager.getRecentsAnimationController() != null) {
            mWindowManager.getRecentsAnimationController().forceCancelAnimation(
                    REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
        }

        // If the activity is associated with the root recents task, then try and get that first
        Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
                mTargetActivityType);
        ActivityRecord targetActivity = getTargetActivity(targetRootTask);
        final boolean hasExistingActivity = targetActivity != null;
        if (hasExistingActivity) {
            mRestoreTargetBehindRootTask = getRootTaskAbove(targetRootTask);
            if (mRestoreTargetBehindRootTask == null
                    && targetRootTask.getTopMostTask() == targetActivity.getTask()) {
                notifyAnimationCancelBeforeStart(recentsAnimationRunner);
                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                        "No root task above target root task=%s", targetRootTask);
                return;
            }
        }

        // Send launch hint if we are actually launching the target. If it's already visible
        // (shouldn't happen in general) we don't need to send it.
        if (targetActivity == null || !targetActivity.mVisibleRequested) {
            mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
                    true /* forceSend */, targetActivity);
        }

        final LaunchingState launchingState =
                mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunching(mTargetIntent);

        if (mCaller != null) {
            mCaller.setRunningRecentsAnimation(true);
        }

        mService.deferWindowLayout();
        try {
            if (hasExistingActivity) {
                // Move the recents activity into place for the animation if it is not top most
                mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(targetRootTask);
                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved rootTask=%s behind rootTask=%s",
                        targetRootTask, getRootTaskAbove(targetRootTask));

                // If there are multiple tasks in the target root task (ie. the root home task,
                // with 3p and default launchers coexisting), then move the task to the top as a
                // part of moving the root task to the front
                final Task task = targetActivity.getTask();
                if (targetRootTask.getTopMostTask() != task) {
                    targetRootTask.positionChildAtTop(task);
                }
            } else {
                // No recents activity, create the new recents activity bottom most
                startRecentsActivityInBackground("startRecentsActivity_noTargetActivity");

                // Move the recents activity into place for the animation
                targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
                        mTargetActivityType);
                targetActivity = getTargetActivity(targetRootTask);
                mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(targetRootTask);
                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved rootTask=%s behind rootTask=%s",
                        targetRootTask, getRootTaskAbove(targetRootTask));

                mWindowManager.prepareAppTransitionNone();
                mWindowManager.executeAppTransition();

                // TODO: Maybe wait for app to draw in this particular case?

                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Started intent=%s", mTargetIntent);
            }

            // Mark the target activity as launch-behind to bump its visibility for the
            // duration of the gesture that is driven by the recents component
            targetActivity.mLaunchTaskBehind = true;
            mLaunchedTargetActivity = targetActivity;
            // TODO(b/156772625): Evaluate to send new intents vs. replacing the intent extras.
            targetActivity.intent.replaceExtras(mTargetIntent);

            // Fetch all the surface controls and pass them to the client to get the animation
            // started
            mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
                    this, mDefaultTaskDisplayArea.getDisplayId(),
                    mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);

            // If we updated the launch-behind state, update the visibility of the activities after
            // we fetch the visible tasks to be controlled by the animation
            mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);

            ActivityOptions options = null;
            if (eventTime > 0) {
                options = ActivityOptions.makeBasic();
                options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
            }
            mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState,
                    START_TASK_TO_FRONT, !hasExistingActivity, targetActivity, options);

            // Register for root task order changes
            mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(this);
        } catch (Exception e) {
            Slog.e(TAG, "Failed to start recents activity", e);
            throw e;
        } finally {
            mService.continueWindowLayout();
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }

    private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode,
            boolean sendUserLeaveHint) {
        synchronized (mService.mGlobalLock) {
            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                    "onAnimationFinished(): controller=%s reorderMode=%d",
                            mWindowManager.getRecentsAnimationController(), reorderMode);

            // Unregister for root task order changes
            mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(this);

            final RecentsAnimationController controller =
                    mWindowManager.getRecentsAnimationController();
            if (controller == null) return;

            // Just to be sure end the launch hint in case the target activity was never launched.
            // However, if we're keeping the activity and making it visible, we can leave it on.
            if (reorderMode != REORDER_KEEP_IN_PLACE) {
                mService.endLaunchPowerMode(
                        ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
            }

            // Once the target is shown, prevent spurious background app switches
            if (reorderMode == REORDER_MOVE_TO_TOP) {
                mService.stopAppSwitches();
            }

            if (mCaller != null) {
                mCaller.setRunningRecentsAnimation(false);
            }

            mWindowManager.inSurfaceTransaction(() -> {
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                        "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
                mService.deferWindowLayout();
                try {
                    mWindowManager.cleanupRecentsAnimation(reorderMode);

                    final Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(
                            WINDOWING_MODE_UNDEFINED, mTargetActivityType);
                    // Prefer to use the original target activity instead of top activity because
                    // we may have moved another task to top (starting 3p launcher).
                    final ActivityRecord targetActivity = targetRootTask != null
                            ? targetRootTask.isInTask(mLaunchedTargetActivity)
                            : null;
                    ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                            "onAnimationFinished(): targetRootTask=%s targetActivity=%s "
                                    + "mRestoreTargetBehindRootTask=%s",
                            targetRootTask, targetActivity, mRestoreTargetBehindRootTask);
                    if (targetActivity == null) {
                        return;
                    }

                    // Restore the launched-behind state
                    targetActivity.mLaunchTaskBehind = false;

                    if (reorderMode == REORDER_MOVE_TO_TOP) {
                        // Bring the target root task to the front
                        mTaskSupervisor.mNoAnimActivities.add(targetActivity);

                        if (sendUserLeaveHint) {
                            // Setting this allows the previous app to PiP.
                            mTaskSupervisor.mUserLeaving = true;
                            targetRootTask.moveTaskToFront(targetActivity.getTask(),
                                    true /* noAnimation */, null /* activityOptions */,
                                    targetActivity.appTimeTracker,
                                    "RecentsAnimation.onAnimationFinished()");
                        } else {
                            targetRootTask.moveToFront("RecentsAnimation.onAnimationFinished()");
                        }

                        if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
                            final Task topRootTask = getTopNonAlwaysOnTopRootTask();
                            if (topRootTask != targetRootTask) {
                                ProtoLog.w(WM_DEBUG_RECENTS_ANIMATIONS,
                                        "Expected target rootTask=%s"
                                        + " to be top most but found rootTask=%s",
                                        targetRootTask, topRootTask);
                            }
                        }
                    } else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
                        // Restore the target root task to its previous position
                        final TaskDisplayArea taskDisplayArea = targetActivity.getDisplayArea();
                        taskDisplayArea.moveRootTaskBehindRootTask(targetRootTask,
                                mRestoreTargetBehindRootTask);
                        if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
                            final Task aboveTargetRootTask = getRootTaskAbove(targetRootTask);
                            if (mRestoreTargetBehindRootTask != null
                                    && aboveTargetRootTask != mRestoreTargetBehindRootTask) {
                                ProtoLog.w(WM_DEBUG_RECENTS_ANIMATIONS,
                                        "Expected target rootTask=%s to restored behind "
                                                + "rootTask=%s but it is behind rootTask=%s",
                                        targetRootTask, mRestoreTargetBehindRootTask,
                                        aboveTargetRootTask);
                            }
                        }
                    } else {
                        // If there is no recents screenshot animation, we can update the visibility
                        // of target root task immediately because it is visually invisible and the
                        // launch-behind state is restored. That also prevents the next transition
                        // type being disturbed if the visibility is updated after setting the next
                        // transition (the target activity will be one of closing apps).
                        if (!controller.shouldDeferCancelWithScreenshot()
                                && !targetRootTask.isFocusedRootTaskOnDisplay()) {
                            targetRootTask.ensureActivitiesVisible(null /* starting */,
                                    0 /* starting */, false /* preserveWindows */);
                        }
                        // Keep target root task in place, nothing changes, so ignore the transition
                        // logic below
                        return;
                    }

                    mWindowManager.prepareAppTransitionNone();
                    mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, false);
                    mService.mRootWindowContainer.resumeFocusedTasksTopActivities();

                    // No reason to wait for the pausing activity in this case, as the hiding of
                    // surfaces needs to be done immediately.
                    mWindowManager.executeAppTransition();

                    final Task rootTask = targetRootTask.getRootTask();
                    // Client state may have changed during the recents animation, so force
                    // send task info so the client can synchronize its state.
                    rootTask.dispatchTaskInfoChangedIfNeeded(true /* force */);
                } catch (Exception e) {
                    Slog.e(TAG, "Failed to clean up recents activity", e);
                    throw e;
                } finally {
                    mTaskSupervisor.mUserLeaving = false;
                    mService.continueWindowLayout();
                    // Make sure the surfaces are updated with the latest state. Sometimes the
                    // surface placement may be skipped if display configuration is changed (i.e.
                    // {@link DisplayContent#mWaitingForConfig} is true).
                    if (mWindowManager.mRoot.isLayoutNeeded()) {
                        mWindowManager.mRoot.performSurfacePlacement();
                    }
                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                }
            });
        }
    }

    @Override
    public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
            boolean sendUserLeaveHint) {
        finishAnimation(reorderMode, sendUserLeaveHint);
    }

    @Override
    public void onRootTaskOrderChanged(Task rootTask) {
        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "onRootTaskOrderChanged(): rootTask=%s", rootTask);
        if (mDefaultTaskDisplayArea.getRootTask(t -> t == rootTask) == null
                || !rootTask.shouldBeVisible(null)) {
            // The root task is not visible, so ignore this change
            return;
        }
        final RecentsAnimationController controller =
                mWindowManager.getRecentsAnimationController();
        if (controller == null) {
            return;
        }

        // We defer canceling the recents animation until the next app transition in the following
        // cases:
        // 1) The next launching task is not being animated by the recents animation
        // 2) The next task is home activity. (i.e. pressing home key to back home in recents).
        if ((!controller.isAnimatingTask(rootTask.getTopMostTask())
                || controller.isTargetApp(rootTask.getTopNonFinishingActivity()))
                && controller.shouldDeferCancelUntilNextTransition()) {
            // Always prepare an app transition since we rely on the transition callbacks to cleanup
            mWindowManager.prepareAppTransitionNone();
            controller.setCancelOnNextTransitionStart();
        }
    }

    private void startRecentsActivityInBackground(String reason) {
        final ActivityOptions options = ActivityOptions.makeBasic();
        options.setLaunchActivityType(mTargetActivityType);
        options.setAvoidMoveToFront();
        mTargetIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);

        mActivityStartController
                .obtainStarter(mTargetIntent, reason)
                .setCallingUid(mRecentsUid)
                .setCallingPackage(mRecentsComponent.getPackageName())
                .setCallingFeatureId(mRecentsFeatureId)
                .setActivityOptions(new SafeActivityOptions(options))
                .setUserId(mUserId)
                .execute();
    }

    /**
     * Called only when the animation should be canceled prior to starting.
     */
    static void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) {
        try {
            recentsAnimationRunner.onAnimationCanceled(null /* taskSnapshot */);
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to cancel recents animation before start", e);
        }
    }

    /**
     * @return The top root task that is not always-on-top.
     */
    private Task getTopNonAlwaysOnTopRootTask() {
        return mDefaultTaskDisplayArea.getRootTask(task ->
                !task.getWindowConfiguration().isAlwaysOnTop());
    }

    /**
     * @return the top activity in the {@param targetRootTask} matching the {@param component},
     * or just the top activity of the top task if no task matches the component.
     */
    private ActivityRecord getTargetActivity(Task targetRootTask) {
        if (targetRootTask == null) {
            return null;
        }

        final PooledPredicate p = PooledLambda.obtainPredicate(RecentsAnimation::matchesTarget,
                this, PooledLambda.__(Task.class));
        final Task task = targetRootTask.getTask(p);
        p.recycle();
        return task != null ? task.getTopNonFinishingActivity() : null;
    }

    private boolean matchesTarget(Task task) {
        return task.mUserId == mUserId
                && task.getBaseIntent().getComponent().equals(mTargetIntent.getComponent());
    }
}