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