RecentsView.java revision 8aa7d161a11965cd75eace74e1bcf15421bbea18
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.app.ActivityOptions;
20import android.app.TaskStackBuilder;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.net.Uri;
27import android.os.UserHandle;
28import android.provider.Settings;
29import android.util.AttributeSet;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewStub;
33import android.view.WindowInsets;
34import android.widget.FrameLayout;
35import com.android.systemui.R;
36import com.android.systemui.recents.Constants;
37import com.android.systemui.recents.RecentsConfiguration;
38import com.android.systemui.recents.misc.SystemServicesProxy;
39import com.android.systemui.recents.model.RecentsPackageMonitor;
40import com.android.systemui.recents.model.RecentsTaskLoader;
41import com.android.systemui.recents.model.Task;
42import com.android.systemui.recents.model.TaskStack;
43
44import java.util.ArrayList;
45import java.util.List;
46
47/**
48 * This view is the the top level layout that contains TaskStacks (which are laid out according
49 * to their SpaceNode bounds.
50 */
51public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks,
52        RecentsPackageMonitor.PackageCallbacks {
53
54    /** The RecentsView callbacks */
55    public interface RecentsViewCallbacks {
56        public void onTaskViewClicked();
57        public void onTaskLaunchFailed();
58        public void onAllTaskViewsDismissed();
59        public void onExitToHomeAnimationTriggered();
60        public void onScreenPinningRequest();
61
62        public void onTaskResize(Task t);
63    }
64
65    RecentsConfiguration mConfig;
66    LayoutInflater mInflater;
67    DebugOverlayView mDebugOverlay;
68    RecentsViewLayoutAlgorithm mLayoutAlgorithm;
69
70    ArrayList<TaskStack> mStacks;
71    List<TaskStackView> mTaskStackViews = new ArrayList<>();
72    View mSearchBar;
73    RecentsViewCallbacks mCb;
74
75    public RecentsView(Context context) {
76        super(context);
77    }
78
79    public RecentsView(Context context, AttributeSet attrs) {
80        this(context, attrs, 0);
81    }
82
83    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
84        this(context, attrs, defStyleAttr, 0);
85    }
86
87    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
88        super(context, attrs, defStyleAttr, defStyleRes);
89        mConfig = RecentsConfiguration.getInstance();
90        mInflater = LayoutInflater.from(context);
91        mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig);
92    }
93
94    /** Sets the callbacks */
95    public void setCallbacks(RecentsViewCallbacks cb) {
96        mCb = cb;
97    }
98
99    /** Sets the debug overlay */
100    public void setDebugOverlay(DebugOverlayView overlay) {
101        mDebugOverlay = overlay;
102    }
103
104    /** Set/get the bsp root node */
105    public void setTaskStacks(ArrayList<TaskStack> stacks) {
106        int numStacks = stacks.size();
107
108        // Remove all/extra stack views
109        int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout
110        if (mConfig.launchedReuseTaskStackViews) {
111            numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks);
112        }
113        for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
114            removeView(mTaskStackViews.remove(i));
115        }
116
117        // Update the stack views that we are keeping
118        for (int i = 0; i < numTaskStacksToKeep; i++) {
119            TaskStackView tsv = mTaskStackViews.get(i);
120            // If onRecentsHidden is not triggered, we need to the stack view again here
121            tsv.reset();
122            tsv.setStack(stacks.get(i));
123        }
124
125        // Add remaining/recreate stack views
126        mStacks = stacks;
127        for (int i = mTaskStackViews.size(); i < numStacks; i++) {
128            TaskStack stack = stacks.get(i);
129            TaskStackView stackView = new TaskStackView(getContext(), stack);
130            stackView.setCallbacks(this);
131            addView(stackView);
132            mTaskStackViews.add(stackView);
133        }
134
135        // Enable debug mode drawing on all the stacks if necessary
136        if (mConfig.debugModeEnabled) {
137            for (int i = mTaskStackViews.size() - 1; i >= 0; i--) {
138                TaskStackView stackView = mTaskStackViews.get(i);
139                stackView.setDebugOverlay(mDebugOverlay);
140            }
141        }
142
143        // Trigger a new layout
144        requestLayout();
145    }
146
147    /** Gets the list of task views */
148    List<TaskStackView> getTaskStackViews() {
149        return mTaskStackViews;
150    }
151
152    /** Gets the next task in the stack - or if the last - the top task */
153    public Task getNextTaskOrTopTask(Task taskToSearch) {
154        Task returnTask = null;
155        boolean found = false;
156        List<TaskStackView> stackViews = getTaskStackViews();
157        int stackCount = stackViews.size();
158        for (int i = stackCount - 1; i >= 0; --i) {
159            TaskStack stack = stackViews.get(i).getStack();
160            ArrayList<Task> taskList = stack.getTasks();
161            // Iterate the stack views and try and find the focused task
162            for (int j = taskList.size() - 1; j >= 0; --j) {
163                Task task = taskList.get(j);
164                // Return the next task in the line.
165                if (found)
166                    return task;
167                // Remember the first possible task as the top task.
168                if (returnTask == null)
169                    returnTask = task;
170                if (task == taskToSearch)
171                    found = true;
172            }
173        }
174        return returnTask;
175    }
176
177    /** Launches the focused task from the first stack if possible */
178    public boolean launchFocusedTask() {
179        // Get the first stack view
180        List<TaskStackView> stackViews = getTaskStackViews();
181        int stackCount = stackViews.size();
182        for (int i = 0; i < stackCount; i++) {
183            TaskStackView stackView = stackViews.get(i);
184            TaskStack stack = stackView.getStack();
185            // Iterate the stack views and try and find the focused task
186            List<TaskView> taskViews = stackView.getTaskViews();
187            int taskViewCount = taskViews.size();
188            for (int j = 0; j < taskViewCount; j++) {
189                TaskView tv = taskViews.get(j);
190                Task task = tv.getTask();
191                if (tv.isFocusedTask()) {
192                    onTaskViewClicked(stackView, tv, stack, task, false);
193                    return true;
194                }
195            }
196        }
197        return false;
198    }
199
200    /** Launches a given task. */
201    public boolean launchTask(Task task) {
202        // Get the first stack view
203        List<TaskStackView> stackViews = getTaskStackViews();
204        int stackCount = stackViews.size();
205        for (int i = 0; i < stackCount; i++) {
206            TaskStackView stackView = stackViews.get(i);
207            TaskStack stack = stackView.getStack();
208            // Iterate the stack views and try and find the given task.
209            List<TaskView> taskViews = stackView.getTaskViews();
210            int taskViewCount = taskViews.size();
211            for (int j = 0; j < taskViewCount; j++) {
212                TaskView tv = taskViews.get(j);
213                if (tv.getTask() == task) {
214                    onTaskViewClicked(stackView, tv, stack, task, false);
215                    return true;
216                }
217            }
218        }
219        return false;
220    }
221
222    /** Launches the task that Recents was launched from, if possible */
223    public boolean launchPreviousTask() {
224        // Get the first stack view
225        List<TaskStackView> stackViews = getTaskStackViews();
226        int stackCount = stackViews.size();
227        for (int i = 0; i < stackCount; i++) {
228            TaskStackView stackView = stackViews.get(i);
229            TaskStack stack = stackView.getStack();
230            ArrayList<Task> tasks = stack.getTasks();
231
232            // Find the launch task in the stack
233            if (!tasks.isEmpty()) {
234                int taskCount = tasks.size();
235                for (int j = 0; j < taskCount; j++) {
236                    if (tasks.get(j).isLaunchTarget) {
237                        Task task = tasks.get(j);
238                        TaskView tv = stackView.getChildViewForTask(task);
239                        onTaskViewClicked(stackView, tv, stack, task, false);
240                        return true;
241                    }
242                }
243            }
244        }
245        return false;
246    }
247
248    /** Requests all task stacks to start their enter-recents animation */
249    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
250        // We have to increment/decrement the post animation trigger in case there are no children
251        // to ensure that it runs
252        ctx.postAnimationTrigger.increment();
253
254        List<TaskStackView> stackViews = getTaskStackViews();
255        int stackCount = stackViews.size();
256        for (int i = 0; i < stackCount; i++) {
257            TaskStackView stackView = stackViews.get(i);
258            stackView.startEnterRecentsAnimation(ctx);
259        }
260        ctx.postAnimationTrigger.decrement();
261    }
262
263    /** Requests all task stacks to start their exit-recents animation */
264    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
265        // We have to increment/decrement the post animation trigger in case there are no children
266        // to ensure that it runs
267        ctx.postAnimationTrigger.increment();
268        List<TaskStackView> stackViews = getTaskStackViews();
269        int stackCount = stackViews.size();
270        for (int i = 0; i < stackCount; i++) {
271            TaskStackView stackView = stackViews.get(i);
272            stackView.startExitToHomeAnimation(ctx);
273        }
274        ctx.postAnimationTrigger.decrement();
275
276        // Notify of the exit animation
277        mCb.onExitToHomeAnimationTriggered();
278    }
279
280    /** Adds the search bar */
281    public void setSearchBar(View searchBar) {
282        // Create the search bar (and hide it if we have no recent tasks)
283        if (Constants.DebugFlags.App.EnableSearchLayout) {
284            // Remove the previous search bar if one exists
285            if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
286                removeView(mSearchBar);
287            }
288            // Add the new search bar
289            if (searchBar != null) {
290                mSearchBar = searchBar;
291                addView(mSearchBar);
292            }
293        }
294    }
295
296    /** Returns whether there is currently a search bar */
297    public boolean hasSearchBar() {
298        return mSearchBar != null;
299    }
300
301    /** Sets the visibility of the search bar */
302    public void setSearchBarVisibility(int visibility) {
303        if (mSearchBar != null) {
304            mSearchBar.setVisibility(visibility);
305            // Always bring the search bar to the top
306            mSearchBar.bringToFront();
307        }
308    }
309
310    /**
311     * This is called with the full size of the window since we are handling our own insets.
312     */
313    @Override
314    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
315        int width = MeasureSpec.getSize(widthMeasureSpec);
316        int height = MeasureSpec.getSize(heightMeasureSpec);
317
318        // Get the search bar bounds and measure the search bar layout
319        if (mSearchBar != null) {
320            Rect searchBarSpaceBounds = new Rect();
321            mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds);
322            mSearchBar.measure(
323                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
324                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
325        }
326
327        Rect taskStackBounds = new Rect();
328        mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top,
329                mConfig.systemInsets.right, taskStackBounds);
330
331        // Measure each TaskStackView with the full width and height of the window since the
332        // transition view is a child of that stack view
333        List<TaskStackView> stackViews = getTaskStackViews();
334        List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews,
335                taskStackBounds);
336        int stackCount = stackViews.size();
337        for (int i = 0; i < stackCount; i++) {
338            TaskStackView stackView = stackViews.get(i);
339            if (stackView.getVisibility() != GONE) {
340                // We are going to measure the TaskStackView with the whole RecentsView dimensions,
341                // but the actual stack is going to be inset to the bounds calculated by the layout
342                // algorithm
343                stackView.setStackInsetRect(stackViewsBounds.get(i));
344                stackView.measure(widthMeasureSpec, heightMeasureSpec);
345            }
346        }
347
348        setMeasuredDimension(width, height);
349    }
350
351    /**
352     * This is called with the full size of the window since we are handling our own insets.
353     */
354    @Override
355    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
356        // Get the search bar bounds so that we lay it out
357        if (mSearchBar != null) {
358            Rect searchBarSpaceBounds = new Rect();
359            mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
360                    mConfig.systemInsets.top, searchBarSpaceBounds);
361            mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
362                    searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
363        }
364
365        // Layout each TaskStackView with the full width and height of the window since the
366        // transition view is a child of that stack view
367        List<TaskStackView> stackViews = getTaskStackViews();
368        int stackCount = stackViews.size();
369        for (int i = 0; i < stackCount; i++) {
370            TaskStackView stackView = stackViews.get(i);
371            if (stackView.getVisibility() != GONE) {
372                stackView.layout(left, top, left + stackView.getMeasuredWidth(),
373                        top + stackView.getMeasuredHeight());
374            }
375        }
376    }
377
378    @Override
379    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
380        // Update the configuration with the latest system insets and trigger a relayout
381        mConfig.updateSystemInsets(insets.getSystemWindowInsets());
382        requestLayout();
383        return insets.consumeSystemWindowInsets();
384    }
385
386    /** Notifies each task view of the user interaction. */
387    public void onUserInteraction() {
388        // Get the first stack view
389        List<TaskStackView> stackViews = getTaskStackViews();
390        int stackCount = stackViews.size();
391        for (int i = 0; i < stackCount; i++) {
392            TaskStackView stackView = stackViews.get(i);
393            stackView.onUserInteraction();
394        }
395    }
396
397    /** Focuses the next task in the first stack view */
398    public void focusNextTask(boolean forward) {
399        // Get the first stack view
400        List<TaskStackView> stackViews = getTaskStackViews();
401        if (!stackViews.isEmpty()) {
402            stackViews.get(0).focusNextTask(forward, true);
403        }
404    }
405
406    /** Dismisses the focused task. */
407    public void dismissFocusedTask() {
408        // Get the first stack view
409        List<TaskStackView> stackViews = getTaskStackViews();
410        if (!stackViews.isEmpty()) {
411            stackViews.get(0).dismissFocusedTask();
412        }
413    }
414
415    /** Unfilters any filtered stacks */
416    public boolean unfilterFilteredStacks() {
417        if (mStacks != null) {
418            // Check if there are any filtered stacks and unfilter them before we back out of Recents
419            boolean stacksUnfiltered = false;
420            int numStacks = mStacks.size();
421            for (int i = 0; i < numStacks; i++) {
422                TaskStack stack = mStacks.get(i);
423                if (stack.hasFilteredTasks()) {
424                    stack.unfilterTasks();
425                    stacksUnfiltered = true;
426                }
427            }
428            return stacksUnfiltered;
429        }
430        return false;
431    }
432
433    /**** TaskStackView.TaskStackCallbacks Implementation ****/
434
435    @Override
436    public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
437                                  final TaskStack stack, final Task task, final boolean lockToTask) {
438        // Notify any callbacks of the launching of a new task
439        if (mCb != null) {
440            mCb.onTaskViewClicked();
441        }
442
443        // Upfront the processing of the thumbnail
444        TaskViewTransform transform = new TaskViewTransform();
445        View sourceView;
446        int offsetX = 0;
447        int offsetY = 0;
448        float stackScroll = stackView.getScroller().getStackScroll();
449        if (tv == null) {
450            // If there is no actual task view, then use the stack view as the source view
451            // and then offset to the expected transform rect, but bound this to just
452            // outside the display rect (to ensure we don't animate from too far away)
453            sourceView = stackView;
454            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
455            offsetX = transform.rect.left;
456            offsetY = mConfig.displayRect.height();
457        } else {
458            sourceView = tv.mThumbnailView;
459            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
460        }
461
462        // Compute the thumbnail to scale up from
463        final SystemServicesProxy ssp =
464                RecentsTaskLoader.getInstance().getSystemServicesProxy();
465        ActivityOptions opts = null;
466        if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
467                task.thumbnail.getHeight() > 0) {
468            Bitmap b;
469            if (tv != null) {
470                // Disable any focused state before we draw the header
471                if (tv.isFocusedTask()) {
472                    tv.unsetFocusedTask();
473                }
474
475                float scale = tv.getScaleX();
476                int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
477                int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
478                b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
479                        Bitmap.Config.ARGB_8888);
480                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
481                    b.eraseColor(0xFFff0000);
482                } else {
483                    Canvas c = new Canvas(b);
484                    c.scale(tv.getScaleX(), tv.getScaleY());
485                    tv.mHeaderView.draw(c);
486                    c.setBitmap(null);
487                }
488            } else {
489                // Notify the system to skip the thumbnail layer by using an ALPHA_8 bitmap
490                b = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
491            }
492            ActivityOptions.OnAnimationStartedListener animStartedListener = null;
493            if (lockToTask) {
494                animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
495                    boolean mTriggered = false;
496                    @Override
497                    public void onAnimationStarted() {
498                        if (!mTriggered) {
499                            postDelayed(new Runnable() {
500                                @Override
501                                public void run() {
502                                    mCb.onScreenPinningRequest();
503                                }
504                            }, 350);
505                            mTriggered = true;
506                        }
507                    }
508                };
509            }
510            if (mConfig.multiStackEnabled) {
511                opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
512                        R.anim.recents_from_unknown_enter,
513                        R.anim.recents_from_unknown_exit,
514                        sourceView.getHandler(), animStartedListener);
515            } else {
516                opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
517                        b, offsetX, offsetY, transform.rect.width(), transform.rect.height(),
518                        sourceView.getHandler(), animStartedListener);
519            }
520        }
521
522        final ActivityOptions launchOpts = opts;
523        final Runnable launchRunnable = new Runnable() {
524            @Override
525            public void run() {
526                if (task.isActive) {
527                    // Bring an active task to the foreground
528                    ssp.moveTaskToFront(task.key.id, launchOpts);
529                } else {
530                    if (ssp.startActivityFromRecents(getContext(), task.key.id,
531                            task.activityLabel, launchOpts)) {
532                        if (launchOpts == null && lockToTask) {
533                            mCb.onScreenPinningRequest();
534                        }
535                    } else {
536                        // Dismiss the task and return the user to home if we fail to
537                        // launch the task
538                        onTaskViewDismissed(task);
539                        if (mCb != null) {
540                            mCb.onTaskLaunchFailed();
541                        }
542                    }
543                }
544            }
545        };
546
547        // Launch the app right away if there is no task view, otherwise, animate the icon out first
548        if (tv == null) {
549            launchRunnable.run();
550        } else {
551            if (!task.group.isFrontMostTask(task)) {
552                // For affiliated tasks that are behind other tasks, we must animate the front cards
553                // out of view before starting the task transition
554                stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask);
555            } else {
556                // Otherwise, we can start the task transition immediately
557                stackView.startLaunchTaskAnimation(tv, null, lockToTask);
558                launchRunnable.run();
559            }
560        }
561    }
562
563    @Override
564    public void onTaskViewAppInfoClicked(Task t) {
565        // Create a new task stack with the application info details activity
566        Intent baseIntent = t.key.baseIntent;
567        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
568                Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
569        intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
570        TaskStackBuilder.create(getContext())
571                .addNextIntentWithParentStack(intent).startActivities(null,
572                new UserHandle(t.key.userId));
573    }
574
575    @Override
576    public void onTaskViewDismissed(Task t) {
577        // Remove any stored data from the loader.  We currently don't bother notifying the views
578        // that the data has been unloaded because at the point we call onTaskViewDismissed(), the views
579        // either don't need to be updated, or have already been removed.
580        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
581        loader.deleteTaskData(t, false);
582
583        // Remove the old task from activity manager
584        loader.getSystemServicesProxy().removeTask(t.key.id);
585    }
586
587    @Override
588    public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) {
589        if (removedTasks != null) {
590            int taskCount = removedTasks.size();
591            for (int i = 0; i < taskCount; i++) {
592                onTaskViewDismissed(removedTasks.get(i));
593            }
594        }
595
596        mCb.onAllTaskViewsDismissed();
597    }
598
599    /** Final callback after Recents is finally hidden. */
600    public void onRecentsHidden() {
601        // Notify each task stack view
602        List<TaskStackView> stackViews = getTaskStackViews();
603        int stackCount = stackViews.size();
604        for (int i = 0; i < stackCount; i++) {
605            TaskStackView stackView = stackViews.get(i);
606            stackView.onRecentsHidden();
607        }
608    }
609
610    @Override
611    public void onTaskStackFilterTriggered() {
612        // Hide the search bar
613        if (mSearchBar != null) {
614            mSearchBar.animate()
615                    .alpha(0f)
616                    .setStartDelay(0)
617                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
618                    .setDuration(mConfig.filteringCurrentViewsAnimDuration)
619                    .withLayer()
620                    .start();
621        }
622    }
623
624    @Override
625    public void onTaskStackUnfilterTriggered() {
626        // Show the search bar
627        if (mSearchBar != null) {
628            mSearchBar.animate()
629                    .alpha(1f)
630                    .setStartDelay(0)
631                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
632                    .setDuration(mConfig.filteringNewViewsAnimDuration)
633                    .withLayer()
634                    .start();
635        }
636    }
637
638    @Override
639    public void onTaskResize(Task t) {
640        if (mCb != null) {
641            mCb.onTaskResize(t);
642        }
643    }
644
645    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
646
647    @Override
648    public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
649        // Propagate this event down to each task stack view
650        List<TaskStackView> stackViews = getTaskStackViews();
651        int stackCount = stackViews.size();
652        for (int i = 0; i < stackCount; i++) {
653            TaskStackView stackView = stackViews.get(i);
654            stackView.onPackagesChanged(monitor, packageName, userId);
655        }
656    }
657}
658