1 /* 2 * Copyright (C) 2017 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.lowram; 18 19 import android.content.Context; 20 import android.graphics.Rect; 21 import android.view.ViewConfiguration; 22 23 import com.android.systemui.R; 24 import com.android.systemui.recents.LegacyRecentsImpl; 25 import com.android.systemui.recents.RecentsActivityLaunchState; 26 import com.android.systemui.recents.utilities.Utilities; 27 import com.android.systemui.shared.recents.model.Task; 28 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; 29 import com.android.systemui.recents.views.TaskViewTransform; 30 31 import java.util.ArrayList; 32 33 import static com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport; 34 35 public class TaskStackLowRamLayoutAlgorithm { 36 37 private static final String TAG = "TaskStackLowRamLayoutAlgorithm"; 38 private static final float MAX_OVERSCROLL = 0.2f / 0.3f; 39 40 public static final int MAX_LAYOUT_TASK_COUNT = 9; 41 public static final int NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME = 2; 42 public static final int NUM_TASK_VISIBLE_LAUNCHED_FROM_APP = 43 NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME + 1; 44 private Rect mWindowRect; 45 46 private int mFlingThreshold; 47 private int mPadding; 48 private int mPaddingLeftRight; 49 private int mTopOffset; 50 private int mPaddingEndTopBottom; 51 private Rect mTaskRect = new Rect(); 52 private Rect mSystemInsets = new Rect(); 53 TaskStackLowRamLayoutAlgorithm(Context context)54 public TaskStackLowRamLayoutAlgorithm(Context context) { 55 reloadOnConfigurationChange(context); 56 } 57 reloadOnConfigurationChange(Context context)58 public void reloadOnConfigurationChange(Context context) { 59 mPadding = context.getResources() 60 .getDimensionPixelSize(R.dimen.recents_layout_side_margin_phone); 61 mFlingThreshold = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); 62 } 63 initialize(Rect windowRect)64 public void initialize(Rect windowRect) { 65 mWindowRect = windowRect; 66 if (mWindowRect.height() > 0) { 67 int windowHeight = mWindowRect.height() - mSystemInsets.bottom; 68 int windowWidth = mWindowRect.width() - mSystemInsets.right - mSystemInsets.left; 69 int width = Math.min(windowWidth, windowHeight) - mPadding * 2; 70 boolean isLandscape = windowWidth > windowHeight; 71 mTaskRect.set(0, 0, width, isLandscape ? width * 2 / 3 : width); 72 mPaddingLeftRight = (windowWidth - mTaskRect.width()) / 2; 73 mPaddingEndTopBottom = (windowHeight - mTaskRect.height()) / 2; 74 75 // Compute the top offset to center tasks in the middle of the screen 76 mTopOffset = (getTotalHeightOfTasks(MAX_LAYOUT_TASK_COUNT) - windowHeight) / 2; 77 } 78 } 79 setSystemInsets(Rect systemInsets)80 public void setSystemInsets(Rect systemInsets) { 81 mSystemInsets = systemInsets; 82 } 83 computeStackVisibilityReport(ArrayList<Task> tasks)84 public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) { 85 RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState(); 86 int maxVisible = launchState.launchedFromHome || launchState.launchedFromPipApp 87 || launchState.launchedWithNextPipApp 88 ? NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME 89 : NUM_TASK_VISIBLE_LAUNCHED_FROM_APP; 90 int visibleCount = Math.min(maxVisible, tasks.size()); 91 return new VisibilityReport(visibleCount, visibleCount); 92 } 93 getFrontOfStackTransform(TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout)94 public void getFrontOfStackTransform(TaskViewTransform transformOut, 95 TaskStackLayoutAlgorithm stackLayout) { 96 if (mWindowRect == null) { 97 transformOut.reset(); 98 return; 99 } 100 101 // Calculate the static task y position 2 tasks after/below the middle/current task 102 int windowHeight = mWindowRect.height() - mSystemInsets.bottom; 103 int bottomOfCurrentTask = (windowHeight + mTaskRect.height()) / 2; 104 int y = bottomOfCurrentTask + mTaskRect.height() + mPadding * 2; 105 fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, true); 106 } 107 getBackOfStackTransform(TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout)108 public void getBackOfStackTransform(TaskViewTransform transformOut, 109 TaskStackLayoutAlgorithm stackLayout) { 110 if (mWindowRect == null) { 111 transformOut.reset(); 112 return; 113 } 114 115 // Calculate the static task y position 2 tasks before/above the middle/current task 116 int windowHeight = mWindowRect.height() - mSystemInsets.bottom; 117 int topOfCurrentTask = (windowHeight - mTaskRect.height()) / 2; 118 int y = topOfCurrentTask - (mTaskRect.height() + mPadding) * 2; 119 fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, true); 120 } 121 getTransform(int taskIndex, float stackScroll, TaskViewTransform transformOut, int taskCount, TaskStackLayoutAlgorithm stackLayout)122 public TaskViewTransform getTransform(int taskIndex, float stackScroll, 123 TaskViewTransform transformOut, int taskCount, TaskStackLayoutAlgorithm stackLayout) { 124 if (taskCount == 0) { 125 transformOut.reset(); 126 return transformOut; 127 } 128 boolean visible = true; 129 int y; 130 if (taskCount > 1) { 131 y = getTaskTopFromIndex(taskIndex) - percentageToScroll(stackScroll); 132 133 // Check visibility from the bottom of the task 134 visible = y + mPadding + getTaskRect().height() > 0; 135 } else { 136 int windowHeight = mWindowRect.height() - mSystemInsets.bottom; 137 y = (windowHeight - mTaskRect.height()) / 2 - percentageToScroll(stackScroll); 138 } 139 fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, visible); 140 return transformOut; 141 } 142 143 /** 144 * Finds the closest task to the scroll percentage in the y axis and returns the percentage of 145 * the task to scroll to. 146 * @param scrollP percentage to find nearest to 147 * @param numTasks number of tasks in recents stack 148 * @param velocity speed of fling 149 */ getClosestTaskP(float scrollP, int numTasks, int velocity)150 public float getClosestTaskP(float scrollP, int numTasks, int velocity) { 151 int y = percentageToScroll(scrollP); 152 153 int lastY = getTaskTopFromIndex(0) - mPaddingEndTopBottom; 154 for (int i = 1; i < numTasks; i++) { 155 int taskY = getTaskTopFromIndex(i) - mPaddingEndTopBottom; 156 int diff = taskY - y; 157 if (diff > 0) { 158 int diffPrev = Math.abs(y - lastY); 159 boolean useNext = diff > diffPrev; 160 if (Math.abs(velocity) > mFlingThreshold) { 161 useNext = velocity > 0; 162 } 163 return useNext 164 ? scrollToPercentage(lastY) : scrollToPercentage(taskY); 165 } 166 lastY = taskY; 167 } 168 return scrollToPercentage(lastY); 169 } 170 171 /** 172 * Convert a scroll value to a percentage 173 * @param scroll a scroll value 174 * @return a percentage that represents the scroll from the total height of tasks 175 */ scrollToPercentage(int scroll)176 public float scrollToPercentage(int scroll) { 177 return (float) scroll / (mTaskRect.height() + mPadding); 178 } 179 180 /** 181 * Converts a percentage to the scroll value from the total height of tasks 182 * @param p a percentage that represents the scroll value 183 * @return a scroll value in pixels 184 */ percentageToScroll(float p)185 public int percentageToScroll(float p) { 186 return (int) (p * (mTaskRect.height() + mPadding)); 187 } 188 189 /** 190 * Get the min scroll progress for low ram layout. This computes the top position of the 191 * first task and reduce by the end padding to center the first task 192 * @return position of max scroll 193 */ getMinScrollP()194 public float getMinScrollP() { 195 return getScrollPForTask(0); 196 } 197 198 /** 199 * Get the max scroll progress for low ram layout. This computes the top position of the last 200 * task and reduce by the end padding to center the last task 201 * @param taskCount the amount of tasks in the recents stack 202 * @return position of max scroll 203 */ getMaxScrollP(int taskCount)204 public float getMaxScrollP(int taskCount) { 205 return getScrollPForTask(taskCount - 1); 206 } 207 208 /** 209 * Get the initial scroll value whether launched from home or from an app. 210 * @param taskCount the amount of tasks currently in recents 211 * @param fromHome if launching recents from home or not 212 * @return from home it will return max value and from app it will return 2nd last task 213 */ getInitialScrollP(int taskCount, boolean fromHome)214 public float getInitialScrollP(int taskCount, boolean fromHome) { 215 if (fromHome) { 216 return getMaxScrollP(taskCount); 217 } 218 if (taskCount < 2) { 219 return 0; 220 } 221 return getScrollPForTask(taskCount - 2); 222 } 223 224 /** 225 * Get the scroll progress for any task 226 * @param taskIndex task index to get the scroll progress of 227 * @return scroll progress of task 228 */ getScrollPForTask(int taskIndex)229 public float getScrollPForTask(int taskIndex) { 230 return scrollToPercentage(getTaskTopFromIndex(taskIndex) - mPaddingEndTopBottom); 231 } 232 getTaskRect()233 public Rect getTaskRect() { 234 return mTaskRect; 235 } 236 getMaxOverscroll()237 public float getMaxOverscroll() { 238 return MAX_OVERSCROLL; 239 } 240 getTaskTopFromIndex(int index)241 private int getTaskTopFromIndex(int index) { 242 return getTotalHeightOfTasks(index) - mTopOffset; 243 } 244 getTotalHeightOfTasks(int taskCount)245 private int getTotalHeightOfTasks(int taskCount) { 246 return taskCount * mTaskRect.height() + (taskCount + 1) * mPadding; 247 } 248 fillStackTransform(TaskViewTransform transformOut, int y, int translationZ, boolean visible)249 private void fillStackTransform(TaskViewTransform transformOut, int y, int translationZ, 250 boolean visible) { 251 transformOut.scale = 1f; 252 transformOut.alpha = 1f; 253 transformOut.translationZ = translationZ; 254 transformOut.dimAlpha = 0f; 255 transformOut.viewOutlineAlpha = 1f; 256 transformOut.rect.set(getTaskRect()); 257 transformOut.rect.offset(mPaddingLeftRight + mSystemInsets.left, y); 258 Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); 259 transformOut.visible = visible; 260 } 261 } 262