1 /* 2 * Copyright (C) 2021 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.launcher3; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.content.res.XmlResourceParser; 22 import android.util.AttributeSet; 23 import android.util.Log; 24 import android.util.TypedValue; 25 import android.util.Xml; 26 27 import org.xmlpull.v1.XmlPullParser; 28 import org.xmlpull.v1.XmlPullParserException; 29 30 import java.io.IOException; 31 import java.util.ArrayList; 32 33 /** 34 * Workspace items have a fixed height, so we need a way to distribute any unused workspace height. 35 * 36 * The unused or "extra" height is allocated to three different variable heights: 37 * - The space above the workspace 38 * - The space between the workspace and hotseat 39 * - The espace below the hotseat 40 */ 41 public class DevicePaddings { 42 43 private static final String DEVICE_PADDING = "device-paddings"; 44 private static final String DEVICE_PADDINGS = "device-padding"; 45 46 private static final String WORKSPACE_TOP_PADDING = "workspaceTopPadding"; 47 private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding"; 48 private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding"; 49 50 private static final String TAG = DevicePaddings.class.getSimpleName(); 51 private static final boolean DEBUG = false; 52 53 ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>(); 54 DevicePaddings(Context context, int devicePaddingId)55 public DevicePaddings(Context context, int devicePaddingId) { 56 try (XmlResourceParser parser = context.getResources().getXml(devicePaddingId)) { 57 final int depth = parser.getDepth(); 58 int type; 59 while (((type = parser.next()) != XmlPullParser.END_TAG || 60 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 61 if ((type == XmlPullParser.START_TAG) && DEVICE_PADDING.equals(parser.getName())) { 62 final int displayDepth = parser.getDepth(); 63 while (((type = parser.next()) != XmlPullParser.END_TAG || 64 parser.getDepth() > displayDepth) 65 && type != XmlPullParser.END_DOCUMENT) { 66 if ((type == XmlPullParser.START_TAG) 67 && DEVICE_PADDINGS.equals(parser.getName())) { 68 TypedArray a = context.obtainStyledAttributes( 69 Xml.asAttributeSet(parser), R.styleable.DevicePadding); 70 int maxWidthPx = a.getDimensionPixelSize( 71 R.styleable.DevicePadding_maxEmptySpace, 0); 72 a.recycle(); 73 74 PaddingFormula workspaceTopPadding = null; 75 PaddingFormula workspaceBottomPadding = null; 76 PaddingFormula hotseatBottomPadding = null; 77 78 final int limitDepth = parser.getDepth(); 79 while (((type = parser.next()) != XmlPullParser.END_TAG || 80 parser.getDepth() > limitDepth) 81 && type != XmlPullParser.END_DOCUMENT) { 82 AttributeSet attr = Xml.asAttributeSet(parser); 83 if ((type == XmlPullParser.START_TAG)) { 84 if (WORKSPACE_TOP_PADDING.equals(parser.getName())) { 85 workspaceTopPadding = new PaddingFormula(context, attr); 86 } else if (WORKSPACE_BOTTOM_PADDING.equals(parser.getName())) { 87 workspaceBottomPadding = new PaddingFormula(context, attr); 88 } else if (HOTSEAT_BOTTOM_PADDING.equals(parser.getName())) { 89 hotseatBottomPadding = new PaddingFormula(context, attr); 90 } 91 } 92 } 93 94 if (workspaceTopPadding == null 95 || workspaceBottomPadding == null 96 || hotseatBottomPadding == null) { 97 if (Utilities.IS_DEBUG_DEVICE) { 98 throw new RuntimeException("DevicePadding missing padding."); 99 } 100 } 101 102 DevicePadding dp = new DevicePadding(maxWidthPx, workspaceTopPadding, 103 workspaceBottomPadding, hotseatBottomPadding); 104 if (dp.isValid()) { 105 mDevicePaddings.add(dp); 106 } else { 107 Log.e(TAG, "Invalid device padding found."); 108 if (Utilities.IS_DEBUG_DEVICE) { 109 throw new RuntimeException("DevicePadding is invalid"); 110 } 111 } 112 } 113 } 114 } 115 } 116 } catch (IOException | XmlPullParserException e) { 117 Log.e(TAG, "Failure parsing device padding layout.", e); 118 throw new RuntimeException(e); 119 } 120 121 // Sort ascending by maxEmptySpacePx 122 mDevicePaddings.sort((sl1, sl2) -> Integer.compare(sl1.maxEmptySpacePx, 123 sl2.maxEmptySpacePx)); 124 } 125 getDevicePadding(int extraSpacePx)126 public DevicePadding getDevicePadding(int extraSpacePx) { 127 for (DevicePadding limit : mDevicePaddings) { 128 if (extraSpacePx <= limit.maxEmptySpacePx) { 129 return limit; 130 } 131 } 132 133 return mDevicePaddings.get(mDevicePaddings.size() - 1); 134 } 135 136 /** 137 * Holds all the formulas to calculate the padding for a particular device based on the 138 * amount of extra space. 139 */ 140 public static final class DevicePadding { 141 142 // One for each padding since they can each be off by 1 due to rounding errors. 143 private static final int ROUNDING_THRESHOLD_PX = 3; 144 145 private final int maxEmptySpacePx; 146 private final PaddingFormula workspaceTopPadding; 147 private final PaddingFormula workspaceBottomPadding; 148 private final PaddingFormula hotseatBottomPadding; 149 DevicePadding(int maxEmptySpacePx, PaddingFormula workspaceTopPadding, PaddingFormula workspaceBottomPadding, PaddingFormula hotseatBottomPadding)150 public DevicePadding(int maxEmptySpacePx, 151 PaddingFormula workspaceTopPadding, 152 PaddingFormula workspaceBottomPadding, 153 PaddingFormula hotseatBottomPadding) { 154 this.maxEmptySpacePx = maxEmptySpacePx; 155 this.workspaceTopPadding = workspaceTopPadding; 156 this.workspaceBottomPadding = workspaceBottomPadding; 157 this.hotseatBottomPadding = hotseatBottomPadding; 158 } 159 getMaxEmptySpacePx()160 public int getMaxEmptySpacePx() { 161 return maxEmptySpacePx; 162 } 163 getWorkspaceTopPadding(int extraSpacePx)164 public int getWorkspaceTopPadding(int extraSpacePx) { 165 return workspaceTopPadding.calculate(extraSpacePx); 166 } 167 getWorkspaceBottomPadding(int extraSpacePx)168 public int getWorkspaceBottomPadding(int extraSpacePx) { 169 return workspaceBottomPadding.calculate(extraSpacePx); 170 } 171 getHotseatBottomPadding(int extraSpacePx)172 public int getHotseatBottomPadding(int extraSpacePx) { 173 return hotseatBottomPadding.calculate(extraSpacePx); 174 } 175 isValid()176 public boolean isValid() { 177 int workspaceTopPadding = getWorkspaceTopPadding(maxEmptySpacePx); 178 int workspaceBottomPadding = getWorkspaceBottomPadding(maxEmptySpacePx); 179 int hotseatBottomPadding = getHotseatBottomPadding(maxEmptySpacePx); 180 int sum = workspaceTopPadding + workspaceBottomPadding + hotseatBottomPadding; 181 int diff = Math.abs(sum - maxEmptySpacePx); 182 if (DEBUG) { 183 Log.d(TAG, "isValid: workspaceTopPadding=" + workspaceTopPadding 184 + ", workspaceBottomPadding=" + workspaceBottomPadding 185 + ", hotseatBottomPadding=" + hotseatBottomPadding 186 + ", sum=" + sum 187 + ", diff=" + diff); 188 } 189 return diff <= ROUNDING_THRESHOLD_PX; 190 } 191 } 192 193 /** 194 * Used to calculate a padding based on three variables: a, b, and c. 195 * 196 * Calculation: a * (extraSpace - c) + b 197 */ 198 private static final class PaddingFormula { 199 200 private final float a; 201 private final float b; 202 private final float c; 203 PaddingFormula(Context context, AttributeSet attrs)204 public PaddingFormula(Context context, AttributeSet attrs) { 205 TypedArray t = context.obtainStyledAttributes(attrs, 206 R.styleable.DevicePaddingFormula); 207 208 a = getValue(t, R.styleable.DevicePaddingFormula_a); 209 b = getValue(t, R.styleable.DevicePaddingFormula_b); 210 c = getValue(t, R.styleable.DevicePaddingFormula_c); 211 212 t.recycle(); 213 } 214 calculate(int extraSpacePx)215 public int calculate(int extraSpacePx) { 216 if (DEBUG) { 217 Log.d(TAG, "a=" + a + " * (" + extraSpacePx + " - " + c + ") + b=" + b); 218 } 219 return Math.round(a * (extraSpacePx - c) + b); 220 } 221 getValue(TypedArray a, int index)222 private static float getValue(TypedArray a, int index) { 223 if (a.getType(index) == TypedValue.TYPE_DIMENSION) { 224 return a.getDimensionPixelSize(index, 0); 225 } else if (a.getType(index) == TypedValue.TYPE_FLOAT) { 226 return a.getFloat(index, 0); 227 } 228 return 0; 229 } 230 231 @Override toString()232 public String toString() { 233 return "a=" + a + ", b=" + b + ", c=" + c; 234 } 235 } 236 } 237