RecentsView.java revision 10f8139d3b8dd7cd08a2fc688285b3b74a34f0db
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
17package com.android.systemui.recents.views;
18
19import android.app.ActivityOptions;
20import android.app.TaskStackBuilder;
21import android.content.ActivityNotFoundException;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.graphics.Bitmap;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.net.Uri;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.WindowInsets;
34import android.widget.FrameLayout;
35import com.android.systemui.recents.Console;
36import com.android.systemui.recents.Constants;
37import com.android.systemui.recents.RecentsConfiguration;
38import com.android.systemui.recents.RecentsPackageMonitor;
39import com.android.systemui.recents.RecentsTaskLoader;
40import com.android.systemui.recents.model.SpaceNode;
41import com.android.systemui.recents.model.Task;
42import com.android.systemui.recents.model.TaskStack;
43
44import java.util.ArrayList;
45import java.util.Set;
46
47
48/**
49 * This view is the the top level layout that contains TaskStacks (which are laid out according
50 * to their SpaceNode bounds.
51 */
52public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks,
53        RecentsPackageMonitor.PackageCallbacks {
54
55    /** The RecentsView callbacks */
56    public interface RecentsViewCallbacks {
57        public void onTaskLaunching();
58    }
59
60    // The space partitioning root of this container
61    SpaceNode mBSP;
62    // Whether there are any tasks
63    boolean mHasTasks;
64    // Search bar view
65    View mSearchBar;
66    // Recents view callbacks
67    RecentsViewCallbacks mCb;
68
69    LayoutInflater mInflater;
70
71    public RecentsView(Context context) {
72        super(context);
73        mInflater = LayoutInflater.from(context);
74        setWillNotDraw(false);
75    }
76
77    /** Sets the callbacks */
78    public void setCallbacks(RecentsViewCallbacks cb) {
79        mCb = cb;
80    }
81
82    /** Set/get the bsp root node */
83    public void setBSP(SpaceNode n) {
84        mBSP = n;
85
86        // Create and add all the stacks for this partition of space.
87        mHasTasks = false;
88        removeAllViews();
89        ArrayList<TaskStack> stacks = mBSP.getStacks();
90        for (TaskStack stack : stacks) {
91            TaskStackView stackView = new TaskStackView(getContext(), stack);
92            stackView.setCallbacks(this);
93            addView(stackView);
94            mHasTasks |= (stack.getTaskCount() > 0);
95        }
96    }
97
98    /** Launches the focused task from the first stack if possible */
99    public boolean launchFocusedTask() {
100        // Get the first stack view
101        int childCount = getChildCount();
102        for (int i = 0; i < childCount; i++) {
103            View child = getChildAt(i);
104            if (child instanceof TaskStackView) {
105                TaskStackView stackView = (TaskStackView) child;
106                TaskStack stack = stackView.mStack;
107                // Iterate the stack views and try and find the focused task
108                int taskCount = stackView.getChildCount();
109                for (int j = 0; j < taskCount; j++) {
110                    TaskView tv = (TaskView) stackView.getChildAt(j);
111                    Task task = tv.getTask();
112                    if (tv.isFocusedTask()) {
113                        if (Console.Enabled) {
114                            Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
115                                    "Found focused Task");
116                        }
117                        onTaskLaunched(stackView, tv, stack, task);
118                        return true;
119                    }
120                }
121            }
122        }
123        if (Console.Enabled) {
124            Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
125                    "No Tasks focused");
126        }
127        return false;
128    }
129
130    /** Launches the first task from the first stack if possible */
131    public boolean launchFirstTask() {
132        // Get the first stack view
133        int childCount = getChildCount();
134        for (int i = 0; i < childCount; i++) {
135            View child = getChildAt(i);
136            if (child instanceof TaskStackView) {
137                TaskStackView stackView = (TaskStackView) child;
138                TaskStack stack = stackView.mStack;
139                ArrayList<Task> tasks = stack.getTasks();
140
141                // Get the first task in the stack
142                if (!tasks.isEmpty()) {
143                    Task task = tasks.get(tasks.size() - 1);
144                    TaskView tv = null;
145
146                    // Try and use the first child task view as the source of the launch animation
147                    if (stackView.getChildCount() > 0) {
148                        TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1);
149                        if (stv.getTask() == task) {
150                            tv = stv;
151                        }
152                    }
153                    onTaskLaunched(stackView, tv, stack, task);
154                    return true;
155                }
156            }
157        }
158        return false;
159    }
160
161    /** Adds the search bar */
162    public void setSearchBar(View searchBar) {
163        // Create the search bar (and hide it if we have no recent tasks)
164        if (Constants.DebugFlags.App.EnableSearchLayout) {
165            // Remove the previous search bar if one exists
166            if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
167                removeView(mSearchBar);
168            }
169            // Add the new search bar
170            if (searchBar != null) {
171                mSearchBar = searchBar;
172                mSearchBar.setVisibility(mHasTasks ? View.VISIBLE : View.GONE);
173                addView(mSearchBar);
174
175                if (Console.Enabled) {
176                    Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsView|setSearchBar]",
177                            "" + (mSearchBar.getVisibility() == View.VISIBLE),
178                            Console.AnsiBlue);
179                }
180            }
181        }
182    }
183
184    /**
185     * This is called with the full size of the window since we are handling our own insets.
186     */
187    @Override
188    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
189        int width = MeasureSpec.getSize(widthMeasureSpec);
190        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
191        int height = MeasureSpec.getSize(heightMeasureSpec);
192        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
193
194        if (Console.Enabled) {
195            Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|measure]",
196                    "width: " + width + " height: " + height, Console.AnsiGreen);
197            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
198                    Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onMeasure");
199        }
200
201        // Get the search bar bounds and measure the search bar layout
202        RecentsConfiguration config = RecentsConfiguration.getInstance();
203        if (mSearchBar != null) {
204            Rect searchBarSpaceBounds = new Rect();
205            config.getSearchBarBounds(width, height - config.systemInsets.top, searchBarSpaceBounds);
206            mSearchBar.measure(
207                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
208                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
209        }
210
211        // We give the full width of the space, not including the right nav bar insets in landscape,
212        // to the stack view, since we want the tasks to render under the search bar in landscape.
213        // In addition, we give it the full height, not including the top inset or search bar space,
214        // since we want the tasks to render under the navigation buttons in portrait.
215        Rect taskStackBounds = new Rect();
216        config.getTaskStackBounds(width, height, taskStackBounds);
217        int childWidth = width - config.systemInsets.right;
218        int childHeight = taskStackBounds.height() - config.systemInsets.top;
219
220        // Measure each TaskStackView
221        int childCount = getChildCount();
222        for (int i = 0; i < childCount; i++) {
223            View child = getChildAt(i);
224            if (child instanceof TaskStackView && child.getVisibility() != GONE) {
225                child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode),
226                        MeasureSpec.makeMeasureSpec(childHeight, heightMode));
227            }
228        }
229
230        setMeasuredDimension(width, height);
231    }
232
233    /**
234     * This is called with the full size of the window since we are handling our own insets.
235     */
236    @Override
237    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
238        if (Console.Enabled) {
239            Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|layout]",
240                    new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
241            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
242                    Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onLayout");
243        }
244
245        // Get the search bar bounds so that we lay it out
246        RecentsConfiguration config = RecentsConfiguration.getInstance();
247        if (mSearchBar != null) {
248            Rect searchBarSpaceBounds = new Rect();
249            config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds);
250            mSearchBar.layout(config.systemInsets.left + searchBarSpaceBounds.left,
251                    config.systemInsets.top + searchBarSpaceBounds.top,
252                    config.systemInsets.left + mSearchBar.getMeasuredWidth(),
253                    config.systemInsets.top + mSearchBar.getMeasuredHeight());
254        }
255
256        // We offset the stack view by the left inset (if any), but lay it out under the search bar.
257        // In addition, we offset our stack views by the top inset and search bar height, but not
258        // the bottom insets because we want it to render under the navigation buttons.
259        Rect taskStackBounds = new Rect();
260        config.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
261        left += config.systemInsets.left;
262        top += config.systemInsets.top + taskStackBounds.top;
263
264        // Layout each child
265        // XXX: Based on the space node for that task view
266        int childCount = getChildCount();
267        for (int i = 0; i < childCount; i++) {
268            View child = getChildAt(i);
269            if (child instanceof TaskStackView && child.getVisibility() != GONE) {
270                TaskStackView tsv = (TaskStackView) child;
271                child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight());
272            }
273        }
274    }
275
276    /** Focuses the next task in the first stack view */
277    public void focusNextTask(boolean forward) {
278        // Get the first stack view
279        TaskStackView stackView = null;
280        int childCount = getChildCount();
281        for (int i = 0; i < childCount; i++) {
282            View child = getChildAt(i);
283            if (child instanceof TaskStackView) {
284                stackView = (TaskStackView) child;
285                break;
286            }
287        }
288
289        if (stackView != null) {
290            stackView.focusNextTask(forward);
291        }
292    }
293
294    @Override
295    protected void dispatchDraw(Canvas canvas) {
296        if (Console.Enabled) {
297            Console.log(Constants.Log.UI.Draw, "[RecentsView|dispatchDraw]", "",
298                    Console.AnsiPurple);
299        }
300        super.dispatchDraw(canvas);
301    }
302
303    @Override
304    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
305        if (Console.Enabled) {
306            Console.log(Constants.Log.UI.MeasureAndLayout,
307                    "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
308        }
309
310        // Update the configuration with the latest system insets and trigger a relayout
311        RecentsConfiguration config = RecentsConfiguration.getInstance();
312        config.updateSystemInsets(insets.getSystemWindowInsets());
313        requestLayout();
314
315        return insets.consumeSystemWindowInsets(false, false, false, true);
316    }
317
318    /** Closes any open info panes */
319    public boolean closeOpenInfoPanes() {
320        if (mBSP != null) {
321            // Get the first stack view
322            int childCount = getChildCount();
323            for (int i = 0; i < childCount; i++) {
324                View child = getChildAt(i);
325                if (child instanceof TaskStackView) {
326                    TaskStackView stackView = (TaskStackView) child;
327                    if (stackView.closeOpenInfoPanes()) {
328                        return true;
329                    }
330                }
331            }
332        }
333        return false;
334    }
335
336    /** Unfilters any filtered stacks */
337    public boolean unfilterFilteredStacks() {
338        if (mBSP != null) {
339            // Check if there are any filtered stacks and unfilter them before we back out of Recents
340            boolean stacksUnfiltered = false;
341            ArrayList<TaskStack> stacks = mBSP.getStacks();
342            for (TaskStack stack : stacks) {
343                if (stack.hasFilteredTasks()) {
344                    stack.unfilterTasks();
345                    stacksUnfiltered = true;
346                }
347            }
348            return stacksUnfiltered;
349        }
350        return false;
351    }
352
353    /**** TaskStackView.TaskStackCallbacks Implementation ****/
354
355    @Override
356    public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
357                               final TaskStack stack, final Task task) {
358        // Notify any callbacks of the launching of a new task
359        if (mCb != null) {
360            mCb.onTaskLaunching();
361        }
362
363        // Close any open info panes
364        closeOpenInfoPanes();
365
366        final Runnable launchRunnable = new Runnable() {
367            @Override
368            public void run() {
369                TaskViewTransform transform;
370                View sourceView = tv;
371                int offsetX = 0;
372                int offsetY = 0;
373                int stackScroll = stackView.getStackScroll();
374                if (tv == null) {
375                    // If there is no actual task view, then use the stack view as the source view
376                    // and then offset to the expected transform rect, but bound this to just
377                    // outside the display rect (to ensure we don't animate from too far away)
378                    RecentsConfiguration config = RecentsConfiguration.getInstance();
379                    sourceView = stackView;
380                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
381                    offsetX = transform.rect.left;
382                    offsetY = Math.min(transform.rect.top, config.displayRect.height());
383                } else {
384                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
385                }
386
387                // Compute the thumbnail to scale up from
388                ActivityOptions opts = null;
389                int thumbnailWidth = transform.rect.width();
390                int thumbnailHeight = transform.rect.height();
391                if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
392                        task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
393                    // Resize the thumbnail to the size of the view that we are animating from
394                    Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
395                            Bitmap.Config.ARGB_8888);
396                    Canvas c = new Canvas(b);
397                    c.drawBitmap(task.thumbnail,
398                            new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
399                            new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
400                    c.setBitmap(null);
401                    opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
402                            b, offsetX, offsetY);
403                }
404
405                if (task.isActive) {
406                    // Bring an active task to the foreground
407                    RecentsTaskLoader.getInstance().getSystemServicesProxy()
408                            .moveTaskToFront(task.key.id, opts);
409                } else {
410                    // Launch the activity anew with the desired animation
411                    Intent i = new Intent(task.key.baseIntent);
412                    i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
413                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME
414                            | Intent.FLAG_ACTIVITY_NEW_TASK);
415                    try {
416                        UserHandle taskUser = new UserHandle(task.userId);
417                        if (opts != null) {
418                            getContext().startActivityAsUser(i, opts.toBundle(), taskUser);
419                        } else {
420                            getContext().startActivityAsUser(i, taskUser);
421                        }
422                    } catch (ActivityNotFoundException anfe) {
423                        Console.logError(getContext(), "Could not start Activity");
424                    }
425
426                    // And clean up the old task
427                    onTaskRemoved(task);
428                }
429
430                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
431                        Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
432            }
433        };
434
435        Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
436                Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
437
438        // Launch the app right away if there is no task view, otherwise, animate the icon out first
439        if (tv == null) {
440            post(launchRunnable);
441        } else {
442            tv.animateOnLeavingRecents(launchRunnable);
443        }
444    }
445
446    @Override
447    public void onTaskAppInfoLaunched(Task t) {
448        // Create a new task stack with the application info details activity
449        Intent baseIntent = t.key.baseIntent;
450        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
451                Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
452        intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
453        TaskStackBuilder.create(getContext())
454                .addNextIntentWithParentStack(intent).startActivities();
455    }
456
457    @Override
458    public void onTaskRemoved(Task t) {
459        // Remove any stored data from the loader.  We currently don't bother notifying the views
460        // that the data has been unloaded because at the point we call onTaskRemoved(), the views
461        // either don't need to be updated, or have already been removed.
462        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
463        loader.deleteTaskData(t, false);
464
465        // Remove the old task from activity manager
466        int flags = t.key.baseIntent.getFlags();
467        boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) ==
468                Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
469        RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id,
470                isDocument);
471    }
472
473    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
474
475    @Override
476    public void onComponentRemoved(Set<ComponentName> cns) {
477        // Propagate this event down to each task stack view
478        int childCount = getChildCount();
479        for (int i = 0; i < childCount; i++) {
480            View child = getChildAt(i);
481            if (child instanceof TaskStackView) {
482                TaskStackView stackView = (TaskStackView) child;
483                stackView.onComponentRemoved(cns);
484            }
485        }
486    }
487}
488