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