Home | History | Annotate | Download | only in views
      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 {
     65         public void onTaskViewClicked();
     66         public void onTaskLaunchFailed();
     67         public void onAllTaskViewsDismissed();
     68         public void onExitToHomeAnimationTriggered();
     69         public void onScreenPinningRequest();
     70         public void onTaskResize(Task t);
     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 
     84     public RecentsView(Context context) {
     85         super(context);
     86     }
     87 
     88     public RecentsView(Context context, AttributeSet attrs) {
     89         this(context, attrs, 0);
     90     }
     91 
     92     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
     93         this(context, attrs, defStyleAttr, 0);
     94     }
     95 
     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 */
    104     public void setCallbacks(RecentsViewCallbacks cb) {
    105         mCb = cb;
    106     }
    107 
    108     /** Sets the debug overlay */
    109     public void setDebugOverlay(DebugOverlayView overlay) {
    110         mDebugOverlay = overlay;
    111     }
    112 
    113     /** Set/get the bsp root node */
    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 */
    157     List<TaskStackView> getTaskStackViews() {
    158         return mTaskStackViews;
    159     }
    160 
    161     /** Gets the next task in the stack - or if the last - the top task */
    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 */
    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. */
    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 */
    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 */
    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 */
    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 */
    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 */
    303     public boolean hasValidSearchBar() {
    304         return mSearchBar != null && !mSearchBar.isReinflateRequired();
    305     }
    306 
    307     /** Sets the visibility of the search bar */
    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
    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
    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
    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. */
    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 */
    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. */
    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 */
    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 
    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 
    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
    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
    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
    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
    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. */
    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
    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
    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
    704     public void onTaskResize(Task t) {
    705         if (mCb != null) {
    706             mCb.onTaskResize(t);
    707         }
    708     }
    709 
    710     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
    711 
    712     @Override
    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