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