blob: 08fb47b2cff986da6b68ea4c43e682b327c01135 [file] [log] [blame]
Jon Miranda228877d2021-02-09 11:05:00 -05001/*
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
17package com.android.launcher3;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.content.res.XmlResourceParser;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.util.TypedValue;
25import android.util.Xml;
26
27import org.xmlpull.v1.XmlPullParser;
28import org.xmlpull.v1.XmlPullParserException;
29
30import java.io.IOException;
31import 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
Thales Limad1df5fc2021-09-03 18:37:19 +010039 * - The space below the hotseat
Jon Miranda228877d2021-02-09 11:05:00 -050040 */
41public class DevicePaddings {
42
Thales Limad1df5fc2021-09-03 18:37:19 +010043 private static final String DEVICE_PADDINGS = "device-paddings";
44 private static final String DEVICE_PADDING = "device-padding";
Jon Miranda228877d2021-02-09 11:05:00 -050045
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
Jon Mirandac9e69fa2021-03-22 17:13:34 -040055 public DevicePaddings(Context context, int devicePaddingId) {
56 try (XmlResourceParser parser = context.getResources().getXml(devicePaddingId)) {
Jon Miranda228877d2021-02-09 11:05:00 -050057 final int depth = parser.getDepth();
58 int type;
59 while (((type = parser.next()) != XmlPullParser.END_TAG ||
60 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
Thales Limad1df5fc2021-09-03 18:37:19 +010061 if ((type == XmlPullParser.START_TAG) && DEVICE_PADDINGS.equals(parser.getName())) {
Jon Miranda228877d2021-02-09 11:05:00 -050062 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)
Thales Limad1df5fc2021-09-03 18:37:19 +010067 && DEVICE_PADDING.equals(parser.getName())) {
Jon Miranda228877d2021-02-09 11:05:00 -050068 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) {
Jon Mirandac9e69fa2021-03-22 17:13:34 -040097 if (Utilities.IS_DEBUG_DEVICE) {
98 throw new RuntimeException("DevicePadding missing padding.");
99 }
Jon Miranda228877d2021-02-09 11:05:00 -0500100 }
101
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400102 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 }
Jon Miranda228877d2021-02-09 11:05:00 -0500112 }
113 }
114 }
115 }
116 } catch (IOException | XmlPullParserException e) {
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400117 Log.e(TAG, "Failure parsing device padding layout.", e);
Jon Miranda228877d2021-02-09 11:05:00 -0500118 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
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
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400142 // 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
Jon Miranda228877d2021-02-09 11:05:00 -0500145 private final int maxEmptySpacePx;
146 private final PaddingFormula workspaceTopPadding;
147 private final PaddingFormula workspaceBottomPadding;
148 private final PaddingFormula hotseatBottomPadding;
149
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
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400160 public int getMaxEmptySpacePx() {
161 return maxEmptySpacePx;
162 }
163
Jon Miranda228877d2021-02-09 11:05:00 -0500164 public int getWorkspaceTopPadding(int extraSpacePx) {
165 return workspaceTopPadding.calculate(extraSpacePx);
166 }
167
168 public int getWorkspaceBottomPadding(int extraSpacePx) {
169 return workspaceBottomPadding.calculate(extraSpacePx);
170 }
171
172 public int getHotseatBottomPadding(int extraSpacePx) {
173 return hotseatBottomPadding.calculate(extraSpacePx);
174 }
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400175
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 }
Jon Miranda228877d2021-02-09 11:05:00 -0500191 }
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
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
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
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
232 public String toString() {
233 return "a=" + a + ", b=" + b + ", c=" + c;
234 }
235 }
236}