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