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