blob: 1df4670a7072062756189e439bb14493aab7bd8f [file] [log] [blame]
Winson Chung303e1ff2014-03-07 15:06:19 -08001/*
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;
Winson Chung9f9679d2014-04-11 16:49:09 -070020import android.app.TaskStackBuilder;
Winson Chungc620baf2014-03-19 17:15:29 -070021import android.content.ActivityNotFoundException;
Winson Chung303e1ff2014-03-07 15:06:19 -080022import android.content.Context;
23import android.content.Intent;
24import android.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Rect;
Winson Chung9f9679d2014-04-11 16:49:09 -070027import android.net.Uri;
Winson Chung303e1ff2014-03-07 15:06:19 -080028import android.os.UserHandle;
Winson Chung9f9679d2014-04-11 16:49:09 -070029import android.provider.Settings;
Winson Chungecd9b302014-04-16 17:07:18 -070030import android.view.Gravity;
31import android.view.LayoutInflater;
Winson Chung303e1ff2014-03-07 15:06:19 -080032import android.view.View;
33import android.widget.FrameLayout;
Winson Chungecd9b302014-04-16 17:07:18 -070034import android.widget.TextView;
Winson Chung303e1ff2014-03-07 15:06:19 -080035import com.android.systemui.recents.Console;
36import com.android.systemui.recents.Constants;
37import com.android.systemui.recents.RecentsConfiguration;
Winson Chunga10370f2014-04-02 12:25:04 -070038import com.android.systemui.recents.RecentsTaskLoader;
Winson Chung303e1ff2014-03-07 15:06:19 -080039import com.android.systemui.recents.model.SpaceNode;
40import com.android.systemui.recents.model.Task;
41import com.android.systemui.recents.model.TaskStack;
Winson Chungecd9b302014-04-16 17:07:18 -070042import com.android.systemui.R;
Winson Chung303e1ff2014-03-07 15:06:19 -080043
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 */
Winson Chung04dfe0d2014-03-14 14:06:29 -070051public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
Winson Chung47c4c692014-03-17 10:17:11 -070052
53 /** The RecentsView callbacks */
54 public interface RecentsViewCallbacks {
55 public void onTaskLaunching();
56 }
57
Winson Chung303e1ff2014-03-07 15:06:19 -080058 // The space partitioning root of this container
59 SpaceNode mBSP;
Winson Chungecd9b302014-04-16 17:07:18 -070060 // Search bar view
61 View mSearchBar;
Winson Chung47c4c692014-03-17 10:17:11 -070062 // Recents view callbacks
63 RecentsViewCallbacks mCb;
Winson Chung303e1ff2014-03-07 15:06:19 -080064
Winson Chungecd9b302014-04-16 17:07:18 -070065 LayoutInflater mInflater;
66
Winson Chung303e1ff2014-03-07 15:06:19 -080067 public RecentsView(Context context) {
68 super(context);
Winson Chungecd9b302014-04-16 17:07:18 -070069 mInflater = LayoutInflater.from(context);
Winson Chung303e1ff2014-03-07 15:06:19 -080070 setWillNotDraw(false);
71 }
72
Winson Chung47c4c692014-03-17 10:17:11 -070073 /** Sets the callbacks */
74 public void setCallbacks(RecentsViewCallbacks cb) {
75 mCb = cb;
76 }
77
Winson Chung303e1ff2014-03-07 15:06:19 -080078 /** Set/get the bsp root node */
79 public void setBSP(SpaceNode n) {
80 mBSP = n;
81
Winson Chung04dfe0d2014-03-14 14:06:29 -070082 // Create and add all the stacks for this partition of space.
Winson Chungecd9b302014-04-16 17:07:18 -070083 boolean hasTasks = false;
Winson Chung303e1ff2014-03-07 15:06:19 -080084 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);
Winson Chungecd9b302014-04-16 17:07:18 -070090 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 }
Winson Chung303e1ff2014-03-07 15:06:19 -080099 }
100 }
101
102 /** Launches the first task from the first stack if possible */
103 public boolean launchFirstTask() {
Winson Chung47c4c692014-03-17 10:17:11 -0700104 // Get the first stack view
Winson Chung303e1ff2014-03-07 15:06:19 -0800105 int childCount = getChildCount();
106 for (int i = 0; i < childCount; i++) {
Winson Chungecd9b302014-04-16 17:07:18 -0700107 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();
Winson Chung47c4c692014-03-17 10:17:11 -0700112
Winson Chungecd9b302014-04-16 17:07:18 -0700113 // Get the first task in the stack
114 if (!tasks.isEmpty()) {
115 Task task = tasks.get(tasks.size() - 1);
116 TaskView tv = null;
Winson Chung47c4c692014-03-17 10:17:11 -0700117
Winson Chungecd9b302014-04-16 17:07:18 -0700118 // 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 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800124 }
Winson Chungecd9b302014-04-16 17:07:18 -0700125 onTaskLaunched(stackView, tv, stack, task);
126 return true;
Winson Chung303e1ff2014-03-07 15:06:19 -0800127 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800128 }
129 }
130 return false;
131 }
132
Winson Chungecd9b302014-04-16 17:07:18 -0700133 /** 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
Winson Chung303e1ff2014-03-07 15:06:19 -0800146 @Override
147 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
148 int width = MeasureSpec.getSize(widthMeasureSpec);
Winson Chung67369052014-04-07 17:35:48 -0700149 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chung303e1ff2014-03-07 15:06:19 -0800150 int height = MeasureSpec.getSize(heightMeasureSpec);
151 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chungbd912972014-03-18 14:36:35 -0700152
Winson Chung71243902014-03-14 17:52:47 -0700153 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|measure]",
154 "width: " + width + " height: " + height, Console.AnsiGreen);
Winson Chungbd912972014-03-18 14:36:35 -0700155 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
156 Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onMeasure");
Winson Chung303e1ff2014-03-07 15:06:19 -0800157
Winson Chungecd9b302014-04-16 17:07:18 -0700158 // 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();
Winson Chung303e1ff2014-03-07 15:06:19 -0800161 RecentsConfiguration config = RecentsConfiguration.getInstance();
Winson Chungecd9b302014-04-16 17:07:18 -0700162 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.
Winson Chung67369052014-04-07 17:35:48 -0700170 int childWidth = width - config.systemInsets.right;
Winson Chungecd9b302014-04-16 17:07:18 -0700171 int childHeight = height - config.systemInsets.top - searchBarSpaceBounds.height();
Winson Chung303e1ff2014-03-07 15:06:19 -0800172
173 // Measure each child
174 int childCount = getChildCount();
175 for (int i = 0; i < childCount; i++) {
Winson Chungecd9b302014-04-16 17:07:18 -0700176 View child = getChildAt(i);
177 if (child instanceof TaskStackView && child.getVisibility() != GONE) {
Winson Chung67369052014-04-07 17:35:48 -0700178 child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode),
Winson Chung303e1ff2014-03-07 15:06:19 -0800179 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) {
Winson Chung71243902014-03-14 17:52:47 -0700188 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|layout]",
189 new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
Winson Chungbd912972014-03-18 14:36:35 -0700190 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
191 Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onLayout");
192
Winson Chungecd9b302014-04-16 17:07:18 -0700193 // 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();
Winson Chung303e1ff2014-03-07 15:06:19 -0800196 RecentsConfiguration config = RecentsConfiguration.getInstance();
Winson Chungecd9b302014-04-16 17:07:18 -0700197 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();
Winson Chung303e1ff2014-03-07 15:06:19 -0800208
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++) {
Winson Chungecd9b302014-04-16 17:07:18 -0700213 View child = getChildAt(i);
214 if (child instanceof TaskStackView && child.getVisibility() != GONE) {
215 int width = child.getMeasuredWidth();
216 int height = child.getMeasuredHeight();
Winson Chung303e1ff2014-03-07 15:06:19 -0800217 child.layout(left, top, left + width, top + height);
218 }
219 }
220 }
221
222 @Override
223 protected void dispatchDraw(Canvas canvas) {
Winson Chung47c4c692014-03-17 10:17:11 -0700224 Console.log(Constants.DebugFlags.UI.Draw, "[RecentsView|dispatchDraw]", "",
225 Console.AnsiPurple);
Winson Chung303e1ff2014-03-07 15:06:19 -0800226 super.dispatchDraw(canvas);
227 }
228
229 @Override
230 protected boolean fitSystemWindows(Rect insets) {
Winson Chung47c4c692014-03-17 10:17:11 -0700231 Console.log(Constants.DebugFlags.UI.MeasureAndLayout,
232 "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
Winson Chung303e1ff2014-03-07 15:06:19 -0800233
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
Winson Chung9f9679d2014-04-11 16:49:09 -0700242 /** 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++) {
Winson Chungecd9b302014-04-16 17:07:18 -0700248 View child = getChildAt(i);
249 if (child instanceof TaskStackView) {
250 TaskStackView stackView = (TaskStackView) child;
251 if (stackView.closeOpenInfoPanes()) {
252 return true;
253 }
Winson Chung9f9679d2014-04-11 16:49:09 -0700254 }
255 }
256 }
257 return false;
258 }
259
Winson Chung303e1ff2014-03-07 15:06:19 -0800260 /** 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
Winson Chung47c4c692014-03-17 10:17:11 -0700277 /**** TaskStackView.TaskStackCallbacks Implementation ****/
Winson Chung303e1ff2014-03-07 15:06:19 -0800278
279 @Override
280 public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
281 final TaskStack stack, final Task task) {
Winson Chung47c4c692014-03-17 10:17:11 -0700282 // Notify any callbacks of the launching of a new task
283 if (mCb != null) {
284 mCb.onTaskLaunching();
285 }
286
Winson Chung9f9679d2014-04-11 16:49:09 -0700287 // Close any open info panes
288 closeOpenInfoPanes();
289
Winson Chung303e1ff2014-03-07 15:06:19 -0800290 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;
Winson Chungc6a16232014-04-01 14:04:48 -0700297 int stackScroll = stackView.getStackScroll();
Winson Chung303e1ff2014-03-07 15:06:19 -0800298 if (tv == null) {
Winson Chungc620baf2014-03-19 17:15:29 -0700299 // 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();
Winson Chung303e1ff2014-03-07 15:06:19 -0800303 sourceView = stackView;
Winson Chungc6a16232014-04-01 14:04:48 -0700304 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
Winson Chung303e1ff2014-03-07 15:06:19 -0800305 offsetX = transform.rect.left;
Winson Chungc620baf2014-03-19 17:15:29 -0700306 offsetY = Math.min(transform.rect.top, config.displayRect.height());
Winson Chung303e1ff2014-03-07 15:06:19 -0800307 } else {
Winson Chungc6a16232014-04-01 14:04:48 -0700308 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
Winson Chung303e1ff2014-03-07 15:06:19 -0800309 }
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
Winson Chung4be04452014-03-24 17:22:30 -0700329
330 if (task.isActive) {
331 // Bring an active task to the foreground
Winson Chunga10370f2014-04-02 12:25:04 -0700332 RecentsTaskLoader.getInstance().getSystemServicesProxy()
333 .moveTaskToFront(task.key.id, opts);
Winson Chung4be04452014-03-24 17:22:30 -0700334 } else {
335 // Launch the activity with the desired animation
Winson Chungc6a16232014-04-01 14:04:48 -0700336 Intent i = new Intent(task.key.baseIntent);
Winson Chung4be04452014-03-24 17:22:30 -0700337 i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
338 | Intent.FLAG_ACTIVITY_TASK_ON_HOME
339 | Intent.FLAG_ACTIVITY_NEW_TASK);
340 try {
Winson Chung67369052014-04-07 17:35:48 -0700341 UserHandle taskUser = new UserHandle(task.userId);
Winson Chung4be04452014-03-24 17:22:30 -0700342 if (opts != null) {
Winson Chung67369052014-04-07 17:35:48 -0700343 getContext().startActivityAsUser(i, opts.toBundle(), taskUser);
Winson Chung4be04452014-03-24 17:22:30 -0700344 } else {
Winson Chung67369052014-04-07 17:35:48 -0700345 getContext().startActivityAsUser(i, taskUser);
Winson Chung4be04452014-03-24 17:22:30 -0700346 }
347 } catch (ActivityNotFoundException anfe) {
348 Console.logError(getContext(), "Could not start Activity");
349 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800350 }
Winson Chungbd912972014-03-18 14:36:35 -0700351
352 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
353 Constants.DebugFlags.App.TimeRecentsLaunchKey, "startActivity");
Winson Chung303e1ff2014-03-07 15:06:19 -0800354 }
355 };
356
Winson Chungbd912972014-03-18 14:36:35 -0700357 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
358 Constants.DebugFlags.App.TimeRecentsLaunchKey, "onTaskLaunched");
359
Winson Chung303e1ff2014-03-07 15:06:19 -0800360 // Launch the app right away if there is no task view, otherwise, animate the icon out first
Winson Chungb44c24f2014-04-09 15:17:43 -0700361 if (tv == null || !Constants.Values.TaskView.AnimateFrontTaskBarOnLeavingRecents) {
Winson Chung47c4c692014-03-17 10:17:11 -0700362 post(launchRunnable);
Winson Chung303e1ff2014-03-07 15:06:19 -0800363 } else {
364 tv.animateOnLeavingRecents(launchRunnable);
365 }
366 }
Winson Chung9f9679d2014-04-11 16:49:09 -0700367
368 @Override
369 public void onTaskAppInfoLaunched(Task t) {
370 // Create a new task stack with the application info details activity
371 Intent baseIntent = t.key.baseIntent;
372 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
373 Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
374 intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
375 TaskStackBuilder.create(getContext())
376 .addNextIntentWithParentStack(intent).startActivities();
377 }
Winson Chungecd9b302014-04-16 17:07:18 -0700378
379 public void onSearchTriggered() {
380 // Get the search bar source bounds
381 Rect searchBarSpaceBounds = new Rect();
382 Rect searchBarBounds = new Rect();
383 RecentsConfiguration config = RecentsConfiguration.getInstance();
384 config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
385 searchBarSpaceBounds, searchBarBounds);
386
387 // Get the search intent and start it
388 Intent searchIntent = RecentsTaskLoader.getInstance().getSystemServicesProxy()
389 .getGlobalSearchIntent(searchBarBounds);
390 if (searchIntent != null) {
391 try {
392 getContext().startActivity(searchIntent);
393 } catch (ActivityNotFoundException anfe) {
394 Console.logError(getContext(), "Could not start Search activity");
395 }
396 }
397 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800398}