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