RecentsView.java revision 863db8a4e7906826bc105f49a4596dc7b336088a
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    /** Unfilters any filtered stacks */
305    public boolean unfilterFilteredStacks() {
306        if (mBSP != null) {
307            // Check if there are any filtered stacks and unfilter them before we back out of Recents
308            boolean stacksUnfiltered = false;
309            ArrayList<TaskStack> stacks = mBSP.getStacks();
310            for (TaskStack stack : stacks) {
311                if (stack.hasFilteredTasks()) {
312                    stack.unfilterTasks();
313                    stacksUnfiltered = true;
314                }
315            }
316            return stacksUnfiltered;
317        }
318        return false;
319    }
320
321    /**** TaskStackView.TaskStackCallbacks Implementation ****/
322
323    @Override
324    public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
325                               final TaskStack stack, final Task task) {
326        // Notify any callbacks of the launching of a new task
327        if (mCb != null) {
328            mCb.onTaskLaunching();
329        }
330
331        final Runnable launchRunnable = new Runnable() {
332            @Override
333            public void run() {
334                TaskViewTransform transform;
335                View sourceView = tv;
336                int offsetX = 0;
337                int offsetY = 0;
338                int stackScroll = stackView.getStackScroll();
339                if (tv == null) {
340                    // If there is no actual task view, then use the stack view as the source view
341                    // and then offset to the expected transform rect, but bound this to just
342                    // outside the display rect (to ensure we don't animate from too far away)
343                    RecentsConfiguration config = RecentsConfiguration.getInstance();
344                    sourceView = stackView;
345                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
346                    offsetX = transform.rect.left;
347                    offsetY = Math.min(transform.rect.top, config.displayRect.height());
348                } else {
349                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
350                }
351
352                // Compute the thumbnail to scale up from
353                ActivityOptions opts = null;
354                int thumbnailWidth = transform.rect.width();
355                int thumbnailHeight = transform.rect.height();
356                if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
357                        task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
358                    // Resize the thumbnail to the size of the view that we are animating from
359                    Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
360                            Bitmap.Config.ARGB_8888);
361                    Canvas c = new Canvas(b);
362                    c.drawBitmap(task.thumbnail,
363                            new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
364                            new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
365                    c.setBitmap(null);
366                    opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
367                            b, offsetX, offsetY);
368                }
369
370                if (task.isActive) {
371                    // Bring an active task to the foreground
372                    RecentsTaskLoader.getInstance().getSystemServicesProxy()
373                            .moveTaskToFront(task.key.id, opts);
374                } else {
375                    // Launch the activity anew with the desired animation
376                    Intent i = new Intent(task.key.baseIntent);
377                    i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
378                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME
379                            | Intent.FLAG_ACTIVITY_NEW_TASK);
380                    try {
381                        UserHandle taskUser = new UserHandle(task.userId);
382                        if (opts != null) {
383                            getContext().startActivityAsUser(i, opts.toBundle(), taskUser);
384                        } else {
385                            getContext().startActivityAsUser(i, taskUser);
386                        }
387                    } catch (ActivityNotFoundException anfe) {
388                        Console.logError(getContext(), "Could not start Activity");
389                    }
390
391                    // And clean up the old task
392                    onTaskRemoved(task);
393                }
394
395                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
396                        Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
397            }
398        };
399
400        Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
401                Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
402
403        // Launch the app right away if there is no task view, otherwise, animate the icon out first
404        if (tv == null) {
405            post(launchRunnable);
406        } else {
407            tv.animateOnLeavingRecents(launchRunnable);
408        }
409    }
410
411    @Override
412    public void onTaskAppInfoLaunched(Task t) {
413        // Create a new task stack with the application info details activity
414        Intent baseIntent = t.key.baseIntent;
415        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
416                Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
417        intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
418        TaskStackBuilder.create(getContext())
419                .addNextIntentWithParentStack(intent).startActivities();
420    }
421
422    @Override
423    public void onTaskRemoved(Task t) {
424        // Remove any stored data from the loader.  We currently don't bother notifying the views
425        // that the data has been unloaded because at the point we call onTaskRemoved(), the views
426        // either don't need to be updated, or have already been removed.
427        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
428        loader.deleteTaskData(t, false);
429
430        // Remove the old task from activity manager
431        int flags = t.key.baseIntent.getFlags();
432        boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) ==
433                Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
434        RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id,
435                isDocument);
436    }
437
438    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
439
440    @Override
441    public void onComponentRemoved(Set<ComponentName> cns) {
442        // Propagate this event down to each task stack view
443        int childCount = getChildCount();
444        for (int i = 0; i < childCount; i++) {
445            View child = getChildAt(i);
446            if (child instanceof TaskStackView) {
447                TaskStackView stackView = (TaskStackView) child;
448                stackView.onComponentRemoved(cns);
449            }
450        }
451    }
452}
453