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