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