RecentsView.java revision 012ef36a6c5e9745d112c734aed916cab052558c
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.provider.Settings; 30import android.util.AttributeSet; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.WindowInsets; 34import android.widget.FrameLayout; 35import com.android.systemui.recents.Constants; 36import com.android.systemui.recents.RecentsConfiguration; 37import com.android.systemui.recents.misc.Console; 38import com.android.systemui.recents.misc.SystemServicesProxy; 39import com.android.systemui.recents.misc.Utilities; 40import com.android.systemui.recents.model.RecentsPackageMonitor; 41import com.android.systemui.recents.model.RecentsTaskLoader; 42import com.android.systemui.recents.model.Task; 43import com.android.systemui.recents.model.TaskStack; 44 45import java.util.ArrayList; 46import java.util.HashSet; 47 48/** 49 * This view is the the top level layout that contains TaskStacks (which are laid out according 50 * to their SpaceNode bounds. 51 */ 52public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks, 53 RecentsPackageMonitor.PackageCallbacks { 54 55 /** The RecentsView callbacks */ 56 public interface RecentsViewCallbacks { 57 public void onTaskViewClicked(); 58 public void onAllTaskViewsDismissed(); 59 public void onExitToHomeAnimationTriggered(); 60 } 61 62 RecentsConfiguration mConfig; 63 LayoutInflater mInflater; 64 DebugOverlayView mDebugOverlay; 65 66 ArrayList<TaskStack> mStacks; 67 View mSearchBar; 68 RecentsViewCallbacks mCb; 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 124 /** Launches the focused task from the first stack if possible */ 125 public boolean launchFocusedTask() { 126 // Get the first stack view 127 int childCount = getChildCount(); 128 for (int i = 0; i < childCount; i++) { 129 View child = getChildAt(i); 130 if (child != mSearchBar) { 131 TaskStackView stackView = (TaskStackView) child; 132 TaskStack stack = stackView.mStack; 133 // Iterate the stack views and try and find the focused task 134 int taskCount = stackView.getChildCount(); 135 for (int j = 0; j < taskCount; j++) { 136 TaskView tv = (TaskView) stackView.getChildAt(j); 137 Task task = tv.getTask(); 138 if (tv.isFocusedTask()) { 139 onTaskViewClicked(stackView, tv, stack, task, false); 140 return true; 141 } 142 } 143 } 144 } 145 return false; 146 } 147 148 /** Launches the task that Recents was launched from, if possible */ 149 public boolean launchPreviousTask() { 150 // Get the first stack view 151 int childCount = getChildCount(); 152 for (int i = 0; i < childCount; i++) { 153 View child = getChildAt(i); 154 if (child != mSearchBar) { 155 TaskStackView stackView = (TaskStackView) child; 156 TaskStack stack = stackView.mStack; 157 ArrayList<Task> tasks = stack.getTasks(); 158 159 // Find the launch task in the stack 160 if (!tasks.isEmpty()) { 161 int taskCount = tasks.size(); 162 for (int j = 0; j < taskCount; j++) { 163 if (tasks.get(j).isLaunchTarget) { 164 Task task = tasks.get(j); 165 TaskView tv = stackView.getChildViewForTask(task); 166 onTaskViewClicked(stackView, tv, stack, task, false); 167 return true; 168 } 169 } 170 } 171 } 172 } 173 return false; 174 } 175 176 /** Requests all task stacks to start their enter-recents animation */ 177 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 178 int childCount = getChildCount(); 179 for (int i = 0; i < childCount; i++) { 180 View child = getChildAt(i); 181 if (child != mSearchBar) { 182 TaskStackView stackView = (TaskStackView) child; 183 stackView.startEnterRecentsAnimation(ctx); 184 } 185 } 186 } 187 188 /** Requests all task stacks to start their exit-recents animation */ 189 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 190 int childCount = getChildCount(); 191 for (int i = 0; i < childCount; i++) { 192 View child = getChildAt(i); 193 if (child != mSearchBar) { 194 TaskStackView stackView = (TaskStackView) child; 195 stackView.startExitToHomeAnimation(ctx); 196 } 197 } 198 199 // Notify of the exit animation 200 mCb.onExitToHomeAnimationTriggered(); 201 } 202 203 /** Adds the search bar */ 204 public void setSearchBar(View searchBar) { 205 // Create the search bar (and hide it if we have no recent tasks) 206 if (Constants.DebugFlags.App.EnableSearchLayout) { 207 // Remove the previous search bar if one exists 208 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 209 removeView(mSearchBar); 210 } 211 // Add the new search bar 212 if (searchBar != null) { 213 mSearchBar = searchBar; 214 addView(mSearchBar); 215 } 216 } 217 } 218 219 /** Returns whether there is currently a search bar */ 220 public boolean hasSearchBar() { 221 return mSearchBar != null; 222 } 223 224 /** Sets the visibility of the search bar */ 225 public void setSearchBarVisibility(int visibility) { 226 if (mSearchBar != null) { 227 mSearchBar.setVisibility(visibility); 228 // Always bring the search bar to the top 229 mSearchBar.bringToFront(); 230 } 231 } 232 233 /** 234 * This is called with the full size of the window since we are handling our own insets. 235 */ 236 @Override 237 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 238 int width = MeasureSpec.getSize(widthMeasureSpec); 239 int height = MeasureSpec.getSize(heightMeasureSpec); 240 241 // Get the search bar bounds and measure the search bar layout 242 if (mSearchBar != null) { 243 Rect searchBarSpaceBounds = new Rect(); 244 mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds); 245 mSearchBar.measure( 246 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 247 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 248 } 249 250 Rect taskStackBounds = new Rect(); 251 mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top, 252 mConfig.systemInsets.right, taskStackBounds); 253 254 // Measure each TaskStackView with the full width and height of the window since the 255 // transition view is a child of that stack view 256 int childCount = getChildCount(); 257 for (int i = 0; i < childCount; i++) { 258 View child = getChildAt(i); 259 if (child != mSearchBar && child.getVisibility() != GONE) { 260 TaskStackView tsv = (TaskStackView) child; 261 // Set the insets to be the top/left inset + search bounds 262 tsv.setStackInsetRect(taskStackBounds); 263 tsv.measure(widthMeasureSpec, heightMeasureSpec); 264 } 265 } 266 267 setMeasuredDimension(width, height); 268 } 269 270 /** 271 * This is called with the full size of the window since we are handling our own insets. 272 */ 273 @Override 274 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 275 // Get the search bar bounds so that we lay it out 276 if (mSearchBar != null) { 277 Rect searchBarSpaceBounds = new Rect(); 278 mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), 279 mConfig.systemInsets.top, searchBarSpaceBounds); 280 mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top, 281 searchBarSpaceBounds.right, searchBarSpaceBounds.bottom); 282 } 283 284 // Layout each TaskStackView with the full width and height of the window since the 285 // transition view is a child of that stack view 286 int childCount = getChildCount(); 287 for (int i = 0; i < childCount; i++) { 288 View child = getChildAt(i); 289 if (child != mSearchBar && child.getVisibility() != GONE) { 290 child.layout(left, top, left + child.getMeasuredWidth(), 291 top + child.getMeasuredHeight()); 292 } 293 } 294 } 295 296 @Override 297 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 298 // Update the configuration with the latest system insets and trigger a relayout 299 mConfig.updateSystemInsets(insets.getSystemWindowInsets()); 300 requestLayout(); 301 return insets.consumeSystemWindowInsets(); 302 } 303 304 /** Notifies each task view of the user interaction. */ 305 public void onUserInteraction() { 306 // Get the first stack view 307 TaskStackView stackView = null; 308 int childCount = getChildCount(); 309 for (int i = 0; i < childCount; i++) { 310 View child = getChildAt(i); 311 if (child != mSearchBar) { 312 stackView = (TaskStackView) child; 313 stackView.onUserInteraction(); 314 } 315 } 316 } 317 318 /** Focuses the next task in the first stack view */ 319 public void focusNextTask(boolean forward) { 320 // Get the first stack view 321 TaskStackView stackView = null; 322 int childCount = getChildCount(); 323 for (int i = 0; i < childCount; i++) { 324 View child = getChildAt(i); 325 if (child != mSearchBar) { 326 stackView = (TaskStackView) child; 327 break; 328 } 329 } 330 331 if (stackView != null) { 332 stackView.focusNextTask(forward); 333 } 334 } 335 336 /** Unfilters any filtered stacks */ 337 public boolean unfilterFilteredStacks() { 338 if (mStacks != null) { 339 // Check if there are any filtered stacks and unfilter them before we back out of Recents 340 boolean stacksUnfiltered = false; 341 int numStacks = mStacks.size(); 342 for (int i = 0; i < numStacks; i++) { 343 TaskStack stack = mStacks.get(i); 344 if (stack.hasFilteredTasks()) { 345 stack.unfilterTasks(); 346 stacksUnfiltered = true; 347 } 348 } 349 return stacksUnfiltered; 350 } 351 return false; 352 } 353 354 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 355 356 @Override 357 public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv, 358 final TaskStack stack, final Task task, final boolean lockToTask) { 359 // Notify any callbacks of the launching of a new task 360 if (mCb != null) { 361 mCb.onTaskViewClicked(); 362 } 363 364 // Upfront the processing of the thumbnail 365 TaskViewTransform transform = new TaskViewTransform(); 366 View sourceView = tv; 367 int offsetX = 0; 368 int offsetY = 0; 369 float stackScroll = stackView.getScroller().getStackScroll(); 370 if (tv == null) { 371 // If there is no actual task view, then use the stack view as the source view 372 // and then offset to the expected transform rect, but bound this to just 373 // outside the display rect (to ensure we don't animate from too far away) 374 sourceView = stackView; 375 transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null); 376 offsetX = transform.rect.left; 377 offsetY = Math.min(transform.rect.top, mConfig.displayRect.height()); 378 } else { 379 transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null); 380 } 381 382 // Compute the thumbnail to scale up from 383 final SystemServicesProxy ssp = 384 RecentsTaskLoader.getInstance().getSystemServicesProxy(); 385 ActivityOptions opts = null; 386 int thumbnailWidth = transform.rect.width(); 387 int thumbnailHeight = transform.rect.height(); 388 if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && 389 task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { 390 // Resize the thumbnail to the size of the view that we are animating from 391 Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, 392 Bitmap.Config.ARGB_8888); 393 Canvas c = new Canvas(b); 394 c.drawBitmap(task.thumbnail, 395 new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), 396 new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); 397 c.setBitmap(null); 398 ActivityOptions.OnAnimationStartedListener animStartedListener = null; 399 if (lockToTask) { 400 animStartedListener = new ActivityOptions.OnAnimationStartedListener() { 401 boolean mTriggered = false; 402 @Override 403 public void onAnimationStarted() { 404 if (!mTriggered) { 405 postDelayed(new Runnable() { 406 @Override 407 public void run() { 408 ssp.lockCurrentTask(); 409 } 410 }, 350); 411 mTriggered = true; 412 } 413 } 414 }; 415 } 416 opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, 417 b, offsetX, offsetY, animStartedListener); 418 } 419 420 final ActivityOptions launchOpts = opts; 421 final Runnable launchRunnable = new Runnable() { 422 @Override 423 public void run() { 424 if (task.isActive) { 425 // Bring an active task to the foreground 426 ssp.moveTaskToFront(task.key.id, launchOpts); 427 } else { 428 // Launch the activity anew with the desired animation 429 Intent i = new Intent(task.key.baseIntent); 430 i.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 431 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 432 if (!Utilities.isDocument(i)) { 433 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 434 } 435 try { 436 ssp.startActivityFromRecents(task.key.id, launchOpts); 437 if (launchOpts == null && lockToTask) { 438 ssp.lockCurrentTask(); 439 } 440 } catch (ActivityNotFoundException anfe) { 441 Console.logError(getContext(), "Could not start Activity"); 442 } 443 444 // And clean up the old task 445 onTaskViewDismissed(task); 446 } 447 } 448 }; 449 450 // Launch the app right away if there is no task view, otherwise, animate the icon out first 451 if (tv == null) { 452 post(launchRunnable); 453 } else { 454 stackView.startLaunchTaskAnimation(tv, launchRunnable); 455 } 456 } 457 458 @Override 459 public void onTaskViewAppInfoClicked(Task t) { 460 // Create a new task stack with the application info details activity 461 Intent baseIntent = t.key.baseIntent; 462 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 463 Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null)); 464 intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); 465 TaskStackBuilder.create(getContext()) 466 .addNextIntentWithParentStack(intent).startActivities(); 467 } 468 469 @Override 470 public void onTaskViewDismissed(Task t) { 471 // Remove any stored data from the loader. We currently don't bother notifying the views 472 // that the data has been unloaded because at the point we call onTaskViewDismissed(), the views 473 // either don't need to be updated, or have already been removed. 474 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 475 loader.deleteTaskData(t, false); 476 477 // Remove the old task from activity manager 478 RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id, 479 Utilities.isDocument(t.key.baseIntent)); 480 } 481 482 @Override 483 public void onAllTaskViewsDismissed() { 484 mCb.onAllTaskViewsDismissed(); 485 } 486 487 @Override 488 public void onTaskStackFilterTriggered() { 489 // Hide the search bar 490 if (mSearchBar != null) { 491 mSearchBar.animate() 492 .alpha(0f) 493 .setStartDelay(0) 494 .setInterpolator(mConfig.fastOutSlowInInterpolator) 495 .setDuration(mConfig.filteringCurrentViewsAnimDuration) 496 .withLayer() 497 .start(); 498 } 499 } 500 501 @Override 502 public void onTaskStackUnfilterTriggered() { 503 // Show the search bar 504 if (mSearchBar != null) { 505 mSearchBar.animate() 506 .alpha(1f) 507 .setStartDelay(0) 508 .setInterpolator(mConfig.fastOutSlowInInterpolator) 509 .setDuration(mConfig.filteringNewViewsAnimDuration) 510 .withLayer() 511 .start(); 512 } 513 } 514 515 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 516 517 @Override 518 public void onComponentRemoved(HashSet<ComponentName> cns) { 519 // Propagate this event down to each task stack view 520 int childCount = getChildCount(); 521 for (int i = 0; i < childCount; i++) { 522 View child = getChildAt(i); 523 if (child != mSearchBar) { 524 TaskStackView stackView = (TaskStackView) child; 525 stackView.onComponentRemoved(cns); 526 } 527 } 528 } 529} 530