summaryrefslogtreecommitdiff
path: root/src/com/android/launcher3/DevicePaddings.java
blob: 08fb47b2cff986da6b68ea4c43e682b327c01135 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3;

import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;

/**
 * Workspace items have a fixed height, so we need a way to distribute any unused workspace height.
 *
 * The unused or "extra" height is allocated to three different variable heights:
 * - The space above the workspace
 * - The space between the workspace and hotseat
 * - The space below the hotseat
 */
public class DevicePaddings {

    private static final String DEVICE_PADDINGS = "device-paddings";
    private static final String DEVICE_PADDING = "device-padding";

    private static final String WORKSPACE_TOP_PADDING = "workspaceTopPadding";
    private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding";
    private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding";

    private static final String TAG = DevicePaddings.class.getSimpleName();
    private static final boolean DEBUG = false;

    ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();

    public DevicePaddings(Context context, int devicePaddingId) {
        try (XmlResourceParser parser = context.getResources().getXml(devicePaddingId)) {
            final int depth = parser.getDepth();
            int type;
            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
                if ((type == XmlPullParser.START_TAG) && DEVICE_PADDINGS.equals(parser.getName())) {
                    final int displayDepth = parser.getDepth();
                    while (((type = parser.next()) != XmlPullParser.END_TAG ||
                            parser.getDepth() > displayDepth)
                            && type != XmlPullParser.END_DOCUMENT) {
                        if ((type == XmlPullParser.START_TAG)
                                && DEVICE_PADDING.equals(parser.getName())) {
                            TypedArray a = context.obtainStyledAttributes(
                                    Xml.asAttributeSet(parser), R.styleable.DevicePadding);
                            int maxWidthPx = a.getDimensionPixelSize(
                                    R.styleable.DevicePadding_maxEmptySpace, 0);
                            a.recycle();

                            PaddingFormula workspaceTopPadding = null;
                            PaddingFormula workspaceBottomPadding = null;
                            PaddingFormula hotseatBottomPadding = null;

                            final int limitDepth = parser.getDepth();
                            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                                    parser.getDepth() > limitDepth)
                                    && type != XmlPullParser.END_DOCUMENT) {
                                AttributeSet attr = Xml.asAttributeSet(parser);
                                if ((type == XmlPullParser.START_TAG)) {
                                    if (WORKSPACE_TOP_PADDING.equals(parser.getName())) {
                                        workspaceTopPadding = new PaddingFormula(context, attr);
                                    } else if (WORKSPACE_BOTTOM_PADDING.equals(parser.getName())) {
                                        workspaceBottomPadding = new PaddingFormula(context, attr);
                                    } else if (HOTSEAT_BOTTOM_PADDING.equals(parser.getName())) {
                                        hotseatBottomPadding = new PaddingFormula(context, attr);
                                    }
                                }
                            }

                            if (workspaceTopPadding == null
                                    || workspaceBottomPadding == null
                                    || hotseatBottomPadding == null) {
                                if (Utilities.IS_DEBUG_DEVICE) {
                                    throw new RuntimeException("DevicePadding missing padding.");
                                }
                            }

                            DevicePadding dp = new DevicePadding(maxWidthPx, workspaceTopPadding,
                                    workspaceBottomPadding, hotseatBottomPadding);
                            if (dp.isValid()) {
                                mDevicePaddings.add(dp);
                            } else {
                                Log.e(TAG, "Invalid device padding found.");
                                if (Utilities.IS_DEBUG_DEVICE) {
                                    throw new RuntimeException("DevicePadding is invalid");
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException | XmlPullParserException e) {
            Log.e(TAG, "Failure parsing device padding layout.", e);
            throw new RuntimeException(e);
        }

        // Sort ascending by maxEmptySpacePx
        mDevicePaddings.sort((sl1, sl2) -> Integer.compare(sl1.maxEmptySpacePx,
                sl2.maxEmptySpacePx));
    }

    public DevicePadding getDevicePadding(int extraSpacePx) {
        for (DevicePadding limit : mDevicePaddings) {
            if (extraSpacePx <= limit.maxEmptySpacePx) {
                return limit;
            }
        }

        return mDevicePaddings.get(mDevicePaddings.size() - 1);
    }

    /**
     * Holds all the formulas to calculate the padding for a particular device based on the
     * amount of extra space.
     */
    public static final class DevicePadding {

        // One for each padding since they can each be off by 1 due to rounding errors.
        private static final int ROUNDING_THRESHOLD_PX = 3;

        private final int maxEmptySpacePx;
        private final PaddingFormula workspaceTopPadding;
        private final PaddingFormula workspaceBottomPadding;
        private final PaddingFormula hotseatBottomPadding;

        public DevicePadding(int maxEmptySpacePx,
                PaddingFormula workspaceTopPadding,
                PaddingFormula workspaceBottomPadding,
                PaddingFormula hotseatBottomPadding) {
            this.maxEmptySpacePx = maxEmptySpacePx;
            this.workspaceTopPadding = workspaceTopPadding;
            this.workspaceBottomPadding = workspaceBottomPadding;
            this.hotseatBottomPadding = hotseatBottomPadding;
        }

        public int getMaxEmptySpacePx() {
            return maxEmptySpacePx;
        }

        public int getWorkspaceTopPadding(int extraSpacePx) {
            return workspaceTopPadding.calculate(extraSpacePx);
        }

        public int getWorkspaceBottomPadding(int extraSpacePx) {
            return workspaceBottomPadding.calculate(extraSpacePx);
        }

        public int getHotseatBottomPadding(int extraSpacePx) {
            return hotseatBottomPadding.calculate(extraSpacePx);
        }

        public boolean isValid() {
            int workspaceTopPadding = getWorkspaceTopPadding(maxEmptySpacePx);
            int workspaceBottomPadding = getWorkspaceBottomPadding(maxEmptySpacePx);
            int hotseatBottomPadding = getHotseatBottomPadding(maxEmptySpacePx);
            int sum = workspaceTopPadding + workspaceBottomPadding + hotseatBottomPadding;
            int diff = Math.abs(sum - maxEmptySpacePx);
            if (DEBUG) {
                Log.d(TAG, "isValid: workspaceTopPadding=" + workspaceTopPadding
                        + ", workspaceBottomPadding=" + workspaceBottomPadding
                        + ", hotseatBottomPadding=" + hotseatBottomPadding
                        + ", sum=" + sum
                        + ", diff=" + diff);
            }
            return diff <= ROUNDING_THRESHOLD_PX;
        }
    }

    /**
     * Used to calculate a padding based on three variables: a, b, and c.
     *
     * Calculation: a * (extraSpace - c) + b
     */
    private static final class PaddingFormula {

        private final float a;
        private final float b;
        private final float c;

        public PaddingFormula(Context context, AttributeSet attrs) {
            TypedArray t = context.obtainStyledAttributes(attrs,
                    R.styleable.DevicePaddingFormula);

            a = getValue(t, R.styleable.DevicePaddingFormula_a);
            b = getValue(t, R.styleable.DevicePaddingFormula_b);
            c = getValue(t, R.styleable.DevicePaddingFormula_c);

            t.recycle();
        }

        public int calculate(int extraSpacePx) {
            if (DEBUG) {
                Log.d(TAG, "a=" + a + " * (" + extraSpacePx + " - " + c + ") + b=" + b);
            }
            return Math.round(a * (extraSpacePx - c) + b);
        }

        private static float getValue(TypedArray a, int index) {
            if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
                return a.getDimensionPixelSize(index, 0);
            } else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
                return a.getFloat(index, 0);
            }
            return 0;
        }

        @Override
        public String toString() {
            return "a=" + a + ", b=" + b + ", c=" + c;
        }
    }
}