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