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