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