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