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