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