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