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