• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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