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.graphics.Rect; 20import com.android.systemui.recents.Constants; 21import com.android.systemui.recents.RecentsConfiguration; 22import com.android.systemui.recents.misc.Utilities; 23import com.android.systemui.recents.model.Task; 24 25import java.util.ArrayList; 26import java.util.HashMap; 27 28/* The layout logic for a TaskStackView. 29 * 30 * We are using a curve that defines the curve of the tasks as that go back in the recents list. 31 * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the 32 * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect. 33 */ 34public class TaskStackViewLayoutAlgorithm { 35 36 // These are all going to change 37 static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area 38 39 // A report of the visibility state of the stack 40 public class VisibilityReport { 41 public int numVisibleTasks; 42 public int numVisibleThumbnails; 43 44 /** Package level ctor */ 45 VisibilityReport(int tasks, int thumbnails) { 46 numVisibleTasks = tasks; 47 numVisibleThumbnails = thumbnails; 48 } 49 } 50 51 RecentsConfiguration mConfig; 52 53 // The various rects that define the stack view 54 Rect mViewRect = new Rect(); 55 Rect mStackVisibleRect = new Rect(); 56 Rect mStackRect = new Rect(); 57 Rect mTaskRect = new Rect(); 58 59 // The min/max scroll progress 60 float mMinScrollP; 61 float mMaxScrollP; 62 float mInitialScrollP; 63 int mWithinAffiliationOffset; 64 int mBetweenAffiliationOffset; 65 HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<Task.TaskKey, Float>(); 66 67 // Log function 68 static final float XScale = 1.75f; // The large the XScale, the longer the flat area of the curve 69 static final float LogBase = 3000; 70 static final int PrecisionSteps = 250; 71 static float[] xp; 72 static float[] px; 73 74 public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) { 75 mConfig = config; 76 77 // Precompute the path 78 initializeCurve(); 79 } 80 81 /** Computes the stack and task rects */ 82 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) { 83 // Compute the stack rects 84 mViewRect.set(0, 0, windowWidth, windowHeight); 85 mStackRect.set(taskStackBounds); 86 mStackVisibleRect.set(taskStackBounds); 87 mStackVisibleRect.bottom = mViewRect.bottom; 88 89 int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width()); 90 int heightPadding = mConfig.taskStackTopPaddingPx; 91 mStackRect.inset(widthPadding, heightPadding); 92 93 // Compute the task rect 94 int size = mStackRect.width(); 95 int left = mStackRect.left + (mStackRect.width() - size) / 2; 96 mTaskRect.set(left, mStackRect.top, 97 left + size, mStackRect.top + size); 98 99 // Update the affiliation offsets 100 float visibleTaskPct = 0.5f; 101 mWithinAffiliationOffset = mConfig.taskBarHeight; 102 mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height()); 103 } 104 105 /** Computes the minimum and maximum scroll progress values. This method may be called before 106 * the RecentsConfiguration is set, so we need to pass in the alt-tab state. */ 107 void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab, 108 boolean launchedFromHome) { 109 // Clear the progress map 110 mTaskProgressMap.clear(); 111 112 // Return early if we have no tasks 113 if (tasks.isEmpty()) { 114 mMinScrollP = mMaxScrollP = 0; 115 return; 116 } 117 118 // Note that we should account for the scale difference of the offsets at the screen bottom 119 int taskHeight = mTaskRect.height(); 120 float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom); 121 float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom - 122 mWithinAffiliationOffset); 123 float scale = curveProgressToScale(pWithinAffiliateTop); 124 int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2); 125 pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom - 126 mWithinAffiliationOffset + scaleYOffset); 127 float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop; 128 float pBetweenAffiliateOffset = pAtBottomOfStackRect - 129 screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset); 130 float pTaskHeightOffset = pAtBottomOfStackRect - 131 screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight); 132 float pNavBarOffset = pAtBottomOfStackRect - 133 screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - 134 mStackRect.bottom)); 135 float pDismissAllButtonOffset = 0f; 136 if (Constants.DebugFlags.App.EnableDismissAll) { 137 pDismissAllButtonOffset = pAtBottomOfStackRect - 138 screenYToCurveProgress(mStackVisibleRect.bottom - mConfig.dismissAllButtonSizePx); 139 } 140 141 // Update the task offsets 142 float pAtBackMostCardTop = 0.5f; 143 float pAtFrontMostCardTop = pAtBackMostCardTop; 144 int taskCount = tasks.size(); 145 for (int i = 0; i < taskCount; i++) { 146 Task task = tasks.get(i); 147 mTaskProgressMap.put(task.key, pAtFrontMostCardTop); 148 149 if (i < (taskCount - 1)) { 150 // Increment the peek height 151 float pPeek = task.group.isFrontMostTask(task) ? 152 pBetweenAffiliateOffset : pWithinAffiliateOffset; 153 pAtFrontMostCardTop += pPeek; 154 } 155 } 156 157 mMaxScrollP = pAtFrontMostCardTop + pDismissAllButtonOffset - 158 ((1f - pTaskHeightOffset - pNavBarOffset)); 159 mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f; 160 if (launchedWithAltTab && launchedFromHome) { 161 // Center the top most task, since that will be focused first 162 mInitialScrollP = mMaxScrollP; 163 } else { 164 mInitialScrollP = pAtFrontMostCardTop - 0.825f; 165 } 166 mInitialScrollP = Math.min(mMaxScrollP, Math.max(0, mInitialScrollP)); 167 } 168 169 /** 170 * Computes the maximum number of visible tasks and thumbnails. Requires that 171 * computeMinMaxScroll() is called first. 172 */ 173 public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) { 174 if (tasks.size() <= 1) { 175 return new VisibilityReport(1, 1); 176 } 177 178 // Walk backwards in the task stack and count the number of tasks and visible thumbnails 179 int taskHeight = mTaskRect.height(); 180 int numVisibleTasks = 1; 181 int numVisibleThumbnails = 1; 182 float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP; 183 int prevScreenY = curveProgressToScreenY(progress); 184 for (int i = tasks.size() - 2; i >= 0; i--) { 185 Task task = tasks.get(i); 186 progress = mTaskProgressMap.get(task.key) - mInitialScrollP; 187 if (progress < 0) { 188 break; 189 } 190 boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task); 191 if (isFrontMostTaskInGroup) { 192 float scaleAtP = curveProgressToScale(progress); 193 int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2); 194 int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP; 195 boolean hasVisibleThumbnail = (prevScreenY - screenY) > mConfig.taskBarHeight; 196 if (hasVisibleThumbnail) { 197 numVisibleThumbnails++; 198 numVisibleTasks++; 199 prevScreenY = screenY; 200 } else { 201 // Once we hit the next front most task that does not have a visible thumbnail, 202 // walk through remaining visible set 203 for (int j = i; j >= 0; j--) { 204 numVisibleTasks++; 205 progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP; 206 if (progress < 0) { 207 break; 208 } 209 } 210 break; 211 } 212 } else if (!isFrontMostTaskInGroup) { 213 // Affiliated task, no thumbnail 214 numVisibleTasks++; 215 } 216 } 217 return new VisibilityReport(numVisibleTasks, numVisibleThumbnails); 218 } 219 220 /** Update/get the transform */ 221 public TaskViewTransform getStackTransform(Task task, float stackScroll, 222 TaskViewTransform transformOut, TaskViewTransform prevTransform) { 223 // Return early if we have an invalid index 224 if (task == null || !mTaskProgressMap.containsKey(task.key)) { 225 transformOut.reset(); 226 return transformOut; 227 } 228 return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut, 229 prevTransform); 230 } 231 232 /** Update/get the transform */ 233 public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, 234 TaskViewTransform transformOut, TaskViewTransform prevTransform) { 235 float pTaskRelative = taskProgress - stackScroll; 236 float pBounded = Math.max(0, Math.min(pTaskRelative, 1f)); 237 // If the task top is outside of the bounds below the screen, then immediately reset it 238 if (pTaskRelative > 1f) { 239 transformOut.reset(); 240 transformOut.rect.set(mTaskRect); 241 return transformOut; 242 } 243 // The check for the top is trickier, since we want to show the next task if it is at all 244 // visible, even if p < 0. 245 if (pTaskRelative < 0f) { 246 if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) { 247 transformOut.reset(); 248 transformOut.rect.set(mTaskRect); 249 return transformOut; 250 } 251 } 252 float scale = curveProgressToScale(pBounded); 253 int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); 254 int minZ = mConfig.taskViewTranslationZMinPx; 255 int maxZ = mConfig.taskViewTranslationZMaxPx; 256 transformOut.scale = scale; 257 transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top - 258 scaleYOffset; 259 transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ))); 260 transformOut.rect.set(mTaskRect); 261 transformOut.rect.offset(0, transformOut.translationY); 262 Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); 263 transformOut.visible = true; 264 transformOut.p = pTaskRelative; 265 return transformOut; 266 } 267 268 /** Returns the untransformed task view size. */ 269 public Rect getUntransformedTaskViewSize() { 270 Rect tvSize = new Rect(mTaskRect); 271 tvSize.offsetTo(0, 0); 272 return tvSize; 273 } 274 275 /** Returns the scroll to such task top = 1f; */ 276 float getStackScrollForTask(Task t) { 277 if (!mTaskProgressMap.containsKey(t.key)) return 0f; 278 return mTaskProgressMap.get(t.key); 279 } 280 281 /** Initializes the curve. */ 282 public static void initializeCurve() { 283 if (xp != null && px != null) return; 284 xp = new float[PrecisionSteps + 1]; 285 px = new float[PrecisionSteps + 1]; 286 287 // Approximate f(x) 288 float[] fx = new float[PrecisionSteps + 1]; 289 float step = 1f / PrecisionSteps; 290 float x = 0; 291 for (int xStep = 0; xStep <= PrecisionSteps; xStep++) { 292 fx[xStep] = logFunc(x); 293 x += step; 294 } 295 // Calculate the arc length for x:1->0 296 float pLength = 0; 297 float[] dx = new float[PrecisionSteps + 1]; 298 dx[0] = 0; 299 for (int xStep = 1; xStep < PrecisionSteps; xStep++) { 300 dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2)); 301 pLength += dx[xStep]; 302 } 303 // Approximate p(x), a function of cumulative progress with x, normalized to 0..1 304 float p = 0; 305 px[0] = 0f; 306 px[PrecisionSteps] = 1f; 307 for (int xStep = 1; xStep <= PrecisionSteps; xStep++) { 308 p += Math.abs(dx[xStep] / pLength); 309 px[xStep] = p; 310 } 311 // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid 312 // function. 313 int xStep = 0; 314 p = 0; 315 xp[0] = 0f; 316 xp[PrecisionSteps] = 1f; 317 for (int pStep = 0; pStep < PrecisionSteps; pStep++) { 318 // Walk forward in px and find the x where px <= p && p < px+1 319 while (xStep < PrecisionSteps) { 320 if (px[xStep] > p) break; 321 xStep++; 322 } 323 // Now, px[xStep-1] <= p < px[xStep] 324 if (xStep == 0) { 325 xp[pStep] = 0; 326 } else { 327 // Find x such that proportionally, x is correct 328 float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]); 329 x = (xStep - 1 + fraction) * step; 330 xp[pStep] = x; 331 } 332 p += step; 333 } 334 } 335 336 /** Reverses and scales out x. */ 337 static float reverse(float x) { 338 return (-x * XScale) + 1; 339 } 340 /** The log function describing the curve. */ 341 static float logFunc(float x) { 342 return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase); 343 } 344 /** The inverse of the log function describing the curve. */ 345 float invLogFunc(float y) { 346 return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase)); 347 } 348 349 /** Converts from the progress along the curve to a screen coordinate. */ 350 int curveProgressToScreenY(float p) { 351 if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height()); 352 float pIndex = p * PrecisionSteps; 353 int pFloorIndex = (int) Math.floor(pIndex); 354 int pCeilIndex = (int) Math.ceil(pIndex); 355 float xFraction = 0; 356 if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) { 357 float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex); 358 xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction; 359 } 360 float x = xp[pFloorIndex] + xFraction; 361 return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height()); 362 } 363 364 /** Converts from the progress along the curve to a scale. */ 365 float curveProgressToScale(float p) { 366 if (p < 0) return StackPeekMinScale; 367 if (p > 1) return 1f; 368 float scaleRange = (1f - StackPeekMinScale); 369 float scale = StackPeekMinScale + (p * scaleRange); 370 return scale; 371 } 372 373 /** Converts from a screen coordinate to the progress along the curve. */ 374 float screenYToCurveProgress(int screenY) { 375 float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height(); 376 if (x < 0 || x > 1) return x; 377 float xIndex = x * PrecisionSteps; 378 int xFloorIndex = (int) Math.floor(xIndex); 379 int xCeilIndex = (int) Math.ceil(xIndex); 380 float pFraction = 0; 381 if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) { 382 float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex); 383 pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction; 384 } 385 return px[xFloorIndex] + pFraction; 386 } 387} 388