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