RecentsView.java revision c6a1623cc48581380b698ae87b43bfafb9c935ba
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.ActivityManager; 20import android.app.ActivityOptions; 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.os.UserHandle; 28import android.view.View; 29import android.widget.FrameLayout; 30import com.android.systemui.recents.Console; 31import com.android.systemui.recents.Constants; 32import com.android.systemui.recents.RecentsConfiguration; 33import com.android.systemui.recents.model.SpaceNode; 34import com.android.systemui.recents.model.Task; 35import com.android.systemui.recents.model.TaskStack; 36 37import java.util.ArrayList; 38 39 40/** 41 * This view is the the top level layout that contains TaskStacks (which are laid out according 42 * to their SpaceNode bounds. 43 */ 44public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks { 45 46 /** The RecentsView callbacks */ 47 public interface RecentsViewCallbacks { 48 public void onTaskLaunching(); 49 } 50 51 // The space partitioning root of this container 52 SpaceNode mBSP; 53 // Recents view callbacks 54 RecentsViewCallbacks mCb; 55 56 public RecentsView(Context context) { 57 super(context); 58 setWillNotDraw(false); 59 } 60 61 /** Sets the callbacks */ 62 public void setCallbacks(RecentsViewCallbacks cb) { 63 mCb = cb; 64 } 65 66 /** Set/get the bsp root node */ 67 public void setBSP(SpaceNode n) { 68 mBSP = n; 69 70 // Create and add all the stacks for this partition of space. 71 removeAllViews(); 72 ArrayList<TaskStack> stacks = mBSP.getStacks(); 73 for (TaskStack stack : stacks) { 74 TaskStackView stackView = new TaskStackView(getContext(), stack); 75 stackView.setCallbacks(this); 76 addView(stackView); 77 } 78 } 79 80 /** Launches the first task from the first stack if possible */ 81 public boolean launchFirstTask() { 82 // Get the first stack view 83 int childCount = getChildCount(); 84 for (int i = 0; i < childCount; i++) { 85 TaskStackView stackView = (TaskStackView) getChildAt(i); 86 TaskStack stack = stackView.mStack; 87 ArrayList<Task> tasks = stack.getTasks(); 88 89 // Get the first task in the stack 90 if (!tasks.isEmpty()) { 91 Task task = tasks.get(tasks.size() - 1); 92 TaskView tv = null; 93 94 // Try and use the first child task view as the source of the launch animation 95 if (stackView.getChildCount() > 0) { 96 TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1); 97 if (stv.getTask() == task) { 98 tv = stv; 99 } 100 } 101 onTaskLaunched(stackView, tv, stack, task); 102 return true; 103 } 104 } 105 return false; 106 } 107 108 @Override 109 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 110 int width = MeasureSpec.getSize(widthMeasureSpec); 111 int height = MeasureSpec.getSize(heightMeasureSpec); 112 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 113 114 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|measure]", 115 "width: " + width + " height: " + height, Console.AnsiGreen); 116 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 117 Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onMeasure"); 118 119 // We measure our stack views sans the status bar. It will handle the nav bar itself. 120 RecentsConfiguration config = RecentsConfiguration.getInstance(); 121 int childHeight = height - config.systemInsets.top; 122 123 // Measure each child 124 int childCount = getChildCount(); 125 for (int i = 0; i < childCount; i++) { 126 final View child = getChildAt(i); 127 if (child.getVisibility() != GONE) { 128 child.measure(widthMeasureSpec, 129 MeasureSpec.makeMeasureSpec(childHeight, heightMode)); 130 } 131 } 132 133 setMeasuredDimension(width, height); 134 } 135 136 @Override 137 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 138 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|layout]", 139 new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen); 140 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 141 Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onLayout"); 142 143 // We offset our stack views by the status bar height. It will handle the nav bar itself. 144 RecentsConfiguration config = RecentsConfiguration.getInstance(); 145 top += config.systemInsets.top; 146 147 // Layout each child 148 // XXX: Based on the space node for that task view 149 int childCount = getChildCount(); 150 for (int i = 0; i < childCount; i++) { 151 final View child = getChildAt(i); 152 if (child.getVisibility() != GONE) { 153 final int width = child.getMeasuredWidth(); 154 final int height = child.getMeasuredHeight(); 155 child.layout(left, top, left + width, top + height); 156 } 157 } 158 } 159 160 @Override 161 protected void dispatchDraw(Canvas canvas) { 162 Console.log(Constants.DebugFlags.UI.Draw, "[RecentsView|dispatchDraw]", "", 163 Console.AnsiPurple); 164 super.dispatchDraw(canvas); 165 } 166 167 @Override 168 protected boolean fitSystemWindows(Rect insets) { 169 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, 170 "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen); 171 172 // Update the configuration with the latest system insets and trigger a relayout 173 RecentsConfiguration config = RecentsConfiguration.getInstance(); 174 config.updateSystemInsets(insets); 175 requestLayout(); 176 177 return true; 178 } 179 180 /** Unfilters any filtered stacks */ 181 public boolean unfilterFilteredStacks() { 182 if (mBSP != null) { 183 // Check if there are any filtered stacks and unfilter them before we back out of Recents 184 boolean stacksUnfiltered = false; 185 ArrayList<TaskStack> stacks = mBSP.getStacks(); 186 for (TaskStack stack : stacks) { 187 if (stack.hasFilteredTasks()) { 188 stack.unfilterTasks(); 189 stacksUnfiltered = true; 190 } 191 } 192 return stacksUnfiltered; 193 } 194 return false; 195 } 196 197 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 198 199 @Override 200 public void onTaskLaunched(final TaskStackView stackView, final TaskView tv, 201 final TaskStack stack, final Task task) { 202 // Notify any callbacks of the launching of a new task 203 if (mCb != null) { 204 mCb.onTaskLaunching(); 205 } 206 207 final Runnable launchRunnable = new Runnable() { 208 @Override 209 public void run() { 210 TaskViewTransform transform; 211 View sourceView = tv; 212 int offsetX = 0; 213 int offsetY = 0; 214 int stackScroll = stackView.getStackScroll(); 215 if (tv == null) { 216 // If there is no actual task view, then use the stack view as the source view 217 // and then offset to the expected transform rect, but bound this to just 218 // outside the display rect (to ensure we don't animate from too far away) 219 RecentsConfiguration config = RecentsConfiguration.getInstance(); 220 sourceView = stackView; 221 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); 222 offsetX = transform.rect.left; 223 offsetY = Math.min(transform.rect.top, config.displayRect.height()); 224 } else { 225 transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); 226 } 227 228 // Compute the thumbnail to scale up from 229 ActivityOptions opts = null; 230 int thumbnailWidth = transform.rect.width(); 231 int thumbnailHeight = transform.rect.height(); 232 if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && 233 task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { 234 // Resize the thumbnail to the size of the view that we are animating from 235 Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, 236 Bitmap.Config.ARGB_8888); 237 Canvas c = new Canvas(b); 238 c.drawBitmap(task.thumbnail, 239 new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), 240 new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); 241 c.setBitmap(null); 242 opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, 243 b, offsetX, offsetY); 244 } 245 246 247 if (task.isActive) { 248 // Bring an active task to the foreground 249 ActivityManager am = (ActivityManager) 250 stackView.getContext().getSystemService(Context.ACTIVITY_SERVICE); 251 if (opts != null) { 252 am.moveTaskToFront(task.key.id, ActivityManager.MOVE_TASK_WITH_HOME, 253 opts.toBundle()); 254 } else { 255 am.moveTaskToFront(task.key.id, ActivityManager.MOVE_TASK_WITH_HOME); 256 } 257 } else { 258 // Launch the activity with the desired animation 259 Intent i = new Intent(task.key.baseIntent); 260 i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 261 | Intent.FLAG_ACTIVITY_TASK_ON_HOME 262 | Intent.FLAG_ACTIVITY_NEW_TASK); 263 try { 264 if (opts != null) { 265 getContext().startActivityAsUser(i, opts.toBundle(), 266 UserHandle.CURRENT); 267 } else { 268 getContext().startActivityAsUser(i, UserHandle.CURRENT); 269 } 270 } catch (ActivityNotFoundException anfe) { 271 Console.logError(getContext(), "Could not start Activity"); 272 } 273 } 274 275 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 276 Constants.DebugFlags.App.TimeRecentsLaunchKey, "startActivity"); 277 } 278 }; 279 280 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 281 Constants.DebugFlags.App.TimeRecentsLaunchKey, "onTaskLaunched"); 282 283 // Launch the app right away if there is no task view, otherwise, animate the icon out first 284 if (tv == null || !Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingRecents) { 285 post(launchRunnable); 286 } else { 287 tv.animateOnLeavingRecents(launchRunnable); 288 } 289 } 290} 291