RecentsView.java revision c6011deffc80eaee6dfb1c7975e050bc71e5ea96
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.Paint; 28import android.graphics.Rect; 29import android.net.Uri; 30import android.os.UserHandle; 31import android.provider.Settings; 32import android.util.AttributeSet; 33import android.view.LayoutInflater; 34import android.view.View; 35import android.view.WindowInsets; 36import android.widget.FrameLayout; 37import com.android.systemui.recents.Console; 38import com.android.systemui.recents.Constants; 39import com.android.systemui.recents.RecentsConfiguration; 40import com.android.systemui.recents.model.RecentsPackageMonitor; 41import com.android.systemui.recents.model.RecentsTaskLoader; 42import com.android.systemui.recents.model.SpaceNode; 43import com.android.systemui.recents.model.Task; 44import com.android.systemui.recents.model.TaskStack; 45 46import java.util.ArrayList; 47import java.util.Set; 48 49/** 50 * This view is the the top level layout that contains TaskStacks (which are laid out according 51 * to their SpaceNode bounds. 52 */ 53public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks, 54 RecentsPackageMonitor.PackageCallbacks { 55 56 /** The RecentsView callbacks */ 57 public interface RecentsViewCallbacks { 58 public void onTaskLaunching(); 59 public void onLastTaskRemoved(); 60 public void onExitToHomeAnimationTriggered(); 61 } 62 63 RecentsConfiguration mConfig; 64 LayoutInflater mInflater; 65 Paint mDebugModePaint; 66 67 // The space partitioning root of this container 68 SpaceNode mBSP; 69 // Whether there are any tasks 70 boolean mHasTasks; 71 // Search bar view 72 View mSearchBar; 73 // Recents view callbacks 74 RecentsViewCallbacks mCb; 75 76 public RecentsView(Context context) { 77 super(context); 78 } 79 80 public RecentsView(Context context, AttributeSet attrs) { 81 this(context, attrs, 0); 82 } 83 84 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 85 this(context, attrs, defStyleAttr, 0); 86 } 87 88 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 89 super(context, attrs, defStyleAttr, defStyleRes); 90 mConfig = RecentsConfiguration.getInstance(); 91 mInflater = LayoutInflater.from(context); 92 } 93 94 /** Sets the callbacks */ 95 public void setCallbacks(RecentsViewCallbacks cb) { 96 mCb = cb; 97 } 98 99 /** Set/get the bsp root node */ 100 public void setBSP(SpaceNode n) { 101 mBSP = n; 102 103 // Create and add all the stacks for this partition of space. 104 mHasTasks = false; 105 removeAllViews(); 106 ArrayList<TaskStack> stacks = mBSP.getStacks(); 107 for (TaskStack stack : stacks) { 108 TaskStackView stackView = new TaskStackView(getContext(), stack); 109 stackView.setCallbacks(this); 110 addView(stackView); 111 mHasTasks |= (stack.getTaskCount() > 0); 112 } 113 114 // Enable debug mode drawing 115 if (mConfig.debugModeEnabled) { 116 mDebugModePaint = new Paint(); 117 mDebugModePaint.setColor(0xFFff0000); 118 mDebugModePaint.setStyle(Paint.Style.STROKE); 119 mDebugModePaint.setStrokeWidth(5f); 120 setWillNotDraw(false); 121 } 122 } 123 124 /** Launches the focused task from the first stack if possible */ 125 public boolean launchFocusedTask() { 126 // Get the first stack view 127 int childCount = getChildCount(); 128 for (int i = 0; i < childCount; i++) { 129 View child = getChildAt(i); 130 if (child instanceof TaskStackView) { 131 TaskStackView stackView = (TaskStackView) child; 132 TaskStack stack = stackView.mStack; 133 // Iterate the stack views and try and find the focused task 134 int taskCount = stackView.getChildCount(); 135 for (int j = 0; j < taskCount; j++) { 136 TaskView tv = (TaskView) stackView.getChildAt(j); 137 Task task = tv.getTask(); 138 if (tv.isFocusedTask()) { 139 if (Console.Enabled) { 140 Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]", 141 "Found focused Task"); 142 } 143 onTaskLaunched(stackView, tv, stack, task); 144 return true; 145 } 146 } 147 } 148 } 149 if (Console.Enabled) { 150 Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]", 151 "No Tasks focused"); 152 } 153 return false; 154 } 155 156 /** Launches the first task from the first stack if possible */ 157 public boolean launchFirstTask() { 158 // Get the first stack view 159 int childCount = getChildCount(); 160 for (int i = 0; i < childCount; i++) { 161 View child = getChildAt(i); 162 if (child instanceof TaskStackView) { 163 TaskStackView stackView = (TaskStackView) child; 164 TaskStack stack = stackView.mStack; 165 ArrayList<Task> tasks = stack.getTasks(); 166 167 // Get the first task in the stack 168 if (!tasks.isEmpty()) { 169 Task task = tasks.get(tasks.size() - 1); 170 TaskView tv = null; 171 172 // Try and use the first child task view as the source of the launch animation 173 if (stackView.getChildCount() > 0) { 174 TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1); 175 if (stv.getTask() == task) { 176 tv = stv; 177 } 178 } 179 onTaskLaunched(stackView, tv, stack, task); 180 return true; 181 } 182 } 183 } 184 return false; 185 } 186 187 /** Requests all task stacks to start their enter-recents animation */ 188 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 189 // Handle the case when there are no views by incrementing and decrementing after all 190 // animations are started. 191 ctx.postAnimationTrigger.increment(); 192 193 int childCount = getChildCount(); 194 for (int i = 0; i < childCount; i++) { 195 View child = getChildAt(i); 196 if (child instanceof TaskStackView) { 197 TaskStackView stackView = (TaskStackView) child; 198 stackView.startEnterRecentsAnimation(ctx); 199 } 200 } 201 202 // Handle the case when there are no views by incrementing and decrementing after all 203 // animations are started. 204 ctx.postAnimationTrigger.decrement(); 205 } 206 207 /** Requests all task stacks to start their exit-recents animation */ 208 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 209 // Handle the case when there are no views by incrementing and decrementing after all 210 // animations are started. 211 ctx.postAnimationTrigger.increment(); 212 213 int childCount = getChildCount(); 214 for (int i = 0; i < childCount; i++) { 215 View child = getChildAt(i); 216 if (child instanceof TaskStackView) { 217 TaskStackView stackView = (TaskStackView) child; 218 stackView.startExitToHomeAnimation(ctx); 219 } 220 } 221 222 // Handle the case when there are no views by incrementing and decrementing after all 223 // animations are started. 224 ctx.postAnimationTrigger.decrement(); 225 226 // Notify of the exit animation 227 mCb.onExitToHomeAnimationTriggered(); 228 } 229 230 /** Adds the search bar */ 231 public void setSearchBar(View searchBar) { 232 // Create the search bar (and hide it if we have no recent tasks) 233 if (Constants.DebugFlags.App.EnableSearchLayout) { 234 // Remove the previous search bar if one exists 235 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 236 removeView(mSearchBar); 237 } 238 // Add the new search bar 239 if (searchBar != null) { 240 mSearchBar = searchBar; 241 mSearchBar.setVisibility(mHasTasks ? View.VISIBLE : View.GONE); 242 addView(mSearchBar); 243 244 if (Console.Enabled) { 245 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsView|setSearchBar]", 246 "" + (mSearchBar.getVisibility() == View.VISIBLE), 247 Console.AnsiBlue); 248 } 249 } 250 } 251 } 252 253 /** 254 * This is called with the full size of the window since we are handling our own insets. 255 */ 256 @Override 257 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 258 int width = MeasureSpec.getSize(widthMeasureSpec); 259 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 260 int height = MeasureSpec.getSize(heightMeasureSpec); 261 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 262 263 if (Console.Enabled) { 264 Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|measure]", 265 "width: " + width + " height: " + height, Console.AnsiGreen); 266 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 267 Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onMeasure"); 268 } 269 270 // Get the search bar bounds and measure the search bar layout 271 if (mSearchBar != null) { 272 Rect searchBarSpaceBounds = new Rect(); 273 mConfig.getSearchBarBounds(width, height - mConfig.systemInsets.top, searchBarSpaceBounds); 274 mSearchBar.measure( 275 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 276 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 277 } 278 279 // We give the full width of the space, not including the right nav bar insets in landscape, 280 // to the stack view, since we want the tasks to render under the search bar in landscape. 281 // In addition, we give it the full height, not including the top inset or search bar space, 282 // since we want the tasks to render under the navigation buttons in portrait. 283 Rect taskStackBounds = new Rect(); 284 mConfig.getTaskStackBounds(width, height, taskStackBounds); 285 int childWidth = width - mConfig.systemInsets.right; 286 int childHeight = taskStackBounds.height() - mConfig.systemInsets.top; 287 288 // Measure each TaskStackView 289 int childCount = getChildCount(); 290 for (int i = 0; i < childCount; i++) { 291 View child = getChildAt(i); 292 if (child instanceof TaskStackView && child.getVisibility() != GONE) { 293 child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode), 294 MeasureSpec.makeMeasureSpec(childHeight, heightMode)); 295 } 296 } 297 298 setMeasuredDimension(width, height); 299 } 300 301 /** 302 * This is called with the full size of the window since we are handling our own insets. 303 */ 304 @Override 305 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 306 if (Console.Enabled) { 307 Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|layout]", 308 new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen); 309 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 310 Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onLayout"); 311 } 312 313 // Get the search bar bounds so that we lay it out 314 if (mSearchBar != null) { 315 Rect searchBarSpaceBounds = new Rect(); 316 mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds); 317 mSearchBar.layout(mConfig.systemInsets.left + searchBarSpaceBounds.left, 318 mConfig.systemInsets.top + searchBarSpaceBounds.top, 319 mConfig.systemInsets.left + mSearchBar.getMeasuredWidth(), 320 mConfig.systemInsets.top + mSearchBar.getMeasuredHeight()); 321 } 322 323 // We offset the stack view by the left inset (if any), but lay it out under the search bar. 324 // In addition, we offset our stack views by the top inset and search bar height, but not 325 // the bottom insets because we want it to render under the navigation buttons. 326 Rect taskStackBounds = new Rect(); 327 mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds); 328 left += mConfig.systemInsets.left; 329 top += mConfig.systemInsets.top + taskStackBounds.top; 330 331 // Layout each child 332 // XXX: Based on the space node for that task view 333 int childCount = getChildCount(); 334 for (int i = 0; i < childCount; i++) { 335 View child = getChildAt(i); 336 if (child instanceof TaskStackView && child.getVisibility() != GONE) { 337 TaskStackView tsv = (TaskStackView) child; 338 child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight()); 339 } 340 } 341 } 342 343 @Override 344 protected void onDraw(Canvas canvas) { 345 super.onDraw(canvas); 346 // Debug mode drawing 347 if (mConfig.debugModeEnabled) { 348 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugModePaint); 349 } 350 } 351 352 @Override 353 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 354 if (Console.Enabled) { 355 Console.log(Constants.Log.UI.MeasureAndLayout, 356 "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen); 357 } 358 359 // Update the configuration with the latest system insets and trigger a relayout 360 mConfig.updateSystemInsets(insets.getSystemWindowInsets()); 361 requestLayout(); 362 363 return insets.consumeSystemWindowInsets(false, false, false, true); 364 } 365 366 /** Notifies each task view of the user interaction. */ 367 public void onUserInteraction() { 368 // Get the first stack view 369 TaskStackView stackView = null; 370 int childCount = getChildCount(); 371 for (int i = 0; i < childCount; i++) { 372 View child = getChildAt(i); 373 if (child instanceof TaskStackView) { 374 stackView = (TaskStackView) child; 375 stackView.onUserInteraction(); 376 } 377 } 378 } 379 380 /** Focuses the next task in the first stack view */ 381 public void focusNextTask(boolean forward) { 382 // Get the first stack view 383 TaskStackView stackView = null; 384 int childCount = getChildCount(); 385 for (int i = 0; i < childCount; i++) { 386 View child = getChildAt(i); 387 if (child instanceof TaskStackView) { 388 stackView = (TaskStackView) child; 389 break; 390 } 391 } 392 393 if (stackView != null) { 394 stackView.focusNextTask(forward); 395 } 396 } 397 398 /** Unfilters any filtered stacks */ 399 public boolean unfilterFilteredStacks() { 400 if (mBSP != null) { 401 // Check if there are any filtered stacks and unfilter them before we back out of Recents 402 boolean stacksUnfiltered = false; 403 ArrayList<TaskStack> stacks = mBSP.getStacks(); 404 for (TaskStack stack : stacks) { 405 if (stack.hasFilteredTasks()) { 406 stack.unfilterTasks(); 407 stacksUnfiltered = true; 408 } 409 } 410 return stacksUnfiltered; 411 } 412 return false; 413 } 414 415 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 416 417 @Override 418 public void onTaskLaunched(final TaskStackView stackView, final TaskView tv, 419 final TaskStack stack, final Task task) { 420 // Notify any callbacks of the launching of a new task 421 if (mCb != null) { 422 mCb.onTaskLaunching(); 423 } 424 425 // Upfront the processing of the thumbnail 426 TaskViewTransform transform; 427 View sourceView = tv; 428 int offsetX = 0; 429 int offsetY = 0; 430 int stackScroll = stackView.getStackScroll(); 431 if (tv == null) { 432 // If there is no actual task view, then use the stack view as the source view 433 // and then offset to the expected transform rect, but bound this to just 434 // outside the display rect (to ensure we don't animate from too far away) 435 sourceView = stackView; 436 transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task), 437 stackScroll); 438 offsetX = transform.rect.left; 439 offsetY = Math.min(transform.rect.top, mConfig.displayRect.height()); 440 } else { 441 transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task), 442 stackScroll); 443 } 444 445 // Compute the thumbnail to scale up from 446 ActivityOptions opts = null; 447 int thumbnailWidth = transform.rect.width(); 448 int thumbnailHeight = transform.rect.height(); 449 if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && 450 task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { 451 // Resize the thumbnail to the size of the view that we are animating from 452 Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, 453 Bitmap.Config.ARGB_8888); 454 Canvas c = new Canvas(b); 455 c.drawBitmap(task.thumbnail, 456 new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), 457 new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); 458 c.setBitmap(null); 459 opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, 460 b, offsetX, offsetY); 461 } 462 463 final ActivityOptions launchOpts = opts; 464 final Runnable launchRunnable = new Runnable() { 465 @Override 466 public void run() { 467 if (Console.Enabled) { 468 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 469 Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity"); 470 } 471 472 if (task.isActive) { 473 // Bring an active task to the foreground 474 RecentsTaskLoader.getInstance().getSystemServicesProxy() 475 .moveTaskToFront(task.key.id, launchOpts); 476 } else { 477 // Launch the activity anew with the desired animation 478 Intent i = new Intent(task.key.baseIntent); 479 i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 480 | Intent.FLAG_ACTIVITY_TASK_ON_HOME 481 | Intent.FLAG_ACTIVITY_NEW_TASK); 482 try { 483 UserHandle taskUser = new UserHandle(task.userId); 484 if (launchOpts != null) { 485 getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser); 486 } else { 487 getContext().startActivityAsUser(i, taskUser); 488 } 489 } catch (ActivityNotFoundException anfe) { 490 Console.logError(getContext(), "Could not start Activity"); 491 } 492 493 // And clean up the old task 494 onTaskRemoved(task); 495 } 496 497 if (Console.Enabled) { 498 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 499 Constants.Log.App.TimeRecentsLaunchKey, "startActivity"); 500 } 501 } 502 }; 503 504 if (Console.Enabled) { 505 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 506 Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched"); 507 } 508 509 // Launch the app right away if there is no task view, otherwise, animate the icon out first 510 if (tv == null) { 511 post(launchRunnable); 512 } else { 513 stackView.animateOnLaunchingTask(tv, launchRunnable); 514 } 515 } 516 517 @Override 518 public void onTaskAppInfoLaunched(Task t) { 519 // Create a new task stack with the application info details activity 520 Intent baseIntent = t.key.baseIntent; 521 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 522 Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null)); 523 intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); 524 TaskStackBuilder.create(getContext()) 525 .addNextIntentWithParentStack(intent).startActivities(); 526 } 527 528 @Override 529 public void onTaskRemoved(Task t) { 530 // Remove any stored data from the loader. We currently don't bother notifying the views 531 // that the data has been unloaded because at the point we call onTaskRemoved(), the views 532 // either don't need to be updated, or have already been removed. 533 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 534 loader.deleteTaskData(t, false); 535 536 // Remove the old task from activity manager 537 int flags = t.key.baseIntent.getFlags(); 538 boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == 539 Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 540 RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id, 541 isDocument); 542 } 543 544 @Override 545 public void onLastTaskRemoved() { 546 mCb.onLastTaskRemoved(); 547 } 548 549 @Override 550 public void onTaskStackFilterTriggered() { 551 // Hide the search bar 552 if (mSearchBar != null) { 553 mSearchBar.animate() 554 .alpha(0f) 555 .setStartDelay(0) 556 .setInterpolator(mConfig.fastOutSlowInInterpolator) 557 .setDuration(mConfig.filteringCurrentViewsAnimDuration) 558 .withLayer() 559 .start(); 560 } 561 } 562 563 @Override 564 public void onTaskStackUnfilterTriggered() { 565 // Show the search bar 566 if (mSearchBar != null) { 567 mSearchBar.animate() 568 .alpha(1f) 569 .setStartDelay(0) 570 .setInterpolator(mConfig.fastOutSlowInInterpolator) 571 .setDuration(mConfig.filteringNewViewsAnimDuration) 572 .withLayer() 573 .start(); 574 } 575 } 576 577 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 578 579 @Override 580 public void onComponentRemoved(Set<ComponentName> cns) { 581 // Propagate this event down to each task stack view 582 int childCount = getChildCount(); 583 for (int i = 0; i < childCount; i++) { 584 View child = getChildAt(i); 585 if (child instanceof TaskStackView) { 586 TaskStackView stackView = (TaskStackView) child; 587 stackView.onComponentRemoved(cns); 588 } 589 } 590 } 591} 592