RecentsView.java revision eca4ab6e99bcb2a7b31b8b4b1c3b5474297b6b25
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.content.Context; 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Rect; 24import android.graphics.drawable.Drawable; 25import android.os.Bundle; 26import android.os.IRemoteCallback; 27import android.os.RemoteException; 28import android.util.ArraySet; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.util.SparseArray; 32import android.view.AppTransitionAnimationSpec; 33import android.view.LayoutInflater; 34import android.view.MotionEvent; 35import android.view.View; 36import android.view.WindowInsets; 37import android.view.WindowManagerGlobal; 38import android.view.animation.AnimationUtils; 39import android.view.animation.Interpolator; 40import android.widget.FrameLayout; 41import com.android.internal.logging.MetricsLogger; 42import com.android.systemui.R; 43import com.android.systemui.recents.Constants; 44import com.android.systemui.recents.Recents; 45import com.android.systemui.recents.RecentsActivity; 46import com.android.systemui.recents.RecentsActivityLaunchState; 47import com.android.systemui.recents.RecentsAppWidgetHostView; 48import com.android.systemui.recents.RecentsConfiguration; 49import com.android.systemui.recents.events.EventBus; 50import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 51import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 52import com.android.systemui.recents.events.ui.DismissTaskViewEvent; 53import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; 54import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 55import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 56import com.android.systemui.recents.misc.SystemServicesProxy; 57import com.android.systemui.recents.model.Task; 58import com.android.systemui.recents.model.TaskStack; 59 60import java.util.ArrayList; 61import java.util.List; 62 63import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 64import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 65import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 66 67/** 68 * This view is the the top level layout that contains TaskStacks (which are laid out according 69 * to their SpaceNode bounds. 70 */ 71public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks { 72 73 private static final String TAG = "RecentsView"; 74 private static final boolean DEBUG = false; 75 76 private static final boolean ADD_HEADER_BITMAP = true; 77 78 /** The RecentsView callbacks */ 79 public interface RecentsViewCallbacks { 80 public void onTaskViewClicked(); 81 public void onTaskLaunchFailed(); 82 public void onAllTaskViewsDismissed(); 83 public void runAfterPause(Runnable r); 84 } 85 86 LayoutInflater mInflater; 87 88 ArrayList<TaskStack> mStacks; 89 TaskStackView mTaskStackView; 90 RecentsAppWidgetHostView mSearchBar; 91 RecentsViewCallbacks mCb; 92 93 RecentsViewTouchHandler mTouchHandler; 94 DragView mDragView; 95 TaskStack.DockState[] mVisibleDockStates = { 96 TaskStack.DockState.LEFT, 97 TaskStack.DockState.TOP, 98 TaskStack.DockState.RIGHT, 99 TaskStack.DockState.BOTTOM, 100 }; 101 102 Interpolator mFastOutSlowInInterpolator; 103 104 Rect mSystemInsets = new Rect(); 105 106 public RecentsView(Context context) { 107 super(context); 108 } 109 110 public RecentsView(Context context, AttributeSet attrs) { 111 this(context, attrs, 0); 112 } 113 114 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 115 this(context, attrs, defStyleAttr, 0); 116 } 117 118 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 119 super(context, attrs, defStyleAttr, defStyleRes); 120 setWillNotDraw(false); 121 mInflater = LayoutInflater.from(context); 122 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 123 com.android.internal.R.interpolator.fast_out_slow_in); 124 mTouchHandler = new RecentsViewTouchHandler(this); 125 } 126 127 /** Sets the callbacks */ 128 public void setCallbacks(RecentsViewCallbacks cb) { 129 mCb = cb; 130 } 131 132 /** Set/get the bsp root node */ 133 public void setTaskStack(TaskStack stack) { 134 RecentsConfiguration config = Recents.getConfiguration(); 135 if (config.getLaunchState().launchedReuseTaskStackViews) { 136 if (mTaskStackView != null) { 137 // If onRecentsHidden is not triggered, we need to the stack view again here 138 mTaskStackView.reset(); 139 mTaskStackView.setStack(stack); 140 } else { 141 mTaskStackView = new TaskStackView(getContext(), stack); 142 mTaskStackView.setCallbacks(this); 143 addView(mTaskStackView); 144 } 145 } else { 146 if (mTaskStackView != null) { 147 removeView(mTaskStackView); 148 } 149 mTaskStackView = new TaskStackView(getContext(), stack); 150 mTaskStackView.setCallbacks(this); 151 addView(mTaskStackView); 152 } 153 154 // Trigger a new layout 155 requestLayout(); 156 } 157 158 /** Gets the next task in the stack - or if the last - the top task */ 159 public Task getNextTaskOrTopTask(Task taskToSearch) { 160 Task returnTask = null; 161 boolean found = false; 162 if (mTaskStackView != null) { 163 TaskStack stack = mTaskStackView.getStack(); 164 ArrayList<Task> taskList = stack.getTasks(); 165 // Iterate the stack views and try and find the focused task 166 for (int j = taskList.size() - 1; j >= 0; --j) { 167 Task task = taskList.get(j); 168 // Return the next task in the line. 169 if (found) 170 return task; 171 // Remember the first possible task as the top task. 172 if (returnTask == null) 173 returnTask = task; 174 if (task == taskToSearch) 175 found = true; 176 } 177 } 178 return returnTask; 179 } 180 181 /** Launches the focused task from the first stack if possible */ 182 public boolean launchFocusedTask() { 183 if (mTaskStackView != null) { 184 TaskStack stack = mTaskStackView.getStack(); 185 // Iterate the stack views and try and find the focused task 186 List<TaskView> taskViews = mTaskStackView.getTaskViews(); 187 int taskViewCount = taskViews.size(); 188 for (int j = 0; j < taskViewCount; j++) { 189 TaskView tv = taskViews.get(j); 190 Task task = tv.getTask(); 191 if (tv.isFocusedTask()) { 192 onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null, 193 INVALID_STACK_ID); 194 return true; 195 } 196 } 197 } 198 return false; 199 } 200 201 /** Launches a given task. */ 202 public boolean launchTask(Task task, Rect taskBounds, int destinationStack) { 203 if (mTaskStackView != null) { 204 TaskStack stack = mTaskStackView.getStack(); 205 // Iterate the stack views and try and find the given task. 206 List<TaskView> taskViews = mTaskStackView.getTaskViews(); 207 int taskViewCount = taskViews.size(); 208 for (int j = 0; j < taskViewCount; j++) { 209 TaskView tv = taskViews.get(j); 210 if (tv.getTask() == task) { 211 onTaskViewClicked(mTaskStackView, tv, stack, task, false, taskBounds != null, 212 taskBounds, destinationStack); 213 return true; 214 } 215 } 216 } 217 return false; 218 } 219 220 /** Launches the task that Recents was launched from, if possible */ 221 public boolean launchPreviousTask() { 222 if (mTaskStackView != null) { 223 TaskStack stack = mTaskStackView.getStack(); 224 ArrayList<Task> tasks = stack.getTasks(); 225 226 // Find the launch task in the stack 227 // TODO: replace this with an event from RecentsActivity 228 if (!tasks.isEmpty()) { 229 int taskCount = tasks.size(); 230 for (int j = 0; j < taskCount; j++) { 231 if (tasks.get(j).isLaunchTarget) { 232 Task task = tasks.get(j); 233 TaskView tv = mTaskStackView.getChildViewForTask(task); 234 onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null, 235 INVALID_STACK_ID); 236 return true; 237 } 238 } 239 } 240 } 241 return false; 242 } 243 244 /** Requests all task stacks to start their enter-recents animation */ 245 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 246 // We have to increment/decrement the post animation trigger in case there are no children 247 // to ensure that it runs 248 ctx.postAnimationTrigger.increment(); 249 if (mTaskStackView != null) { 250 mTaskStackView.startEnterRecentsAnimation(ctx); 251 } 252 ctx.postAnimationTrigger.decrement(); 253 } 254 255 /** Requests all task stacks to start their exit-recents animation */ 256 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 257 // We have to increment/decrement the post animation trigger in case there are no children 258 // to ensure that it runs 259 ctx.postAnimationTrigger.increment(); 260 if (mTaskStackView != null) { 261 mTaskStackView.startExitToHomeAnimation(ctx); 262 } 263 ctx.postAnimationTrigger.decrement(); 264 265 // Notify of the exit animation 266 EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted()); 267 } 268 269 /** Adds the search bar */ 270 public void setSearchBar(RecentsAppWidgetHostView searchBar) { 271 // Remove the previous search bar if one exists 272 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 273 removeView(mSearchBar); 274 } 275 // Add the new search bar 276 if (searchBar != null) { 277 mSearchBar = searchBar; 278 addView(mSearchBar); 279 } 280 } 281 282 /** Returns whether there is currently a search bar */ 283 public boolean hasValidSearchBar() { 284 return mSearchBar != null && !mSearchBar.isReinflateRequired(); 285 } 286 287 /** Sets the visibility of the search bar */ 288 public void setSearchBarVisibility(int visibility) { 289 if (mSearchBar != null) { 290 mSearchBar.setVisibility(visibility); 291 // Always bring the search bar to the top 292 mSearchBar.bringToFront(); 293 } 294 } 295 296 @Override 297 protected void onAttachedToWindow() { 298 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 299 EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 1); 300 super.onAttachedToWindow(); 301 } 302 303 @Override 304 protected void onDetachedFromWindow() { 305 super.onDetachedFromWindow(); 306 EventBus.getDefault().unregister(this); 307 EventBus.getDefault().unregister(mTouchHandler); 308 } 309 310 /** 311 * This is called with the full size of the window since we are handling our own insets. 312 */ 313 @Override 314 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 315 RecentsConfiguration config = Recents.getConfiguration(); 316 int width = MeasureSpec.getSize(widthMeasureSpec); 317 int height = MeasureSpec.getSize(heightMeasureSpec); 318 319 // Get the search bar bounds and measure the search bar layout 320 Rect searchBarSpaceBounds = new Rect(); 321 if (mSearchBar != null) { 322 config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top, 323 searchBarSpaceBounds); 324 mSearchBar.measure( 325 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 326 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 327 } 328 329 Rect taskStackBounds = new Rect(); 330 config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top, 331 mSystemInsets.right, searchBarSpaceBounds, taskStackBounds); 332 if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) { 333 mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets); 334 mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec); 335 } 336 337 if (mDragView != null) { 338 Rect taskRect = mTaskStackView.mLayoutAlgorithm.mTaskRect; 339 mDragView.measure( 340 MeasureSpec.makeMeasureSpec(taskRect.width(), MeasureSpec.AT_MOST), 341 MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST)); 342 } 343 344 setMeasuredDimension(width, height); 345 } 346 347 /** 348 * This is called with the full size of the window since we are handling our own insets. 349 */ 350 @Override 351 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 352 RecentsConfiguration config = Recents.getConfiguration(); 353 354 // Get the search bar bounds so that we lay it out 355 Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 356 Rect searchBarSpaceBounds = new Rect(); 357 if (mSearchBar != null) { 358 config.getSearchBarBounds(measuredRect, 359 mSystemInsets.top, searchBarSpaceBounds); 360 mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top, 361 searchBarSpaceBounds.right, searchBarSpaceBounds.bottom); 362 } 363 364 if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) { 365 mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight()); 366 } 367 368 if (mDragView != null) { 369 mDragView.layout(left, top, left + mDragView.getMeasuredWidth(), 370 top + mDragView.getMeasuredHeight()); 371 } 372 } 373 374 @Override 375 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 376 mSystemInsets.set(insets.getSystemWindowInsets()); 377 requestLayout(); 378 return insets.consumeSystemWindowInsets(); 379 } 380 381 @Override 382 public boolean onInterceptTouchEvent(MotionEvent ev) { 383 return mTouchHandler.onInterceptTouchEvent(ev); 384 } 385 386 @Override 387 public boolean onTouchEvent(MotionEvent ev) { 388 return mTouchHandler.onTouchEvent(ev); 389 } 390 391 @Override 392 protected void dispatchDraw(Canvas canvas) { 393 super.dispatchDraw(canvas); 394 for (int i = mVisibleDockStates.length - 1; i >= 0; i--) { 395 Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay; 396 if (d.getAlpha() > 0) { 397 d.draw(canvas); 398 } 399 } 400 } 401 402 @Override 403 protected boolean verifyDrawable(Drawable who) { 404 for (int i = mVisibleDockStates.length - 1; i >= 0; i--) { 405 Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay; 406 if (d == who) { 407 return true; 408 } 409 } 410 return super.verifyDrawable(who); 411 } 412 413 /** Unfilters any filtered stacks */ 414 public boolean unfilterFilteredStacks() { 415 if (mStacks != null) { 416 // Check if there are any filtered stacks and unfilter them before we back out of Recents 417 boolean stacksUnfiltered = false; 418 int numStacks = mStacks.size(); 419 for (int i = 0; i < numStacks; i++) { 420 TaskStack stack = mStacks.get(i); 421 if (stack.hasFilteredTasks()) { 422 stack.unfilterTasks(); 423 stacksUnfiltered = true; 424 } 425 } 426 return stacksUnfiltered; 427 } 428 return false; 429 } 430 431 public void disableLayersForOneFrame() { 432 if (mTaskStackView != null) { 433 mTaskStackView.disableLayersForOneFrame(); 434 } 435 } 436 437 private void postDrawHeaderThumbnailTransitionRunnable(final TaskStackView view, 438 final TaskView clickedView, final int offsetX, final int offsetY, 439 final float stackScroll, 440 final ActivityOptions.OnAnimationStartedListener animStartedListener, 441 final int destinationStack) { 442 Runnable r = new Runnable() { 443 @Override 444 public void run() { 445 overrideDrawHeaderThumbnailTransition(view, clickedView, offsetX, offsetY, 446 stackScroll, animStartedListener, destinationStack); 447 448 } 449 }; 450 451 mCb.runAfterPause(r); 452 } 453 454 private void overrideDrawHeaderThumbnailTransition(TaskStackView stackView, 455 TaskView clickedTask, int offsetX, int offsetY, float stackScroll, 456 final ActivityOptions.OnAnimationStartedListener animStartedListener, 457 int destinationStack) { 458 List<AppTransitionAnimationSpec> specs = getAppTransitionAnimationSpecs(stackView, 459 clickedTask, offsetX, offsetY, stackScroll, destinationStack); 460 if (specs == null) { 461 return; 462 } 463 464 IRemoteCallback.Stub callback = new IRemoteCallback.Stub() { 465 @Override 466 public void sendResult(Bundle data) throws RemoteException { 467 post(new Runnable() { 468 @Override 469 public void run() { 470 if (animStartedListener != null) { 471 animStartedListener.onAnimationStarted(); 472 } 473 } 474 }); 475 } 476 }; 477 478 AppTransitionAnimationSpec[] specsArray = 479 new AppTransitionAnimationSpec[specs.size()]; 480 try { 481 WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionMultiThumb( 482 specs.toArray(specsArray), callback, null, true /* scaleUp */); 483 484 } catch (RemoteException e) { 485 Log.w(TAG, "Error overriding app transition", e); 486 } 487 } 488 489 private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView, 490 TaskView clickedTask, int offsetX, int offsetY, float stackScroll, 491 int destinationStack) { 492 final int targetStackId = destinationStack != INVALID_STACK_ID ? 493 destinationStack : clickedTask.getTask().key.stackId; 494 if (targetStackId != FREEFORM_WORKSPACE_STACK_ID 495 && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) { 496 return null; 497 } 498 // If this is a full screen stack, the transition will be towards the single, full screen 499 // task. We only need the transition spec for this task. 500 List<AppTransitionAnimationSpec> specs = new ArrayList<>(); 501 if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) { 502 specs.add(createThumbnailHeaderAnimationSpec( 503 stackView, offsetX, offsetY, stackScroll, clickedTask, 504 clickedTask.getTask().key.id, ADD_HEADER_BITMAP)); 505 return specs; 506 } 507 // This is a free form stack or full screen stack, so there will be multiple windows 508 // animating from thumbnails. We need transition animation specs for all of them. 509 510 // We will use top and bottom task views as a base for tasks, that aren't visible on the 511 // screen. This is necessary for cascade recents list, where some of the tasks might be 512 // hidden. 513 List<TaskView> taskViews = stackView.getTaskViews(); 514 int childCount = taskViews.size(); 515 TaskView topChild = taskViews.get(0); 516 TaskView bottomChild = taskViews.get(childCount - 1); 517 SparseArray<TaskView> taskViewsByTaskId = new SparseArray<>(); 518 for (int i = 0; i < childCount; i++) { 519 TaskView taskView = taskViews.get(i); 520 taskViewsByTaskId.put(taskView.getTask().key.id, taskView); 521 } 522 523 TaskStack stack = stackView.getStack(); 524 // We go through all tasks now and for each generate transition animation spec. If there is 525 // a view associated with a task, we use that view as a base for the animation. If there 526 // isn't, we use bottom or top view, depending on which one would be closer to the task 527 // view if it existed. 528 ArrayList<Task> tasks = stack.getTasks(); 529 boolean passedClickedTask = false; 530 for (int i = 0, n = tasks.size(); i < n; i++) { 531 Task task = tasks.get(i); 532 TaskView taskView = taskViewsByTaskId.get(task.key.id); 533 if (taskView != null) { 534 specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY, 535 stackScroll, taskView, taskView.getTask().key.id, ADD_HEADER_BITMAP)); 536 if (taskView == clickedTask) { 537 passedClickedTask = true; 538 } 539 } else { 540 taskView = passedClickedTask ? bottomChild : topChild; 541 specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY, 542 stackScroll, taskView, task.key.id, !ADD_HEADER_BITMAP)); 543 } 544 } 545 546 return specs; 547 } 548 549 private AppTransitionAnimationSpec createThumbnailHeaderAnimationSpec(TaskStackView stackView, 550 int offsetX, int offsetY, float stackScroll, TaskView tv, int taskId, 551 boolean addHeaderBitmap) { 552 // Disable any focused state before we draw the header 553 // Upfront the processing of the thumbnail 554 if (tv.isFocusedTask()) { 555 tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */); 556 } 557 TaskViewTransform transform = new TaskViewTransform(); 558 transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll, 559 transform, null); 560 561 float scale = tv.getScaleX(); 562 int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale); 563 int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale); 564 565 Bitmap b = null; 566 if (addHeaderBitmap) { 567 b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight, 568 Bitmap.Config.ARGB_8888); 569 570 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { 571 b.eraseColor(0xFFff0000); 572 } else { 573 Canvas c = new Canvas(b); 574 c.scale(tv.getScaleX(), tv.getScaleY()); 575 tv.mHeaderView.draw(c); 576 c.setBitmap(null); 577 578 } 579 b = b.createAshmemBitmap(); 580 } 581 582 int[] pts = new int[2]; 583 tv.getLocationOnScreen(pts); 584 585 final int left = pts[0] + offsetX; 586 final int top = pts[1] + offsetY; 587 final Rect rect = new Rect(left, top, left + (int) transform.rect.width(), 588 top + (int) transform.rect.height()); 589 590 return new AppTransitionAnimationSpec(taskId, b, rect); 591 } 592 593 /** 594 * Cancels any running window transitions for the launched task (the task animating into 595 * Recents). 596 */ 597 private void cancelLaunchedTaskWindowTransition(final Task task) { 598 SystemServicesProxy ssp = Recents.getSystemServices(); 599 RecentsConfiguration config = Recents.getConfiguration(); 600 RecentsActivityLaunchState launchState = config.getLaunchState(); 601 if (launchState.launchedToTaskId != -1 && 602 launchState.launchedToTaskId != task.key.id) { 603 ssp.cancelWindowTransition(launchState.launchedToTaskId); 604 } 605 } 606 607 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 608 609 @Override 610 public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv, 611 final TaskStack stack, final Task task, final boolean lockToTask, 612 final boolean boundsValid, final Rect bounds, int destinationStack) { 613 // Notify any callbacks of the launching of a new task 614 if (mCb != null) { 615 mCb.onTaskViewClicked(); 616 } 617 618 // Upfront the processing of the thumbnail 619 TaskViewTransform transform = new TaskViewTransform(); 620 View sourceView; 621 int offsetX = 0; 622 int offsetY = 0; 623 float stackScroll = stackView.getScroller().getStackScroll(); 624 if (tv == null) { 625 // If there is no actual task view, then use the stack view as the source view 626 // and then offset to the expected transform rect, but bound this to just 627 // outside the display rect (to ensure we don't animate from too far away) 628 sourceView = stackView; 629 offsetX = (int) transform.rect.left; 630 offsetY = getMeasuredHeight(); 631 } else { 632 sourceView = tv.mThumbnailView; 633 } 634 635 // Compute the thumbnail to scale up from 636 final SystemServicesProxy ssp = Recents.getSystemServices(); 637 boolean screenPinningRequested = false; 638 ActivityOptions opts = null; 639 ActivityOptions.OnAnimationStartedListener animStartedListener = null; 640 if (task.thumbnail != null && task.thumbnail.getWidth() > 0 && 641 task.thumbnail.getHeight() > 0) { 642 animStartedListener = new ActivityOptions.OnAnimationStartedListener() { 643 @Override 644 public void onAnimationStarted() { 645 // If we are launching into another task, cancel the previous task's 646 // window transition 647 cancelLaunchedTaskWindowTransition(task); 648 649 if (lockToTask) { 650 // Request screen pinning after the animation runs 651 postDelayed(new Runnable() { 652 @Override 653 public void run() { 654 EventBus.getDefault().send(new ScreenPinningRequestEvent( 655 getContext(), ssp)); 656 } 657 }, 350); 658 } 659 } 660 }; 661 postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll, 662 animStartedListener, destinationStack); 663 opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, 664 Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(), 665 offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(), 666 sourceView.getHandler(), animStartedListener); 667 screenPinningRequested = true; 668 } else { 669 opts = ActivityOptions.makeBasic(); 670 } 671 if (boundsValid) { 672 opts.setBounds(bounds.isEmpty() ? null : bounds); 673 } 674 final ActivityOptions launchOpts = opts; 675 final boolean finalScreenPinningRequested = screenPinningRequested; 676 final Runnable launchRunnable = new Runnable() { 677 @Override 678 public void run() { 679 if (task.isActive) { 680 // Bring an active task to the foreground 681 ssp.moveTaskToFront(task.key.id, launchOpts); 682 } else { 683 if (ssp.startActivityFromRecents(getContext(), task.key.id, task.activityLabel, 684 launchOpts)) { 685 if (!finalScreenPinningRequested) { 686 // If we have not requested this already to be run after the window 687 // transition, then just run it now 688 EventBus.getDefault().send(new ScreenPinningRequestEvent( 689 getContext(), ssp)); 690 } 691 } else { 692 // Dismiss the task and return the user to home if we fail to 693 // launch the task 694 EventBus.getDefault().send(new DismissTaskViewEvent(task, tv)); 695 if (mCb != null) { 696 mCb.onTaskLaunchFailed(); 697 } 698 699 // Keep track of failed launches 700 MetricsLogger.count(getContext(), "overview_task_launch_failed", 1); 701 } 702 } 703 } 704 }; 705 706 // Keep track of the index of the task launch 707 int taskIndexFromFront = 0; 708 int taskIndex = stack.indexOfTask(task); 709 if (taskIndex > -1) { 710 taskIndexFromFront = stack.getTaskCount() - taskIndex - 1; 711 } 712 MetricsLogger.histogram(getContext(), "overview_task_launch_index", taskIndexFromFront); 713 714 // Launch the app right away if there is no task view, otherwise, animate the icon out first 715 if (tv == null) { 716 launchRunnable.run(); 717 } else { 718 if (task.group != null && !task.group.isFrontMostTask(task)) { 719 // For affiliated tasks that are behind other tasks, we must animate the front cards 720 // out of view before starting the task transition 721 stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask); 722 } else { 723 // Otherwise, we can start the task transition immediately 724 stackView.startLaunchTaskAnimation(tv, null, lockToTask); 725 launchRunnable.run(); 726 } 727 } 728 } 729 730 @Override 731 public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) { 732 /* TODO: Not currently enabled 733 if (removedTasks != null) { 734 int taskCount = removedTasks.size(); 735 for (int i = 0; i < taskCount; i++) { 736 onTaskViewDismissed(removedTasks.get(i)); 737 } 738 } 739 */ 740 741 mCb.onAllTaskViewsDismissed(); 742 743 // Keep track of all-deletions 744 MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1); 745 } 746 747 @Override 748 public void onTaskStackFilterTriggered() { 749 // Hide the search bar 750 if (mSearchBar != null) { 751 int filterDuration = getResources().getInteger( 752 R.integer.recents_filter_animate_current_views_duration); 753 mSearchBar.animate() 754 .alpha(0f) 755 .setStartDelay(0) 756 .setInterpolator(mFastOutSlowInInterpolator) 757 .setDuration(filterDuration) 758 .withLayer() 759 .start(); 760 } 761 } 762 763 @Override 764 public void onTaskStackUnfilterTriggered() { 765 // Show the search bar 766 if (mSearchBar != null) { 767 int filterDuration = getResources().getInteger( 768 R.integer.recents_filter_animate_new_views_duration); 769 mSearchBar.animate() 770 .alpha(1f) 771 .setStartDelay(0) 772 .setInterpolator(mFastOutSlowInInterpolator) 773 .setDuration(filterDuration) 774 .withLayer() 775 .start(); 776 } 777 } 778 779 /**** EventBus Events ****/ 780 781 public final void onBusEvent(DragStartEvent event) { 782 // Add the drag view 783 mDragView = event.dragView; 784 addView(mDragView); 785 786 updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(), 787 TaskStack.DockState.NONE.viewState.dockAreaAlpha); 788 } 789 790 public final void onBusEvent(DragDropTargetChangedEvent event) { 791 if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) { 792 updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(), 793 TaskStack.DockState.NONE.viewState.dockAreaAlpha); 794 } else { 795 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 796 updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, -1); 797 } 798 } 799 800 public final void onBusEvent(final DragEndEvent event) { 801 final Runnable cleanUpRunnable = new Runnable() { 802 @Override 803 public void run() { 804 // Remove the drag view 805 removeView(mDragView); 806 mDragView = null; 807 } 808 }; 809 810 // Animate the overlay alpha back to 0 811 updateVisibleDockRegions(null, -1); 812 813 if (event.dropTarget == null) { 814 // No drop targets for hit, so just animate the task back to its place 815 event.postAnimationTrigger.increment(); 816 event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 817 @Override 818 public void run() { 819 cleanUpRunnable.run(); 820 } 821 }); 822 // Animate the task back to where it was before then clean up afterwards 823 TaskViewTransform taskTransform = new TaskViewTransform(); 824 TaskStackLayoutAlgorithm layoutAlgorithm = mTaskStackView.getStackAlgorithm(); 825 layoutAlgorithm.getStackTransform(event.task, 826 mTaskStackView.getScroller().getStackScroll(), taskTransform, null); 827 event.dragView.animate() 828 .scaleX(taskTransform.scale) 829 .scaleY(taskTransform.scale) 830 .translationX((layoutAlgorithm.mTaskRect.left - event.dragView.getLeft()) 831 + taskTransform.translationX) 832 .translationY((layoutAlgorithm.mTaskRect.top - event.dragView.getTop()) 833 + taskTransform.translationY) 834 .setDuration(175) 835 .setInterpolator(mFastOutSlowInInterpolator) 836 .withEndAction(event.postAnimationTrigger.decrementAsRunnable()) 837 .start(); 838 839 } else if (event.dropTarget instanceof TaskStack.DockState) { 840 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 841 842 // For now, just remove the drag view and the original task 843 // TODO: Animate the task to the drop target rect before launching it above 844 cleanUpRunnable.run(); 845 846 // Dock the task and launch it 847 SystemServicesProxy ssp = Recents.getSystemServices(); 848 ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode); 849 launchTask(event.task, null, INVALID_STACK_ID); 850 851 } else { 852 // We dropped on another drop target, so just add the cleanup to the post animation 853 // trigger 854 event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 855 @Override 856 public void run() { 857 cleanUpRunnable.run(); 858 } 859 }); 860 } 861 } 862 863 /** 864 * Updates the dock region to match the specified dock state. 865 */ 866 private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, int overrideAlpha) { 867 ArraySet<TaskStack.DockState> newDockStatesSet = new ArraySet<>(); 868 if (newDockStates != null) { 869 for (TaskStack.DockState dockState : newDockStates) { 870 newDockStatesSet.add(dockState); 871 } 872 } 873 for (TaskStack.DockState dockState : mVisibleDockStates) { 874 TaskStack.DockState.ViewState viewState = dockState.viewState; 875 if (newDockStates == null || !newDockStatesSet.contains(dockState)) { 876 // This is no longer visible, so hide it 877 viewState.startAlphaAnimation(0, 150); 878 } else { 879 // This state is now visible, update the bounds and show it 880 int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha); 881 viewState.dockAreaOverlay.setBounds( 882 dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight())); 883 viewState.dockAreaOverlay.setCallback(this); 884 viewState.startAlphaAnimation(alpha, 150); 885 } 886 } 887 } 888} 889