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