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