OSDN Git Service

am 414ea81e: Merge "Ensure notifications are sent when locking/unlocking location...
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / recents / views / RecentsView.java
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 {
54         public void onTaskViewClicked();
55         public void onTaskLaunchFailed();
56         public void onAllTaskViewsDismissed();
57         public void onExitToHomeAnimationTriggered();
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
69     public RecentsView(Context context) {
70         super(context);
71     }
72
73     public RecentsView(Context context, AttributeSet attrs) {
74         this(context, attrs, 0);
75     }
76
77     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
78         this(context, attrs, defStyleAttr, 0);
79     }
80
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 */
88     public void setCallbacks(RecentsViewCallbacks cb) {
89         mCb = cb;
90     }
91
92     /** Sets the debug overlay */
93     public void setDebugOverlay(DebugOverlayView overlay) {
94         mDebugOverlay = overlay;
95     }
96
97     /** Set/get the bsp root node */
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 */
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 */
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 */
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 */
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 */
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 */
258     public boolean hasSearchBar() {
259         return mSearchBar != null;
260     }
261
262     /** Sets the visibility of the search bar */
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
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
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
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. */
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 */
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. */
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 */
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
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
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
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         RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id);
546     }
547
548     @Override
549     public void onAllTaskViewsDismissed() {
550         mCb.onAllTaskViewsDismissed();
551     }
552
553     /** Final callback after Recents is finally hidden. */
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
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
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
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 }