blob: 7c387b1efee8afcd9e0ca25f21ddf268190e9d9a [file] [log] [blame]
/*
* 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 espace below the hotseat
*/
public class DevicePaddings {
private static final String DEVICE_PADDING = "device-paddings";
private static final String DEVICE_PADDINGS = "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_PADDING.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_PADDINGS.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;
}
}
}