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