Matthew Ng | 43db6d2 | 2017-06-27 15:29:39 -0700 | [diff] [blame] | 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.Recents; |
| 25 | import com.android.systemui.recents.RecentsActivityLaunchState; |
| 26 | import com.android.systemui.recents.misc.Utilities; |
| 27 | import com.android.systemui.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 | |
| 54 | public TaskStackLowRamLayoutAlgorithm(Context context) { |
| 55 | reloadOnConfigurationChange(context); |
| 56 | } |
| 57 | |
| 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 | |
| 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 | |
| 80 | public void setSystemInsets(Rect systemInsets) { |
| 81 | mSystemInsets = systemInsets; |
| 82 | } |
| 83 | |
| 84 | public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) { |
| 85 | RecentsActivityLaunchState launchState = Recents.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 | |
| 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 | |
| 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 | |
| 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; |
Matthew Ng | 43db6d2 | 2017-06-27 15:29:39 -0700 | [diff] [blame] | 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 229 | public float getScrollPForTask(int taskIndex) { |
| 230 | return scrollToPercentage(getTaskTopFromIndex(taskIndex) - mPaddingEndTopBottom); |
| 231 | } |
| 232 | |
| 233 | public Rect getTaskRect() { |
| 234 | return mTaskRect; |
| 235 | } |
| 236 | |
| 237 | public float getMaxOverscroll() { |
| 238 | return MAX_OVERSCROLL; |
| 239 | } |
| 240 | |
| 241 | private int getTaskTopFromIndex(int index) { |
| 242 | return getTotalHeightOfTasks(index) - mTopOffset; |
| 243 | } |
| 244 | |
| 245 | private int getTotalHeightOfTasks(int taskCount) { |
| 246 | return taskCount * mTaskRect.height() + (taskCount + 1) * mPadding; |
| 247 | } |
| 248 | |
| 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()); |
Matthew Ng | 3a5ec52 | 2017-09-06 17:10:06 -0700 | [diff] [blame] | 257 | transformOut.rect.offset(mPaddingLeftRight + mSystemInsets.left, y); |
Matthew Ng | 43db6d2 | 2017-06-27 15:29:39 -0700 | [diff] [blame] | 258 | Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); |
| 259 | transformOut.visible = visible; |
| 260 | } |
| 261 | } |