RecentsView.java revision 9f49df933f01a32d04bdf92d53c943065aa8ddf7
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.widget.FrameLayout; 34import com.android.systemui.recents.Console; 35import com.android.systemui.recents.Constants; 36import com.android.systemui.recents.RecentsConfiguration; 37import com.android.systemui.recents.RecentsPackageMonitor; 38import com.android.systemui.recents.RecentsTaskLoader; 39import com.android.systemui.recents.model.SpaceNode; 40import com.android.systemui.recents.model.Task; 41import com.android.systemui.recents.model.TaskStack; 42 43import java.util.ArrayList; 44import java.util.Set; 45 46 47/** 48 * This view is the the top level layout that contains TaskStacks (which are laid out according 49 * to their SpaceNode bounds. 50 */ 51public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks, 52 RecentsPackageMonitor.PackageCallbacks { 53 54 /** The RecentsView callbacks */ 55 public interface RecentsViewCallbacks { 56 public void onTaskLaunching(); 57 } 58 59 // The space partitioning root of this container 60 SpaceNode mBSP; 61 // Whether there are any tasks 62 boolean mHasTasks; 63 // Search bar view 64 View mSearchBar; 65 // Recents view callbacks 66 RecentsViewCallbacks mCb; 67 68 LayoutInflater mInflater; 69 70 public RecentsView(Context context) { 71 super(context); 72 mInflater = LayoutInflater.from(context); 73 setWillNotDraw(false); 74 } 75 76 /** Sets the callbacks */ 77 public void setCallbacks(RecentsViewCallbacks cb) { 78 mCb = cb; 79 } 80 81 /** Set/get the bsp root node */ 82 public void setBSP(SpaceNode n) { 83 mBSP = n; 84 85 // Create and add all the stacks for this partition of space. 86 mHasTasks = false; 87 removeAllViews(); 88 ArrayList<TaskStack> stacks = mBSP.getStacks(); 89 for (TaskStack stack : stacks) { 90 TaskStackView stackView = new TaskStackView(getContext(), stack); 91 stackView.setCallbacks(this); 92 addView(stackView); 93 mHasTasks |= (stack.getTaskCount() > 0); 94 } 95 } 96 97 /** Launches the first task from the first stack if possible */ 98 public boolean launchFirstTask() { 99 // Get the first stack view 100 int childCount = getChildCount(); 101 for (int i = 0; i < childCount; i++) { 102 View child = getChildAt(i); 103 if (child instanceof TaskStackView) { 104 TaskStackView stackView = (TaskStackView) child; 105 TaskStack stack = stackView.mStack; 106 ArrayList<Task> tasks = stack.getTasks(); 107 108 // Get the first task in the stack 109 if (!tasks.isEmpty()) { 110 Task task = tasks.get(tasks.size() - 1); 111 TaskView tv = null; 112 113 // Try and use the first child task view as the source of the launch animation 114 if (stackView.getChildCount() > 0) { 115 TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1); 116 if (stv.getTask() == task) { 117 tv = stv; 118 } 119 } 120 onTaskLaunched(stackView, tv, stack, task); 121 return true; 122 } 123 } 124 } 125 return false; 126 } 127 128 /** Adds the search bar */ 129 public void setSearchBar(View searchBar) { 130 // Create the search bar (and hide it if we have no recent tasks) 131 if (Constants.DebugFlags.App.EnableSearchLayout) { 132 // Remove the previous search bar if one exists 133 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 134 removeView(mSearchBar); 135 } 136 // Add the new search bar 137 if (searchBar != null) { 138 mSearchBar = searchBar; 139 mSearchBar.setVisibility(mHasTasks ? View.VISIBLE : View.GONE); 140 addView(mSearchBar); 141 142 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsView|setSearchBar]", 143 "" + (mSearchBar.getVisibility() == View.VISIBLE), 144 Console.AnsiBlue); 145 } 146 } 147 } 148 149 /** 150 * This is called with the full size of the window since we are handling our own insets. 151 */ 152 @Override 153 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 154 int width = MeasureSpec.getSize(widthMeasureSpec); 155 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 156 int height = MeasureSpec.getSize(heightMeasureSpec); 157 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 158 159 Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|measure]", 160 "width: " + width + " height: " + height, Console.AnsiGreen); 161 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 162 Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onMeasure"); 163 164 // Get the search bar bounds and measure the search bar layout 165 RecentsConfiguration config = RecentsConfiguration.getInstance(); 166 if (mSearchBar != null) { 167 Rect searchBarSpaceBounds = new Rect(); 168 config.getSearchBarBounds(width, height - config.systemInsets.top, searchBarSpaceBounds); 169 mSearchBar.measure( 170 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 171 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 172 } 173 174 // We give the full width of the space, not including the right nav bar insets in landscape, 175 // to the stack view, since we want the tasks to render under the search bar in landscape. 176 // In addition, we give it the full height, not including the top inset or search bar space, 177 // since we want the tasks to render under the navigation buttons in portrait. 178 Rect taskStackBounds = new Rect(); 179 config.getTaskStackBounds(width, height, taskStackBounds); 180 int childWidth = width - config.systemInsets.right; 181 int childHeight = taskStackBounds.height() - config.systemInsets.top; 182 183 // Measure each TaskStackView 184 int childCount = getChildCount(); 185 for (int i = 0; i < childCount; i++) { 186 View child = getChildAt(i); 187 if (child instanceof TaskStackView && child.getVisibility() != GONE) { 188 child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode), 189 MeasureSpec.makeMeasureSpec(childHeight, heightMode)); 190 } 191 } 192 193 setMeasuredDimension(width, height); 194 } 195 196 /** 197 * This is called with the full size of the window since we are handling our own insets. 198 */ 199 @Override 200 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 201 Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|layout]", 202 new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen); 203 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 204 Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onLayout"); 205 206 // Get the search bar bounds so that we lay it out 207 RecentsConfiguration config = RecentsConfiguration.getInstance(); 208 if (mSearchBar != null) { 209 Rect searchBarSpaceBounds = new Rect(); 210 config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds); 211 mSearchBar.layout(config.systemInsets.left + searchBarSpaceBounds.left, 212 config.systemInsets.top + searchBarSpaceBounds.top, 213 config.systemInsets.left + mSearchBar.getMeasuredWidth(), 214 config.systemInsets.top + mSearchBar.getMeasuredHeight()); 215 } 216 217 // We offset the stack view by the left inset (if any), but lay it out under the search bar. 218 // In addition, we offset our stack views by the top inset and search bar height, but not 219 // the bottom insets because we want it to render under the navigation buttons. 220 Rect taskStackBounds = new Rect(); 221 config.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds); 222 left += config.systemInsets.left; 223 top += config.systemInsets.top + taskStackBounds.top; 224 225 // Layout each child 226 // XXX: Based on the space node for that task view 227 int childCount = getChildCount(); 228 for (int i = 0; i < childCount; i++) { 229 View child = getChildAt(i); 230 if (child instanceof TaskStackView && child.getVisibility() != GONE) { 231 TaskStackView tsv = (TaskStackView) child; 232 child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight()); 233 } 234 } 235 } 236 237 @Override 238 protected void dispatchDraw(Canvas canvas) { 239 Console.log(Constants.Log.UI.Draw, "[RecentsView|dispatchDraw]", "", 240 Console.AnsiPurple); 241 super.dispatchDraw(canvas); 242 } 243 244 @Override 245 protected boolean fitSystemWindows(Rect insets) { 246 Console.log(Constants.Log.UI.MeasureAndLayout, 247 "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen); 248 249 // Update the configuration with the latest system insets and trigger a relayout 250 RecentsConfiguration config = RecentsConfiguration.getInstance(); 251 config.updateSystemInsets(insets); 252 requestLayout(); 253 254 return true; 255 } 256 257 /** Closes any open info panes */ 258 public boolean closeOpenInfoPanes() { 259 if (mBSP != null) { 260 // Get the first stack view 261 int childCount = getChildCount(); 262 for (int i = 0; i < childCount; i++) { 263 View child = getChildAt(i); 264 if (child instanceof TaskStackView) { 265 TaskStackView stackView = (TaskStackView) child; 266 if (stackView.closeOpenInfoPanes()) { 267 return true; 268 } 269 } 270 } 271 } 272 return false; 273 } 274 275 /** Unfilters any filtered stacks */ 276 public boolean unfilterFilteredStacks() { 277 if (mBSP != null) { 278 // Check if there are any filtered stacks and unfilter them before we back out of Recents 279 boolean stacksUnfiltered = false; 280 ArrayList<TaskStack> stacks = mBSP.getStacks(); 281 for (TaskStack stack : stacks) { 282 if (stack.hasFilteredTasks()) { 283 stack.unfilterTasks(); 284 stacksUnfiltered = true; 285 } 286 } 287 return stacksUnfiltered; 288 } 289 return false; 290 } 291 292 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 293 294 @Override 295 public void onTaskLaunched(final TaskStackView stackView, final TaskView tv, 296 final TaskStack stack, final Task task) { 297 // Notify any callbacks of the launching of a new task 298 if (mCb != null) { 299 mCb.onTaskLaunching(); 300 } 301 302 // Close any open info panes 303 closeOpenInfoPanes(); 304 305 final Runnable launchRunnable = new Runnable() { 306 @Override 307 public void run() { 308 TaskViewTransform transform; 309 View sourceView = tv; 310 int offsetX = 0; 311 int offsetY = 0; 312 int stackScroll = stackView.getStackScroll(); 313 if (tv == null) { 314 // If there is no actual task view, then use the stack view as the source view 315 // and then offset to the expected transform rect, but bound this to just 316 // outside the display rect (to ensure we don't animate from too far away) 317 RecentsConfiguration config = RecentsConfiguration.getInstance(); 318 sourceView = stackView; 319 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); 320 offsetX = transform.rect.left; 321 offsetY = Math.min(transform.rect.top, config.displayRect.height()); 322 } else { 323 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); 324 } 325 326 // Compute the thumbnail to scale up from 327 ActivityOptions opts = null; 328 int thumbnailWidth = transform.rect.width(); 329 int thumbnailHeight = transform.rect.height(); 330 if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && 331 task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { 332 // Resize the thumbnail to the size of the view that we are animating from 333 Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, 334 Bitmap.Config.ARGB_8888); 335 Canvas c = new Canvas(b); 336 c.drawBitmap(task.thumbnail, 337 new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), 338 new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); 339 c.setBitmap(null); 340 opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, 341 b, offsetX, offsetY); 342 } 343 344 if (task.isActive) { 345 // Bring an active task to the foreground 346 RecentsTaskLoader.getInstance().getSystemServicesProxy() 347 .moveTaskToFront(task.key.id, opts); 348 } else { 349 // Launch the activity with the desired animation 350 Intent i = new Intent(task.key.baseIntent); 351 i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 352 | Intent.FLAG_ACTIVITY_TASK_ON_HOME 353 | Intent.FLAG_ACTIVITY_NEW_TASK); 354 try { 355 UserHandle taskUser = new UserHandle(task.userId); 356 if (opts != null) { 357 getContext().startActivityAsUser(i, opts.toBundle(), taskUser); 358 } else { 359 getContext().startActivityAsUser(i, taskUser); 360 } 361 } catch (ActivityNotFoundException anfe) { 362 Console.logError(getContext(), "Could not start Activity"); 363 } 364 } 365 366 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 367 Constants.Log.App.TimeRecentsLaunchKey, "startActivity"); 368 } 369 }; 370 371 Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, 372 Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched"); 373 374 // Launch the app right away if there is no task view, otherwise, animate the icon out first 375 if (tv == null) { 376 post(launchRunnable); 377 } else { 378 tv.animateOnLeavingRecents(launchRunnable); 379 } 380 } 381 382 @Override 383 public void onTaskAppInfoLaunched(Task t) { 384 // Create a new task stack with the application info details activity 385 Intent baseIntent = t.key.baseIntent; 386 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 387 Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null)); 388 intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); 389 TaskStackBuilder.create(getContext()) 390 .addNextIntentWithParentStack(intent).startActivities(); 391 } 392 393 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 394 395 @Override 396 public void onComponentRemoved(Set<ComponentName> cns) { 397 // Propagate this event down to each task stack view 398 int childCount = getChildCount(); 399 for (int i = 0; i < childCount; i++) { 400 View child = getChildAt(i); 401 if (child instanceof TaskStackView) { 402 TaskStackView stackView = (TaskStackView) child; 403 stackView.onComponentRemoved(cns); 404 } 405 } 406 } 407} 408