| /* |
| * 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; |
| } |
| } |
| } |