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