RecentsView.java revision b44c24fb50845dfbc1f49e78085cf5e01a32067f
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.content.ActivityNotFoundException;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.os.UserHandle;
27import android.view.View;
28import android.widget.FrameLayout;
29import com.android.systemui.recents.Console;
30import com.android.systemui.recents.Constants;
31import com.android.systemui.recents.RecentsConfiguration;
32import com.android.systemui.recents.RecentsTaskLoader;
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 widthMode = MeasureSpec.getMode(widthMeasureSpec);
112        int height = MeasureSpec.getSize(heightMeasureSpec);
113        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
114
115        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|measure]",
116                "width: " + width + " height: " + height, Console.AnsiGreen);
117        Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
118                Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onMeasure");
119
120        // We measure our stack views sans the status bar.  It will handle the nav bar itself.
121        RecentsConfiguration config = RecentsConfiguration.getInstance();
122        int childWidth = width - config.systemInsets.right;
123        int childHeight = height - config.systemInsets.top;
124
125        // Measure each child
126        int childCount = getChildCount();
127        for (int i = 0; i < childCount; i++) {
128            final View child = getChildAt(i);
129            if (child.getVisibility() != GONE) {
130                child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode),
131                        MeasureSpec.makeMeasureSpec(childHeight, heightMode));
132            }
133        }
134
135        setMeasuredDimension(width, height);
136    }
137
138    @Override
139    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
140        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|layout]",
141                new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
142        Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
143                Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onLayout");
144
145        // We offset our stack views by the status bar height.  It will handle the nav bar itself.
146        RecentsConfiguration config = RecentsConfiguration.getInstance();
147        top += config.systemInsets.top;
148
149        // Layout each child
150        // XXX: Based on the space node for that task view
151        int childCount = getChildCount();
152        for (int i = 0; i < childCount; i++) {
153            final View child = getChildAt(i);
154            if (child.getVisibility() != GONE) {
155                final int width = child.getMeasuredWidth();
156                final int height = child.getMeasuredHeight();
157                child.layout(left, top, left + width, top + height);
158            }
159        }
160    }
161
162    @Override
163    protected void dispatchDraw(Canvas canvas) {
164        Console.log(Constants.DebugFlags.UI.Draw, "[RecentsView|dispatchDraw]", "",
165                Console.AnsiPurple);
166        super.dispatchDraw(canvas);
167    }
168
169    @Override
170    protected boolean fitSystemWindows(Rect insets) {
171        Console.log(Constants.DebugFlags.UI.MeasureAndLayout,
172                "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
173
174        // Update the configuration with the latest system insets and trigger a relayout
175        RecentsConfiguration config = RecentsConfiguration.getInstance();
176        config.updateSystemInsets(insets);
177        requestLayout();
178
179        return true;
180    }
181
182    /** Unfilters any filtered stacks */
183    public boolean unfilterFilteredStacks() {
184        if (mBSP != null) {
185            // Check if there are any filtered stacks and unfilter them before we back out of Recents
186            boolean stacksUnfiltered = false;
187            ArrayList<TaskStack> stacks = mBSP.getStacks();
188            for (TaskStack stack : stacks) {
189                if (stack.hasFilteredTasks()) {
190                    stack.unfilterTasks();
191                    stacksUnfiltered = true;
192                }
193            }
194            return stacksUnfiltered;
195        }
196        return false;
197    }
198
199    /**** TaskStackView.TaskStackCallbacks Implementation ****/
200
201    @Override
202    public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
203                               final TaskStack stack, final Task task) {
204        // Notify any callbacks of the launching of a new task
205        if (mCb != null) {
206            mCb.onTaskLaunching();
207        }
208
209        final Runnable launchRunnable = new Runnable() {
210            @Override
211            public void run() {
212                TaskViewTransform transform;
213                View sourceView = tv;
214                int offsetX = 0;
215                int offsetY = 0;
216                int stackScroll = stackView.getStackScroll();
217                if (tv == null) {
218                    // If there is no actual task view, then use the stack view as the source view
219                    // and then offset to the expected transform rect, but bound this to just
220                    // outside the display rect (to ensure we don't animate from too far away)
221                    RecentsConfiguration config = RecentsConfiguration.getInstance();
222                    sourceView = stackView;
223                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
224                    offsetX = transform.rect.left;
225                    offsetY = Math.min(transform.rect.top, config.displayRect.height());
226                } else {
227                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
228                }
229
230                // Compute the thumbnail to scale up from
231                ActivityOptions opts = null;
232                int thumbnailWidth = transform.rect.width();
233                int thumbnailHeight = transform.rect.height();
234                if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
235                        task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
236                    // Resize the thumbnail to the size of the view that we are animating from
237                    Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
238                            Bitmap.Config.ARGB_8888);
239                    Canvas c = new Canvas(b);
240                    c.drawBitmap(task.thumbnail,
241                            new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
242                            new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
243                    c.setBitmap(null);
244                    opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
245                            b, offsetX, offsetY);
246                }
247
248
249                if (task.isActive) {
250                    // Bring an active task to the foreground
251                    RecentsTaskLoader.getInstance().getSystemServicesProxy()
252                            .moveTaskToFront(task.key.id, opts);
253                } else {
254                    // Launch the activity with the desired animation
255                    Intent i = new Intent(task.key.baseIntent);
256                    i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
257                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME
258                            | Intent.FLAG_ACTIVITY_NEW_TASK);
259                    try {
260                        UserHandle taskUser = new UserHandle(task.userId);
261                        if (opts != null) {
262                            getContext().startActivityAsUser(i, opts.toBundle(), taskUser);
263                        } else {
264                            getContext().startActivityAsUser(i, taskUser);
265                        }
266                    } catch (ActivityNotFoundException anfe) {
267                        Console.logError(getContext(), "Could not start Activity");
268                    }
269                }
270
271                Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
272                        Constants.DebugFlags.App.TimeRecentsLaunchKey, "startActivity");
273            }
274        };
275
276        Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
277                Constants.DebugFlags.App.TimeRecentsLaunchKey, "onTaskLaunched");
278
279        // Launch the app right away if there is no task view, otherwise, animate the icon out first
280        if (tv == null || !Constants.Values.TaskView.AnimateFrontTaskBarOnLeavingRecents) {
281            post(launchRunnable);
282        } else {
283            tv.animateOnLeavingRecents(launchRunnable);
284        }
285    }
286}
287