RecentsView.java revision 6ac8bd6198f67b64aea2258bdb5f8ed371b5bec1
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.Context; 22import android.content.Intent; 23import android.graphics.Bitmap; 24import android.graphics.Canvas; 25import android.graphics.Rect; 26import android.net.Uri; 27import android.os.UserHandle; 28import android.provider.Settings; 29import android.util.AttributeSet; 30import android.view.LayoutInflater; 31import android.view.View; 32import android.view.WindowInsets; 33import android.widget.FrameLayout; 34import com.android.systemui.recents.Constants; 35import com.android.systemui.recents.RecentsConfiguration; 36import com.android.systemui.recents.misc.SystemServicesProxy; 37import com.android.systemui.recents.model.RecentsPackageMonitor; 38import com.android.systemui.recents.model.RecentsTaskLoader; 39import com.android.systemui.recents.model.Task; 40import com.android.systemui.recents.model.TaskStack; 41 42import java.util.ArrayList; 43import java.util.List; 44 45/** 46 * This view is the the top level layout that contains TaskStacks (which are laid out according 47 * to their SpaceNode bounds. 48 */ 49public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks, 50 RecentsPackageMonitor.PackageCallbacks { 51 52 /** The RecentsView callbacks */ 53 public interface RecentsViewCallbacks { 54 public void onTaskViewClicked(); 55 public void onTaskLaunchFailed(); 56 public void onAllTaskViewsDismissed(); 57 public void onExitToHomeAnimationTriggered(); 58 public void onScreenPinningRequest(); 59 } 60 61 RecentsConfiguration mConfig; 62 LayoutInflater mInflater; 63 DebugOverlayView mDebugOverlay; 64 65 ArrayList<TaskStack> mStacks; 66 View mSearchBar; 67 RecentsViewCallbacks mCb; 68 69 public RecentsView(Context context) { 70 super(context); 71 } 72 73 public RecentsView(Context context, AttributeSet attrs) { 74 this(context, attrs, 0); 75 } 76 77 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 78 this(context, attrs, defStyleAttr, 0); 79 } 80 81 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 82 super(context, attrs, defStyleAttr, defStyleRes); 83 mConfig = RecentsConfiguration.getInstance(); 84 mInflater = LayoutInflater.from(context); 85 } 86 87 /** Sets the callbacks */ 88 public void setCallbacks(RecentsViewCallbacks cb) { 89 mCb = cb; 90 } 91 92 /** Sets the debug overlay */ 93 public void setDebugOverlay(DebugOverlayView overlay) { 94 mDebugOverlay = overlay; 95 } 96 97 /** Set/get the bsp root node */ 98 public void setTaskStacks(ArrayList<TaskStack> stacks) { 99 int numStacks = stacks.size(); 100 101 // Make a list of the stack view children only 102 ArrayList<TaskStackView> stackViews = new ArrayList<TaskStackView>(); 103 int childCount = getChildCount(); 104 for (int i = 0; i < childCount; i++) { 105 View child = getChildAt(i); 106 if (child != mSearchBar) { 107 stackViews.add((TaskStackView) child); 108 } 109 } 110 111 // Remove all/extra stack views 112 int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout 113 if (mConfig.launchedReuseTaskStackViews) { 114 numTaskStacksToKeep = Math.min(childCount, numStacks); 115 } 116 for (int i = stackViews.size() - 1; i >= numTaskStacksToKeep; i--) { 117 removeView(stackViews.get(i)); 118 stackViews.remove(i); 119 } 120 121 // Update the stack views that we are keeping 122 for (int i = 0; i < numTaskStacksToKeep; i++) { 123 TaskStackView tsv = stackViews.get(i); 124 // If onRecentsHidden is not triggered, we need to the stack view again here 125 tsv.reset(); 126 tsv.setStack(stacks.get(i)); 127 } 128 129 // Add remaining/recreate stack views 130 mStacks = stacks; 131 for (int i = stackViews.size(); i < numStacks; i++) { 132 TaskStack stack = stacks.get(i); 133 TaskStackView stackView = new TaskStackView(getContext(), stack); 134 stackView.setCallbacks(this); 135 addView(stackView); 136 } 137 138 // Enable debug mode drawing on all the stacks if necessary 139 if (mConfig.debugModeEnabled) { 140 for (int i = childCount - 1; i >= 0; i--) { 141 View v = getChildAt(i); 142 if (v != mSearchBar) { 143 TaskStackView stackView = (TaskStackView) v; 144 stackView.setDebugOverlay(mDebugOverlay); 145 } 146 } 147 } 148 149 // Trigger a new layout 150 requestLayout(); 151 } 152 153 /** Launches the focused task from the first stack if possible */ 154 public boolean launchFocusedTask() { 155 // Get the first stack view 156 int childCount = getChildCount(); 157 for (int i = 0; i < childCount; i++) { 158 View child = getChildAt(i); 159 if (child != mSearchBar) { 160 TaskStackView stackView = (TaskStackView) child; 161 TaskStack stack = stackView.mStack; 162 // Iterate the stack views and try and find the focused task 163 List<TaskView> taskViews = stackView.getTaskViews(); 164 int taskViewCount = taskViews.size(); 165 for (int j = 0; j < taskViewCount; j++) { 166 TaskView tv = taskViews.get(j); 167 Task task = tv.getTask(); 168 if (tv.isFocusedTask()) { 169 onTaskViewClicked(stackView, tv, stack, task, false); 170 return true; 171 } 172 } 173 } 174 } 175 return false; 176 } 177 178 /** Launches the task that Recents was launched from, if possible */ 179 public boolean launchPreviousTask() { 180 // Get the first stack view 181 int childCount = getChildCount(); 182 for (int i = 0; i < childCount; i++) { 183 View child = getChildAt(i); 184 if (child != mSearchBar) { 185 TaskStackView stackView = (TaskStackView) child; 186 TaskStack stack = stackView.mStack; 187 ArrayList<Task> tasks = stack.getTasks(); 188 189 // Find the launch task in the stack 190 if (!tasks.isEmpty()) { 191 int taskCount = tasks.size(); 192 for (int j = 0; j < taskCount; j++) { 193 if (tasks.get(j).isLaunchTarget) { 194 Task task = tasks.get(j); 195 TaskView tv = stackView.getChildViewForTask(task); 196 onTaskViewClicked(stackView, tv, stack, task, false); 197 return true; 198 } 199 } 200 } 201 } 202 } 203 return false; 204 } 205 206 /** Requests all task stacks to start their enter-recents animation */ 207 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 208 // We have to increment/decrement the post animation trigger in case there are no children 209 // to ensure that it runs 210 ctx.postAnimationTrigger.increment(); 211 212 int childCount = getChildCount(); 213 for (int i = 0; i < childCount; i++) { 214 View child = getChildAt(i); 215 if (child != mSearchBar) { 216 TaskStackView stackView = (TaskStackView) child; 217 stackView.startEnterRecentsAnimation(ctx); 218 } 219 } 220 ctx.postAnimationTrigger.decrement(); 221 } 222 223 /** Requests all task stacks to start their exit-recents animation */ 224 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 225 // We have to increment/decrement the post animation trigger in case there are no children 226 // to ensure that it runs 227 ctx.postAnimationTrigger.increment(); 228 int childCount = getChildCount(); 229 for (int i = 0; i < childCount; i++) { 230 View child = getChildAt(i); 231 if (child != mSearchBar) { 232 TaskStackView stackView = (TaskStackView) child; 233 stackView.startExitToHomeAnimation(ctx); 234 } 235 } 236 ctx.postAnimationTrigger.decrement(); 237 238 // Notify of the exit animation 239 mCb.onExitToHomeAnimationTriggered(); 240 } 241 242 /** Adds the search bar */ 243 public void setSearchBar(View searchBar) { 244 // Create the search bar (and hide it if we have no recent tasks) 245 if (Constants.DebugFlags.App.EnableSearchLayout) { 246 // Remove the previous search bar if one exists 247 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 248 removeView(mSearchBar); 249 } 250 // Add the new search bar 251 if (searchBar != null) { 252 mSearchBar = searchBar; 253 addView(mSearchBar); 254 } 255 } 256 } 257 258 /** Returns whether there is currently a search bar */ 259 public boolean hasSearchBar() { 260 return mSearchBar != null; 261 } 262 263 /** Sets the visibility of the search bar */ 264 public void setSearchBarVisibility(int visibility) { 265 if (mSearchBar != null) { 266 mSearchBar.setVisibility(visibility); 267 // Always bring the search bar to the top 268 mSearchBar.bringToFront(); 269 } 270 } 271 272 /** 273 * This is called with the full size of the window since we are handling our own insets. 274 */ 275 @Override 276 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 277 int width = MeasureSpec.getSize(widthMeasureSpec); 278 int height = MeasureSpec.getSize(heightMeasureSpec); 279 280 // Get the search bar bounds and measure the search bar layout 281 if (mSearchBar != null) { 282 Rect searchBarSpaceBounds = new Rect(); 283 mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds); 284 mSearchBar.measure( 285 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 286 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 287 } 288 289 Rect taskStackBounds = new Rect(); 290 mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top, 291 mConfig.systemInsets.right, taskStackBounds); 292 293 // Measure each TaskStackView with the full width and height of the window since the 294 // transition view is a child of that stack view 295 int childCount = getChildCount(); 296 for (int i = 0; i < childCount; i++) { 297 View child = getChildAt(i); 298 if (child != mSearchBar && child.getVisibility() != GONE) { 299 TaskStackView tsv = (TaskStackView) child; 300 // Set the insets to be the top/left inset + search bounds 301 tsv.setStackInsetRect(taskStackBounds); 302 tsv.measure(widthMeasureSpec, heightMeasureSpec); 303 } 304 } 305 306 setMeasuredDimension(width, height); 307 } 308 309 /** 310 * This is called with the full size of the window since we are handling our own insets. 311 */ 312 @Override 313 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 314 // Get the search bar bounds so that we lay it out 315 if (mSearchBar != null) { 316 Rect searchBarSpaceBounds = new Rect(); 317 mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), 318 mConfig.systemInsets.top, searchBarSpaceBounds); 319 mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top, 320 searchBarSpaceBounds.right, searchBarSpaceBounds.bottom); 321 } 322 323 // Layout each TaskStackView with the full width and height of the window since the 324 // transition view is a child of that stack view 325 int childCount = getChildCount(); 326 for (int i = 0; i < childCount; i++) { 327 View child = getChildAt(i); 328 if (child != mSearchBar && child.getVisibility() != GONE) { 329 child.layout(left, top, left + child.getMeasuredWidth(), 330 top + child.getMeasuredHeight()); 331 } 332 } 333 } 334 335 @Override 336 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 337 // Update the configuration with the latest system insets and trigger a relayout 338 mConfig.updateSystemInsets(insets.getSystemWindowInsets()); 339 requestLayout(); 340 return insets.consumeSystemWindowInsets(); 341 } 342 343 /** Notifies each task view of the user interaction. */ 344 public void onUserInteraction() { 345 // Get the first stack view 346 int childCount = getChildCount(); 347 for (int i = 0; i < childCount; i++) { 348 View child = getChildAt(i); 349 if (child != mSearchBar) { 350 TaskStackView stackView = (TaskStackView) child; 351 stackView.onUserInteraction(); 352 } 353 } 354 } 355 356 /** Focuses the next task in the first stack view */ 357 public void focusNextTask(boolean forward) { 358 // Get the first stack view 359 int childCount = getChildCount(); 360 for (int i = 0; i < childCount; i++) { 361 View child = getChildAt(i); 362 if (child != mSearchBar) { 363 TaskStackView stackView = (TaskStackView) child; 364 stackView.focusNextTask(forward, true); 365 break; 366 } 367 } 368 } 369 370 /** Dismisses the focused task. */ 371 public void dismissFocusedTask() { 372 // Get the first stack view 373 int childCount = getChildCount(); 374 for (int i = 0; i < childCount; i++) { 375 View child = getChildAt(i); 376 if (child != mSearchBar) { 377 TaskStackView stackView = (TaskStackView) child; 378 stackView.dismissFocusedTask(); 379 break; 380 } 381 } 382 } 383 384 /** Unfilters any filtered stacks */ 385 public boolean unfilterFilteredStacks() { 386 if (mStacks != null) { 387 // Check if there are any filtered stacks and unfilter them before we back out of Recents 388 boolean stacksUnfiltered = false; 389 int numStacks = mStacks.size(); 390 for (int i = 0; i < numStacks; i++) { 391 TaskStack stack = mStacks.get(i); 392 if (stack.hasFilteredTasks()) { 393 stack.unfilterTasks(); 394 stacksUnfiltered = true; 395 } 396 } 397 return stacksUnfiltered; 398 } 399 return false; 400 } 401 402 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 403 404 @Override 405 public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv, 406 final TaskStack stack, final Task task, final boolean lockToTask) { 407 // Notify any callbacks of the launching of a new task 408 if (mCb != null) { 409 mCb.onTaskViewClicked(); 410 } 411 412 // Upfront the processing of the thumbnail 413 TaskViewTransform transform = new TaskViewTransform(); 414 View sourceView; 415 int offsetX = 0; 416 int offsetY = 0; 417 float stackScroll = stackView.getScroller().getStackScroll(); 418 if (tv == null) { 419 // If there is no actual task view, then use the stack view as the source view 420 // and then offset to the expected transform rect, but bound this to just 421 // outside the display rect (to ensure we don't animate from too far away) 422 sourceView = stackView; 423 transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null); 424 offsetX = transform.rect.left; 425 offsetY = mConfig.displayRect.height(); 426 } else { 427 sourceView = tv.mThumbnailView; 428 transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null); 429 } 430 431 // Compute the thumbnail to scale up from 432 final SystemServicesProxy ssp = 433 RecentsTaskLoader.getInstance().getSystemServicesProxy(); 434 ActivityOptions opts = null; 435 if (task.thumbnail != null && task.thumbnail.getWidth() > 0 && 436 task.thumbnail.getHeight() > 0) { 437 Bitmap b; 438 if (tv != null) { 439 // Disable any focused state before we draw the header 440 if (tv.isFocusedTask()) { 441 tv.unsetFocusedTask(); 442 } 443 444 float scale = tv.getScaleX(); 445 int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale); 446 int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale); 447 b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight, 448 Bitmap.Config.ARGB_8888); 449 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { 450 b.eraseColor(0xFFff0000); 451 } else { 452 Canvas c = new Canvas(b); 453 c.scale(tv.getScaleX(), tv.getScaleY()); 454 tv.mHeaderView.draw(c); 455 c.setBitmap(null); 456 } 457 } else { 458 // Notify the system to skip the thumbnail layer by using an ALPHA_8 bitmap 459 b = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); 460 } 461 ActivityOptions.OnAnimationStartedListener animStartedListener = null; 462 if (lockToTask) { 463 animStartedListener = new ActivityOptions.OnAnimationStartedListener() { 464 boolean mTriggered = false; 465 @Override 466 public void onAnimationStarted() { 467 if (!mTriggered) { 468 postDelayed(new Runnable() { 469 @Override 470 public void run() { 471 mCb.onScreenPinningRequest(); 472 } 473 }, 350); 474 mTriggered = true; 475 } 476 } 477 }; 478 } 479 opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, 480 b, offsetX, offsetY, transform.rect.width(), transform.rect.height(), 481 sourceView.getHandler(), animStartedListener); 482 } 483 484 final ActivityOptions launchOpts = opts; 485 final Runnable launchRunnable = new Runnable() { 486 @Override 487 public void run() { 488 if (task.isActive) { 489 // Bring an active task to the foreground 490 ssp.moveTaskToFront(task.key.id, launchOpts); 491 } else { 492 if (ssp.startActivityFromRecents(getContext(), task.key.id, 493 task.activityLabel, launchOpts)) { 494 if (launchOpts == null && lockToTask) { 495 mCb.onScreenPinningRequest(); 496 } 497 } else { 498 // Dismiss the task and return the user to home if we fail to 499 // launch the task 500 onTaskViewDismissed(task); 501 if (mCb != null) { 502 mCb.onTaskLaunchFailed(); 503 } 504 } 505 } 506 } 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 launchRunnable.run(); 512 } else { 513 if (!task.group.isFrontMostTask(task)) { 514 // For affiliated tasks that are behind other tasks, we must animate the front cards 515 // out of view before starting the task transition 516 stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask); 517 } else { 518 // Otherwise, we can start the task transition immediately 519 stackView.startLaunchTaskAnimation(tv, null, lockToTask); 520 launchRunnable.run(); 521 } 522 } 523 } 524 525 @Override 526 public void onTaskViewAppInfoClicked(Task t) { 527 // Create a new task stack with the application info details activity 528 Intent baseIntent = t.key.baseIntent; 529 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 530 Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null)); 531 intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); 532 TaskStackBuilder.create(getContext()) 533 .addNextIntentWithParentStack(intent).startActivities(null, 534 new UserHandle(t.key.userId)); 535 } 536 537 @Override 538 public void onTaskViewDismissed(Task t) { 539 // Remove any stored data from the loader. We currently don't bother notifying the views 540 // that the data has been unloaded because at the point we call onTaskViewDismissed(), the views 541 // either don't need to be updated, or have already been removed. 542 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 543 loader.deleteTaskData(t, false); 544 545 // Remove the old task from activity manager 546 loader.getSystemServicesProxy().removeTask(t.key.id); 547 } 548 549 @Override 550 public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) { 551 if (removedTasks != null) { 552 int taskCount = removedTasks.size(); 553 for (int i = 0; i < taskCount; i++) { 554 onTaskViewDismissed(removedTasks.get(i)); 555 } 556 } 557 558 mCb.onAllTaskViewsDismissed(); 559 } 560 561 /** Final callback after Recents is finally hidden. */ 562 public void onRecentsHidden() { 563 // Notify each task stack view 564 int childCount = getChildCount(); 565 for (int i = 0; i < childCount; i++) { 566 View child = getChildAt(i); 567 if (child != mSearchBar) { 568 TaskStackView stackView = (TaskStackView) child; 569 stackView.onRecentsHidden(); 570 } 571 } 572 } 573 574 @Override 575 public void onTaskStackFilterTriggered() { 576 // Hide the search bar 577 if (mSearchBar != null) { 578 mSearchBar.animate() 579 .alpha(0f) 580 .setStartDelay(0) 581 .setInterpolator(mConfig.fastOutSlowInInterpolator) 582 .setDuration(mConfig.filteringCurrentViewsAnimDuration) 583 .withLayer() 584 .start(); 585 } 586 } 587 588 @Override 589 public void onTaskStackUnfilterTriggered() { 590 // Show the search bar 591 if (mSearchBar != null) { 592 mSearchBar.animate() 593 .alpha(1f) 594 .setStartDelay(0) 595 .setInterpolator(mConfig.fastOutSlowInInterpolator) 596 .setDuration(mConfig.filteringNewViewsAnimDuration) 597 .withLayer() 598 .start(); 599 } 600 } 601 602 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 603 604 @Override 605 public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { 606 // Propagate this event down to each task stack view 607 int childCount = getChildCount(); 608 for (int i = 0; i < childCount; i++) { 609 View child = getChildAt(i); 610 if (child != mSearchBar) { 611 TaskStackView stackView = (TaskStackView) child; 612 stackView.onPackagesChanged(monitor, packageName, userId); 613 } 614 } 615 } 616} 617