RecentsView.java revision 8eaeb7dc93ed71b768a2ea8d45021cca010e8263
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.Paint; 28import android.graphics.Rect; 29import android.net.Uri; 30import android.os.UserHandle; 31import android.provider.Settings; 32import android.util.AttributeSet; 33import android.view.LayoutInflater; 34import android.view.View; 35import android.view.WindowInsets; 36import android.widget.FrameLayout; 37import com.android.systemui.recents.Console; 38import com.android.systemui.recents.Constants; 39import com.android.systemui.recents.RecentsConfiguration; 40import com.android.systemui.recents.model.RecentsPackageMonitor; 41import com.android.systemui.recents.model.RecentsTaskLoader; 42import com.android.systemui.recents.model.SpaceNode; 43import com.android.systemui.recents.model.Task; 44import com.android.systemui.recents.model.TaskStack; 45 46import java.util.ArrayList; 47import java.util.Set; 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 onTaskLaunching(); 59 public void onExitToHomeAnimationTriggered(); 60 } 61 62 RecentsConfiguration mConfig; 63 LayoutInflater mInflater; 64 Paint mDebugModePaint; 65 66 // The space partitioning root of this container 67 SpaceNode mBSP; 68 // Whether there are any tasks 69 boolean mHasTasks; 70 // Search bar view 71 View mSearchBar; 72 // Recents view callbacks 73 RecentsViewCallbacks mCb; 74 75 public RecentsView(Context context) { 76 super(context); 77 } 78 79 public RecentsView(Context context, AttributeSet attrs) { 80 this(context, attrs, 0); 81 } 82 83 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 84 this(context, attrs, defStyleAttr, 0); 85 } 86 87 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 88 super(context, attrs, defStyleAttr, defStyleRes); 89 mConfig = RecentsConfiguration.getInstance(); 90 mInflater = LayoutInflater.from(context); 91 } 92 93 /** Sets the callbacks */ 94 public void setCallbacks(RecentsViewCallbacks cb) { 95 mCb = cb; 96 } 97 98 /** Set/get the bsp root node */ 99 public void setBSP(SpaceNode n) { 100 mBSP = n; 101 102 // Create and add all the stacks for this partition of space. 103 mHasTasks = false; 104 removeAllViews(); 105 ArrayList<TaskStack> stacks = mBSP.getStacks(); 106 for (TaskStack stack : stacks) { 107 TaskStackView stackView = new TaskStackView(getContext(), stack); 108 stackView.setCallbacks(this); 109 addView(stackView); 110 mHasTasks |= (stack.getTaskCount() > 0); 111 } 112 113 // Enable debug mode drawing 114 if (mConfig.debugModeEnabled) { 115 mDebugModePaint = new Paint(); 116 mDebugModePaint.setColor(0xFFff0000); 117 mDebugModePaint.setStyle(Paint.Style.STROKE); 118 mDebugModePaint.setStrokeWidth(5f); 119 setWillNotDraw(false); 120 } 121 } 122 123 /** Launches the focused task from the first stack if possible */ 124 public boolean launchFocusedTask() { 125 // Get the first stack view 126 int childCount = getChildCount(); 127 for (int i = 0; i < childCount; i++) { 128 View child = getChildAt(i); 129 if (child instanceof TaskStackView) { 130 TaskStackView stackView = (TaskStackView) child; 131 TaskStack stack = stackView.mStack; 132 // Iterate the stack views and try and find the focused task 133 int taskCount = stackView.getChildCount(); 134 for (int j = 0; j < taskCount; j++) { 135 TaskView tv = (TaskView) stackView.getChildAt(j); 136 Task task = tv.getTask(); 137 if (tv.isFocusedTask()) { 138 if (Console.Enabled) { 139 Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]", 140 "Found focused Task"); 141 } 142 onTaskLaunched(stackView, tv, stack, task); 143 return true; 144 } 145 } 146 } 147 } 148 if (Console.Enabled) { 149 Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]", 150 "No Tasks focused"); 151 } 152 return false; 153 } 154 155 /** Launches the first task from the first stack if possible */ 156 public boolean launchFirstTask() { 157 // Get the first stack view 158 int childCount = getChildCount(); 159 for (int i = 0; i < childCount; i++) { 160 View child = getChildAt(i); 161 if (child instanceof TaskStackView) { 162 TaskStackView stackView = (TaskStackView) child; 163 TaskStack stack = stackView.mStack; 164 ArrayList<Task> tasks = stack.getTasks(); 165 166 // Get the first task in the stack 167 if (!tasks.isEmpty()) { 168 Task task = tasks.get(tasks.size() - 1); 169 TaskView tv = null; 170 171 // Try and use the first child task view as the source of the launch animation 172 if (stackView.getChildCount() > 0) { 173 TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1); 174 if (stv.getTask() == task) { 175 tv = stv; 176 } 177 } 178 onTaskLaunched(stackView, tv, stack, task); 179 return true; 180 } 181 } 182 } 183 return false; 184 } 185 186 /** Requests all task stacks to start their enter-recents animation */ 187 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 188 int childCount = getChildCount(); 189 for (int i = 0; i < childCount; i++) { 190 View child = getChildAt(i); 191 if (child instanceof TaskStackView) { 192 TaskStackView stackView = (TaskStackView) child; 193 stackView.startEnterRecentsAnimation(ctx); 194 } 195 } 196 } 197 198 /** Requests all task stacks to start their exit-recents animation */ 199 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 200 // Handle the case when there are no views by incrementing and decrementing after all 201 // animations are started. 202 ctx.postAnimationTrigger.increment(); 203 204 if (Constants.DebugFlags.App.EnableHomeTransition) { 205 int childCount = getChildCount(); 206 for (int i = 0; i < childCount; i++) { 207 View child = getChildAt(i); 208 if (child instanceof TaskStackView) { 209 TaskStackView stackView = (TaskStackView) child; 210 stackView.startExitToHomeAnimation(ctx); 211 } 212 } 213 } 214 215 // Handle the case when there are no views by incrementing and decrementing after all 216 // animations are started. 217 ctx.postAnimationTrigger.decrement(); 218 219 // Notify of the exit animation 220 mCb.onExitToHomeAnimationTriggered(); 221 } 222 223 /** Adds the search bar */ 224 public void setSearchBar(View searchBar) { 225 // Create the search bar (and hide it if we have no recent tasks) 226 if (Constants.DebugFlags.App.EnableSearchLayout) { 227 // Remove the previous search bar if one exists 228 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 229 removeView(mSearchBar); 230 } 231 // Add the new search bar 232 if (searchBar != null) { 233 mSearchBar = searchBar; 234 mSearchBar.setVisibility(mHasTasks ? View.VISIBLE : View.GONE); 235 addView(mSearchBar); 236 237 if (Console.Enabled) { 238 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsView|setSearchBar]", 239 "" + (mSearchBar.getVisibility() == View.VISIBLE), 240 Console.AnsiBlue); 241 } 242 } 243 } 244 } 245 246 /** 247 * This is called with the full size of the window since we are handling our own insets. 248 */ 249 @Override 250 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 251 int width = MeasureSpec.getSize(widthMeasureSpec); 252 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 253 int height = MeasureSpec.getSize(heightMeasureSpec); 254 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 255 256 if (Console.Enabled) { 257 Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|measure]", 258 "width: " + width + " height: " + height, Console.AnsiGreen); 259 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 260 Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onMeasure"); 261 } 262 263 // Get the search bar bounds and measure the search bar layout 264 if (mSearchBar != null) { 265 Rect searchBarSpaceBounds = new Rect(); 266 mConfig.getSearchBarBounds(width, height - mConfig.systemInsets.top, searchBarSpaceBounds); 267 mSearchBar.measure( 268 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 269 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 270 } 271 272 // We give the full width of the space, not including the right nav bar insets in landscape, 273 // to the stack view, since we want the tasks to render under the search bar in landscape. 274 // In addition, we give it the full height, not including the top inset or search bar space, 275 // since we want the tasks to render under the navigation buttons in portrait. 276 Rect taskStackBounds = new Rect(); 277 mConfig.getTaskStackBounds(width, height, taskStackBounds); 278 int childWidth = width - mConfig.systemInsets.right; 279 int childHeight = taskStackBounds.height() - mConfig.systemInsets.top; 280 281 // Measure each TaskStackView 282 int childCount = getChildCount(); 283 for (int i = 0; i < childCount; i++) { 284 View child = getChildAt(i); 285 if (child instanceof TaskStackView && child.getVisibility() != GONE) { 286 child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode), 287 MeasureSpec.makeMeasureSpec(childHeight, heightMode)); 288 } 289 } 290 291 setMeasuredDimension(width, height); 292 } 293 294 /** 295 * This is called with the full size of the window since we are handling our own insets. 296 */ 297 @Override 298 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 299 if (Console.Enabled) { 300 Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|layout]", 301 new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen); 302 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 303 Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onLayout"); 304 } 305 306 // Get the search bar bounds so that we lay it out 307 if (mSearchBar != null) { 308 Rect searchBarSpaceBounds = new Rect(); 309 mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds); 310 mSearchBar.layout(mConfig.systemInsets.left + searchBarSpaceBounds.left, 311 mConfig.systemInsets.top + searchBarSpaceBounds.top, 312 mConfig.systemInsets.left + mSearchBar.getMeasuredWidth(), 313 mConfig.systemInsets.top + mSearchBar.getMeasuredHeight()); 314 } 315 316 // We offset the stack view by the left inset (if any), but lay it out under the search bar. 317 // In addition, we offset our stack views by the top inset and search bar height, but not 318 // the bottom insets because we want it to render under the navigation buttons. 319 Rect taskStackBounds = new Rect(); 320 mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds); 321 left += mConfig.systemInsets.left; 322 top += mConfig.systemInsets.top + taskStackBounds.top; 323 324 // Layout each child 325 // XXX: Based on the space node for that task view 326 int childCount = getChildCount(); 327 for (int i = 0; i < childCount; i++) { 328 View child = getChildAt(i); 329 if (child instanceof TaskStackView && child.getVisibility() != GONE) { 330 TaskStackView tsv = (TaskStackView) child; 331 child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight()); 332 } 333 } 334 } 335 336 @Override 337 protected void onDraw(Canvas canvas) { 338 super.onDraw(canvas); 339 // Debug mode drawing 340 if (mConfig.debugModeEnabled) { 341 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugModePaint); 342 } 343 } 344 345 @Override 346 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 347 if (Console.Enabled) { 348 Console.log(Constants.Log.UI.MeasureAndLayout, 349 "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen); 350 } 351 352 // Update the configuration with the latest system insets and trigger a relayout 353 mConfig.updateSystemInsets(insets.getSystemWindowInsets()); 354 requestLayout(); 355 356 return insets.consumeSystemWindowInsets(false, false, false, true); 357 } 358 359 /** Notifies each task view of the user interaction. */ 360 public void onUserInteraction() { 361 // Get the first stack view 362 TaskStackView stackView = null; 363 int childCount = getChildCount(); 364 for (int i = 0; i < childCount; i++) { 365 View child = getChildAt(i); 366 if (child instanceof TaskStackView) { 367 stackView = (TaskStackView) child; 368 stackView.onUserInteraction(); 369 } 370 } 371 } 372 373 /** Focuses the next task in the first stack view */ 374 public void focusNextTask(boolean forward) { 375 // Get the first stack view 376 TaskStackView stackView = null; 377 int childCount = getChildCount(); 378 for (int i = 0; i < childCount; i++) { 379 View child = getChildAt(i); 380 if (child instanceof TaskStackView) { 381 stackView = (TaskStackView) child; 382 break; 383 } 384 } 385 386 if (stackView != null) { 387 stackView.focusNextTask(forward); 388 } 389 } 390 391 /** Unfilters any filtered stacks */ 392 public boolean unfilterFilteredStacks() { 393 if (mBSP != null) { 394 // Check if there are any filtered stacks and unfilter them before we back out of Recents 395 boolean stacksUnfiltered = false; 396 ArrayList<TaskStack> stacks = mBSP.getStacks(); 397 for (TaskStack stack : stacks) { 398 if (stack.hasFilteredTasks()) { 399 stack.unfilterTasks(); 400 stacksUnfiltered = true; 401 } 402 } 403 return stacksUnfiltered; 404 } 405 return false; 406 } 407 408 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 409 410 @Override 411 public void onTaskLaunched(final TaskStackView stackView, final TaskView tv, 412 final TaskStack stack, final Task task) { 413 // Notify any callbacks of the launching of a new task 414 if (mCb != null) { 415 mCb.onTaskLaunching(); 416 } 417 418 // Upfront the processing of the thumbnail 419 TaskViewTransform transform; 420 View sourceView = tv; 421 int offsetX = 0; 422 int offsetY = 0; 423 int stackScroll = stackView.getStackScroll(); 424 if (tv == null) { 425 // If there is no actual task view, then use the stack view as the source view 426 // and then offset to the expected transform rect, but bound this to just 427 // outside the display rect (to ensure we don't animate from too far away) 428 sourceView = stackView; 429 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); 430 offsetX = transform.rect.left; 431 offsetY = Math.min(transform.rect.top, mConfig.displayRect.height()); 432 } else { 433 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); 434 } 435 436 // Compute the thumbnail to scale up from 437 ActivityOptions opts = null; 438 int thumbnailWidth = transform.rect.width(); 439 int thumbnailHeight = transform.rect.height(); 440 if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && 441 task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { 442 // Resize the thumbnail to the size of the view that we are animating from 443 Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, 444 Bitmap.Config.ARGB_8888); 445 Canvas c = new Canvas(b); 446 c.drawBitmap(task.thumbnail, 447 new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), 448 new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); 449 c.setBitmap(null); 450 opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, 451 b, offsetX, offsetY); 452 } 453 454 final ActivityOptions launchOpts = opts; 455 final Runnable launchRunnable = new Runnable() { 456 @Override 457 public void run() { 458 if (Console.Enabled) { 459 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 460 Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity"); 461 } 462 463 if (task.isActive) { 464 // Bring an active task to the foreground 465 RecentsTaskLoader.getInstance().getSystemServicesProxy() 466 .moveTaskToFront(task.key.id, launchOpts); 467 } else { 468 // Launch the activity anew with the desired animation 469 Intent i = new Intent(task.key.baseIntent); 470 i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 471 | Intent.FLAG_ACTIVITY_TASK_ON_HOME 472 | Intent.FLAG_ACTIVITY_NEW_TASK); 473 try { 474 UserHandle taskUser = new UserHandle(task.userId); 475 if (launchOpts != null) { 476 getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser); 477 } else { 478 getContext().startActivityAsUser(i, taskUser); 479 } 480 } catch (ActivityNotFoundException anfe) { 481 Console.logError(getContext(), "Could not start Activity"); 482 } 483 484 // And clean up the old task 485 onTaskRemoved(task); 486 } 487 488 if (Console.Enabled) { 489 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 490 Constants.Log.App.TimeRecentsLaunchKey, "startActivity"); 491 } 492 } 493 }; 494 495 if (Console.Enabled) { 496 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 497 Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched"); 498 } 499 500 // Launch the app right away if there is no task view, otherwise, animate the icon out first 501 if (tv == null) { 502 post(launchRunnable); 503 } else { 504 stackView.animateOnLaunchingTask(tv, launchRunnable); 505 } 506 } 507 508 @Override 509 public void onTaskAppInfoLaunched(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(); 517 } 518 519 @Override 520 public void onTaskRemoved(Task t) { 521 // Remove any stored data from the loader. We currently don't bother notifying the views 522 // that the data has been unloaded because at the point we call onTaskRemoved(), the views 523 // either don't need to be updated, or have already been removed. 524 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 525 loader.deleteTaskData(t, false); 526 527 // Remove the old task from activity manager 528 int flags = t.key.baseIntent.getFlags(); 529 boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == 530 Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 531 RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id, 532 isDocument); 533 } 534 535 @Override 536 public void onTaskStackFilterTriggered() { 537 // Hide the search bar 538 if (mSearchBar != null) { 539 mSearchBar.animate() 540 .alpha(0f) 541 .setStartDelay(0) 542 .setInterpolator(mConfig.fastOutSlowInInterpolator) 543 .setDuration(mConfig.filteringCurrentViewsAnimDuration) 544 .withLayer() 545 .start(); 546 } 547 } 548 549 @Override 550 public void onTaskStackUnfilterTriggered() { 551 // Show the search bar 552 if (mSearchBar != null) { 553 mSearchBar.animate() 554 .alpha(1f) 555 .setStartDelay(0) 556 .setInterpolator(mConfig.fastOutSlowInInterpolator) 557 .setDuration(mConfig.filteringNewViewsAnimDuration) 558 .withLayer() 559 .start(); 560 } 561 } 562 563 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 564 565 @Override 566 public void onComponentRemoved(Set<ComponentName> cns) { 567 // Propagate this event down to each task stack view 568 int childCount = getChildCount(); 569 for (int i = 0; i < childCount; i++) { 570 View child = getChildAt(i); 571 if (child instanceof TaskStackView) { 572 TaskStackView stackView = (TaskStackView) child; 573 stackView.onComponentRemoved(cns); 574 } 575 } 576 } 577} 578