RecentsView.java revision 4be0445c66f19ed985aecbc2a572f5d8c6e2553a
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.ActivityManager;
20import android.app.ActivityOptions;
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.os.UserHandle;
28import android.view.View;
29import android.widget.FrameLayout;
30import com.android.systemui.recents.Console;
31import com.android.systemui.recents.Constants;
32import com.android.systemui.recents.RecentsConfiguration;
33import com.android.systemui.recents.model.SpaceNode;
34import com.android.systemui.recents.model.Task;
35import com.android.systemui.recents.model.TaskStack;
36
37import java.util.ArrayList;
38
39
40/**
41 * This view is the the top level layout that contains TaskStacks (which are laid out according
42 * to their SpaceNode bounds.
43 */
44public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
45
46    /** The RecentsView callbacks */
47    public interface RecentsViewCallbacks {
48        public void onTaskLaunching();
49    }
50
51    // The space partitioning root of this container
52    SpaceNode mBSP;
53    // Recents view callbacks
54    RecentsViewCallbacks mCb;
55
56    public RecentsView(Context context) {
57        super(context);
58        setWillNotDraw(false);
59    }
60
61    /** Sets the callbacks */
62    public void setCallbacks(RecentsViewCallbacks cb) {
63        mCb = cb;
64    }
65
66    /** Set/get the bsp root node */
67    public void setBSP(SpaceNode n) {
68        mBSP = n;
69
70        // Create and add all the stacks for this partition of space.
71        removeAllViews();
72        ArrayList<TaskStack> stacks = mBSP.getStacks();
73        for (TaskStack stack : stacks) {
74            TaskStackView stackView = new TaskStackView(getContext(), stack);
75            stackView.setCallbacks(this);
76            addView(stackView);
77        }
78    }
79
80    /** Launches the first task from the first stack if possible */
81    public boolean launchFirstTask() {
82        // Get the first stack view
83        int childCount = getChildCount();
84        for (int i = 0; i < childCount; i++) {
85            TaskStackView stackView = (TaskStackView) getChildAt(i);
86            TaskStack stack = stackView.mStack;
87            ArrayList<Task> tasks = stack.getTasks();
88
89            // Get the first task in the stack
90            if (!tasks.isEmpty()) {
91                Task task = tasks.get(tasks.size() - 1);
92                TaskView tv = null;
93
94                // Try and use the first child task view as the source of the launch animation
95                if (stackView.getChildCount() > 0) {
96                    TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1);
97                    if (stv.getTask() == task) {
98                        tv = stv;
99                    }
100                }
101                onTaskLaunched(stackView, tv, stack, task);
102                return true;
103            }
104        }
105        return false;
106    }
107
108    @Override
109    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
110        int width = MeasureSpec.getSize(widthMeasureSpec);
111        int height = MeasureSpec.getSize(heightMeasureSpec);
112        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
113
114        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|measure]",
115                "width: " + width + " height: " + height, Console.AnsiGreen);
116        Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
117                Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onMeasure");
118
119        // We measure our stack views sans the status bar.  It will handle the nav bar itself.
120        RecentsConfiguration config = RecentsConfiguration.getInstance();
121        int childHeight = height - config.systemInsets.top;
122
123        // Measure each child
124        int childCount = getChildCount();
125        for (int i = 0; i < childCount; i++) {
126            final View child = getChildAt(i);
127            if (child.getVisibility() != GONE) {
128                child.measure(widthMeasureSpec,
129                        MeasureSpec.makeMeasureSpec(childHeight, heightMode));
130            }
131        }
132
133        setMeasuredDimension(width, height);
134    }
135
136    @Override
137    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
138        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|layout]",
139                new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
140        Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
141                Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onLayout");
142
143        // We offset our stack views by the status bar height.  It will handle the nav bar itself.
144        RecentsConfiguration config = RecentsConfiguration.getInstance();
145        top += config.systemInsets.top;
146
147        // Layout each child
148        // XXX: Based on the space node for that task view
149        int childCount = getChildCount();
150        for (int i = 0; i < childCount; i++) {
151            final View child = getChildAt(i);
152            if (child.getVisibility() != GONE) {
153                final int width = child.getMeasuredWidth();
154                final int height = child.getMeasuredHeight();
155                child.layout(left, top, left + width, top + height);
156            }
157        }
158    }
159
160    @Override
161    protected void dispatchDraw(Canvas canvas) {
162        Console.log(Constants.DebugFlags.UI.Draw, "[RecentsView|dispatchDraw]", "",
163                Console.AnsiPurple);
164        super.dispatchDraw(canvas);
165    }
166
167    @Override
168    protected boolean fitSystemWindows(Rect insets) {
169        Console.log(Constants.DebugFlags.UI.MeasureAndLayout,
170                "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
171
172        // Update the configuration with the latest system insets and trigger a relayout
173        RecentsConfiguration config = RecentsConfiguration.getInstance();
174        config.updateSystemInsets(insets);
175        requestLayout();
176
177        return true;
178    }
179
180    /** Unfilters any filtered stacks */
181    public boolean unfilterFilteredStacks() {
182        if (mBSP != null) {
183            // Check if there are any filtered stacks and unfilter them before we back out of Recents
184            boolean stacksUnfiltered = false;
185            ArrayList<TaskStack> stacks = mBSP.getStacks();
186            for (TaskStack stack : stacks) {
187                if (stack.hasFilteredTasks()) {
188                    stack.unfilterTasks();
189                    stacksUnfiltered = true;
190                }
191            }
192            return stacksUnfiltered;
193        }
194        return false;
195    }
196
197    /**** TaskStackView.TaskStackCallbacks Implementation ****/
198
199    @Override
200    public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
201                               final TaskStack stack, final Task task) {
202        // Notify any callbacks of the launching of a new task
203        if (mCb != null) {
204            mCb.onTaskLaunching();
205        }
206
207        final Runnable launchRunnable = new Runnable() {
208            @Override
209            public void run() {
210                TaskViewTransform transform;
211                View sourceView = tv;
212                int offsetX = 0;
213                int offsetY = 0;
214                if (tv == null) {
215                    // If there is no actual task view, then use the stack view as the source view
216                    // and then offset to the expected transform rect, but bound this to just
217                    // outside the display rect (to ensure we don't animate from too far away)
218                    RecentsConfiguration config = RecentsConfiguration.getInstance();
219                    sourceView = stackView;
220                    transform = stackView.getStackTransform(stack.indexOfTask(task));
221                    offsetX = transform.rect.left;
222                    offsetY = Math.min(transform.rect.top, config.displayRect.height());
223                } else {
224                    transform = stackView.getStackTransform(stack.indexOfTask(task));
225                }
226
227                // Compute the thumbnail to scale up from
228                ActivityOptions opts = null;
229                int thumbnailWidth = transform.rect.width();
230                int thumbnailHeight = transform.rect.height();
231                if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
232                        task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
233                    // Resize the thumbnail to the size of the view that we are animating from
234                    Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
235                            Bitmap.Config.ARGB_8888);
236                    Canvas c = new Canvas(b);
237                    c.drawBitmap(task.thumbnail,
238                            new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
239                            new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
240                    c.setBitmap(null);
241                    opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
242                            b, offsetX, offsetY);
243                }
244
245
246                if (task.isActive) {
247                    // Bring an active task to the foreground
248                    ActivityManager am = (ActivityManager)
249                            stackView.getContext().getSystemService(Context.ACTIVITY_SERVICE);
250                    if (opts != null) {
251                        am.moveTaskToFront(task.key.id, ActivityManager.MOVE_TASK_WITH_HOME,
252                                opts.toBundle());
253                    } else {
254                        am.moveTaskToFront(task.key.id, ActivityManager.MOVE_TASK_WITH_HOME);
255                    }
256                } else {
257                    // Launch the activity with the desired animation
258                    Intent i = new Intent(task.key.intent);
259                    i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
260                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME
261                            | Intent.FLAG_ACTIVITY_NEW_TASK);
262                    try {
263                        if (opts != null) {
264                            getContext().startActivityAsUser(i, opts.toBundle(),
265                                    UserHandle.CURRENT);
266                        } else {
267                            getContext().startActivityAsUser(i, UserHandle.CURRENT);
268                        }
269                    } catch (ActivityNotFoundException anfe) {
270                        Console.logError(getContext(), "Could not start Activity");
271                    }
272                }
273
274                Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
275                        Constants.DebugFlags.App.TimeRecentsLaunchKey, "startActivity");
276            }
277        };
278
279        Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
280                Constants.DebugFlags.App.TimeRecentsLaunchKey, "onTaskLaunched");
281
282        // Launch the app right away if there is no task view, otherwise, animate the icon out first
283        if (tv == null || !Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingRecents) {
284            post(launchRunnable);
285        } else {
286            tv.animateOnLeavingRecents(launchRunnable);
287        }
288    }
289}
290