mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-19 18:58:19 +00:00
The bug is caused by launcher saving the grid name and using that grid name to look for matching display options. This makes sense when changing the grid size, but doesn't work well when changing the display size. Example: Initial Pixel display size is set to Default, so we save "normal" as the KEY_IDP_GRID_NAME. When we change display size to Largest, the KEY_IDP_GRID_NAME is still "normal" and so we only look at display options under "normal". Before this, Pixel with display size set to Largest would be set to "reasonable". This should be safe change for Q, and we can have a proper fix when we officially support changing grid size. Bug: 131867841 Change-Id: If5f3b0a13b90069973e929024b26bd9b9c45a7d8
591 lines
23 KiB
Java
591 lines
23 KiB
Java
/*
|
|
* Copyright (C) 2015 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 static com.android.launcher3.Utilities.getDevicePrefs;
|
|
import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
|
|
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.appwidget.AppWidgetHostView;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.res.Configuration;
|
|
import android.content.res.Resources;
|
|
import android.content.res.TypedArray;
|
|
import android.content.res.XmlResourceParser;
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.text.TextUtils;
|
|
import android.util.AttributeSet;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.Log;
|
|
import android.util.SparseArray;
|
|
import android.util.TypedValue;
|
|
import android.util.Xml;
|
|
import android.view.Display;
|
|
import android.view.WindowManager;
|
|
|
|
import com.android.launcher3.graphics.IconShape;
|
|
import com.android.launcher3.util.ConfigMonitor;
|
|
import com.android.launcher3.util.IntArray;
|
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
|
import com.android.launcher3.util.Themes;
|
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.VisibleForTesting;
|
|
|
|
public class InvariantDeviceProfile {
|
|
|
|
public static final String TAG = "IDP";
|
|
// We do not need any synchronization for this variable as its only written on UI thread.
|
|
public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
|
|
new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
|
|
|
|
private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
|
|
|
|
private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
|
|
|
|
public static final int CHANGE_FLAG_GRID = 1 << 0;
|
|
public static final int CHANGE_FLAG_ICON_PARAMS = 1 << 1;
|
|
|
|
public static final String KEY_ICON_PATH_REF = "pref_icon_shape_path";
|
|
|
|
// Constants that affects the interpolation curve between statically defined device profile
|
|
// buckets.
|
|
private static final float KNEARESTNEIGHBOR = 3;
|
|
private static final float WEIGHT_POWER = 5;
|
|
|
|
// used to offset float not being able to express extremely small weights in extreme cases.
|
|
private static final float WEIGHT_EFFICIENT = 100000f;
|
|
|
|
private static final int CONFIG_ICON_MASK_RES_ID = Resources.getSystem().getIdentifier(
|
|
"config_icon_mask", "string", "android");
|
|
|
|
/**
|
|
* Number of icons per row and column in the workspace.
|
|
*/
|
|
public int numRows;
|
|
public int numColumns;
|
|
|
|
/**
|
|
* Number of icons per row and column in the folder.
|
|
*/
|
|
public int numFolderRows;
|
|
public int numFolderColumns;
|
|
public float iconSize;
|
|
public String iconShapePath;
|
|
public float landscapeIconSize;
|
|
public int iconBitmapSize;
|
|
public int fillResIconDpi;
|
|
public float iconTextSize;
|
|
|
|
private SparseArray<TypedValue> mExtraAttrs;
|
|
|
|
/**
|
|
* Number of icons inside the hotseat area.
|
|
*/
|
|
public int numHotseatIcons;
|
|
|
|
public int defaultLayoutId;
|
|
int demoModeLayoutId;
|
|
|
|
public DeviceProfile landscapeProfile;
|
|
public DeviceProfile portraitProfile;
|
|
|
|
public Point defaultWallpaperSize;
|
|
public Rect defaultWidgetPadding;
|
|
|
|
private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
|
|
private ConfigMonitor mConfigMonitor;
|
|
private OverlayMonitor mOverlayMonitor;
|
|
|
|
@VisibleForTesting
|
|
public InvariantDeviceProfile() {}
|
|
|
|
private InvariantDeviceProfile(InvariantDeviceProfile p) {
|
|
numRows = p.numRows;
|
|
numColumns = p.numColumns;
|
|
numFolderRows = p.numFolderRows;
|
|
numFolderColumns = p.numFolderColumns;
|
|
iconSize = p.iconSize;
|
|
iconShapePath = p.iconShapePath;
|
|
landscapeIconSize = p.landscapeIconSize;
|
|
iconTextSize = p.iconTextSize;
|
|
numHotseatIcons = p.numHotseatIcons;
|
|
defaultLayoutId = p.defaultLayoutId;
|
|
demoModeLayoutId = p.demoModeLayoutId;
|
|
mExtraAttrs = p.mExtraAttrs;
|
|
mOverlayMonitor = p.mOverlayMonitor;
|
|
}
|
|
|
|
@TargetApi(23)
|
|
private InvariantDeviceProfile(Context context) {
|
|
initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null));
|
|
mConfigMonitor = new ConfigMonitor(context,
|
|
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
|
|
mOverlayMonitor = new OverlayMonitor(context);
|
|
}
|
|
|
|
/**
|
|
* This constructor should NOT have any monitors by design.
|
|
*/
|
|
public InvariantDeviceProfile(Context context, String gridName) {
|
|
String newName = initGrid(context, gridName);
|
|
if (newName == null || !newName.equals(gridName)) {
|
|
throw new IllegalArgumentException("Unknown grid name");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve system defined or RRO overriden icon shape.
|
|
*/
|
|
private static String getIconShapePath(Context context) {
|
|
if (CONFIG_ICON_MASK_RES_ID == 0) {
|
|
Log.e(TAG, "Icon mask res identifier failed to retrieve.");
|
|
return "";
|
|
}
|
|
return context.getResources().getString(CONFIG_ICON_MASK_RES_ID);
|
|
}
|
|
|
|
private String initGrid(Context context, String gridName) {
|
|
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
|
Display display = wm.getDefaultDisplay();
|
|
DisplayMetrics dm = new DisplayMetrics();
|
|
display.getMetrics(dm);
|
|
|
|
Point smallestSize = new Point();
|
|
Point largestSize = new Point();
|
|
display.getCurrentSizeRange(smallestSize, largestSize);
|
|
|
|
ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
|
|
// This guarantees that width < height
|
|
float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
|
|
float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
|
|
// Sort the profiles based on the closeness to the device size
|
|
Collections.sort(allOptions, (a, b) ->
|
|
Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
|
|
dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
|
|
DisplayOption interpolatedDisplayOption =
|
|
invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
|
|
|
|
GridOption closestProfile = allOptions.get(0).grid;
|
|
numRows = closestProfile.numRows;
|
|
numColumns = closestProfile.numColumns;
|
|
numHotseatIcons = closestProfile.numHotseatIcons;
|
|
defaultLayoutId = closestProfile.defaultLayoutId;
|
|
demoModeLayoutId = closestProfile.demoModeLayoutId;
|
|
numFolderRows = closestProfile.numFolderRows;
|
|
numFolderColumns = closestProfile.numFolderColumns;
|
|
mExtraAttrs = closestProfile.extraAttrs;
|
|
|
|
if (!closestProfile.name.equals(gridName)) {
|
|
Utilities.getPrefs(context).edit()
|
|
.putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
|
|
}
|
|
|
|
iconSize = interpolatedDisplayOption.iconSize;
|
|
iconShapePath = getIconShapePath(context);
|
|
landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
|
|
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm);
|
|
iconTextSize = interpolatedDisplayOption.iconTextSize;
|
|
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
|
|
|
|
// If the partner customization apk contains any grid overrides, apply them
|
|
// Supported overrides: numRows, numColumns, iconSize
|
|
applyPartnerDeviceProfileOverrides(context, dm);
|
|
|
|
Point realSize = new Point();
|
|
display.getRealSize(realSize);
|
|
// The real size never changes. smallSide and largeSide will remain the
|
|
// same in any orientation.
|
|
int smallSide = Math.min(realSize.x, realSize.y);
|
|
int largeSide = Math.max(realSize.x, realSize.y);
|
|
|
|
landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
|
|
largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
|
|
portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
|
|
smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
|
|
|
|
// We need to ensure that there is enough extra space in the wallpaper
|
|
// for the intended parallax effects
|
|
if (context.getResources().getConfiguration().smallestScreenWidthDp >= 720) {
|
|
defaultWallpaperSize = new Point(
|
|
(int) (largeSide * wallpaperTravelToScreenWidthRatio(largeSide, smallSide)),
|
|
largeSide);
|
|
} else {
|
|
defaultWallpaperSize = new Point(Math.max(smallSide * 2, largeSide), largeSide);
|
|
}
|
|
|
|
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
|
|
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
|
|
|
|
return closestProfile.name;
|
|
}
|
|
|
|
@Nullable
|
|
public TypedValue getAttrValue(int attr) {
|
|
return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
|
|
}
|
|
|
|
public void addOnChangeListener(OnIDPChangeListener listener) {
|
|
mChangeListeners.add(listener);
|
|
}
|
|
|
|
public void removeOnChangeListener(OnIDPChangeListener listener) {
|
|
mChangeListeners.remove(listener);
|
|
}
|
|
|
|
private void killProcess(Context context) {
|
|
Log.e("ConfigMonitor", "restarting launcher");
|
|
android.os.Process.killProcess(android.os.Process.myPid());
|
|
}
|
|
|
|
public void verifyConfigChangedInBackground(final Context context) {
|
|
String savedIconMaskPath = getDevicePrefs(context).getString(KEY_ICON_PATH_REF, "");
|
|
// Good place to check if grid size changed in themepicker when launcher was dead.
|
|
if (savedIconMaskPath.isEmpty()) {
|
|
getDevicePrefs(context).edit().putString(KEY_ICON_PATH_REF, getIconShapePath(context))
|
|
.apply();
|
|
} else if (!savedIconMaskPath.equals(getIconShapePath(context))) {
|
|
getDevicePrefs(context).edit().putString(KEY_ICON_PATH_REF, getIconShapePath(context))
|
|
.apply();
|
|
apply(context, CHANGE_FLAG_ICON_PARAMS);
|
|
}
|
|
}
|
|
|
|
public void setCurrentGrid(Context context, String gridName) {
|
|
Context appContext = context.getApplicationContext();
|
|
Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
|
|
new MainThreadExecutor().execute(() -> onConfigChanged(appContext));
|
|
}
|
|
|
|
private void onConfigChanged(Context context) {
|
|
// Config changes, what shall we do?
|
|
InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
|
|
|
|
// Re-init grid
|
|
// TODO(b/131867841): We pass in null here so that we can calculate the closest profile
|
|
// without the bias of the grid name.
|
|
initGrid(context, null);
|
|
|
|
int changeFlags = 0;
|
|
if (numRows != oldProfile.numRows ||
|
|
numColumns != oldProfile.numColumns ||
|
|
numFolderColumns != oldProfile.numFolderColumns ||
|
|
numFolderRows != oldProfile.numFolderRows ||
|
|
numHotseatIcons != oldProfile.numHotseatIcons) {
|
|
changeFlags |= CHANGE_FLAG_GRID;
|
|
}
|
|
|
|
if (iconSize != oldProfile.iconSize || iconBitmapSize != oldProfile.iconBitmapSize ||
|
|
!iconShapePath.equals(oldProfile.iconShapePath)) {
|
|
changeFlags |= CHANGE_FLAG_ICON_PARAMS;
|
|
}
|
|
if (!iconShapePath.equals(oldProfile.iconShapePath)) {
|
|
IconShape.init(context);
|
|
}
|
|
|
|
apply(context, changeFlags);
|
|
}
|
|
|
|
private void apply(Context context, int changeFlags) {
|
|
// Create a new config monitor
|
|
mConfigMonitor.unregister();
|
|
mConfigMonitor = new ConfigMonitor(context, this::onConfigChanged);
|
|
|
|
for (OnIDPChangeListener listener : mChangeListeners) {
|
|
listener.onIdpChanged(changeFlags, this);
|
|
}
|
|
}
|
|
|
|
static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context, String gridName) {
|
|
ArrayList<DisplayOption> profiles = new ArrayList<>();
|
|
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
|
|
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)
|
|
&& GridOption.TAG_NAME.equals(parser.getName())) {
|
|
|
|
GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser));
|
|
final int displayDepth = parser.getDepth();
|
|
while (((type = parser.next()) != XmlPullParser.END_TAG ||
|
|
parser.getDepth() > displayDepth)
|
|
&& type != XmlPullParser.END_DOCUMENT) {
|
|
if ((type == XmlPullParser.START_TAG) && "display-option".equals(
|
|
parser.getName())) {
|
|
profiles.add(new DisplayOption(
|
|
gridOption, context, Xml.asAttributeSet(parser)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException|XmlPullParserException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
|
|
ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
|
|
if (!TextUtils.isEmpty(gridName)) {
|
|
for (DisplayOption option : profiles) {
|
|
if (gridName.equals(option.grid.name)) {
|
|
filteredProfiles.add(option);
|
|
}
|
|
}
|
|
}
|
|
if (filteredProfiles.isEmpty()) {
|
|
// No grid found, use the default options
|
|
for (DisplayOption option : profiles) {
|
|
if (option.canBeDefault) {
|
|
filteredProfiles.add(option);
|
|
}
|
|
}
|
|
}
|
|
if (filteredProfiles.isEmpty()) {
|
|
throw new RuntimeException("No display option with canBeDefault=true");
|
|
}
|
|
return filteredProfiles;
|
|
}
|
|
|
|
private int getLauncherIconDensity(int requiredSize) {
|
|
// Densities typically defined by an app.
|
|
int[] densityBuckets = new int[] {
|
|
DisplayMetrics.DENSITY_LOW,
|
|
DisplayMetrics.DENSITY_MEDIUM,
|
|
DisplayMetrics.DENSITY_TV,
|
|
DisplayMetrics.DENSITY_HIGH,
|
|
DisplayMetrics.DENSITY_XHIGH,
|
|
DisplayMetrics.DENSITY_XXHIGH,
|
|
DisplayMetrics.DENSITY_XXXHIGH
|
|
};
|
|
|
|
int density = DisplayMetrics.DENSITY_XXXHIGH;
|
|
for (int i = densityBuckets.length - 1; i >= 0; i--) {
|
|
float expectedSize = ICON_SIZE_DEFINED_IN_APP_DP * densityBuckets[i]
|
|
/ DisplayMetrics.DENSITY_DEFAULT;
|
|
if (expectedSize >= requiredSize) {
|
|
density = densityBuckets[i];
|
|
}
|
|
}
|
|
|
|
return density;
|
|
}
|
|
|
|
/**
|
|
* Apply any Partner customization grid overrides.
|
|
*
|
|
* Currently we support: all apps row / column count.
|
|
*/
|
|
private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) {
|
|
Partner p = Partner.get(context.getPackageManager());
|
|
if (p != null) {
|
|
p.applyInvariantDeviceProfileOverrides(this, dm);
|
|
}
|
|
}
|
|
|
|
private static float dist(float x0, float y0, float x1, float y1) {
|
|
return (float) Math.hypot(x1 - x0, y1 - y0);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static DisplayOption invDistWeightedInterpolate(float width, float height,
|
|
ArrayList<DisplayOption> points) {
|
|
float weights = 0;
|
|
|
|
DisplayOption p = points.get(0);
|
|
if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
|
|
return p;
|
|
}
|
|
|
|
DisplayOption out = new DisplayOption();
|
|
for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
|
|
p = points.get(i);
|
|
float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
|
|
weights += w;
|
|
out.add(new DisplayOption().add(p).multiply(w));
|
|
}
|
|
return out.multiply(1.0f / weights);
|
|
}
|
|
|
|
public DeviceProfile getDeviceProfile(Context context) {
|
|
return context.getResources().getConfiguration().orientation
|
|
== Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile;
|
|
}
|
|
|
|
private static float weight(float x0, float y0, float x1, float y1, float pow) {
|
|
float d = dist(x0, y0, x1, y1);
|
|
if (Float.compare(d, 0f) == 0) {
|
|
return Float.POSITIVE_INFINITY;
|
|
}
|
|
return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow));
|
|
}
|
|
|
|
/**
|
|
* As a ratio of screen height, the total distance we want the parallax effect to span
|
|
* horizontally
|
|
*/
|
|
private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
|
|
float aspectRatio = width / (float) height;
|
|
|
|
// At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
|
|
// At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
|
|
// We will use these two data points to extrapolate how much the wallpaper parallax effect
|
|
// to span (ie travel) at any aspect ratio:
|
|
|
|
final float ASPECT_RATIO_LANDSCAPE = 16/10f;
|
|
final float ASPECT_RATIO_PORTRAIT = 10/16f;
|
|
final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
|
|
final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
|
|
|
|
// To find out the desired width at different aspect ratios, we use the following two
|
|
// formulas, where the coefficient on x is the aspect ratio (width/height):
|
|
// (16/10)x + y = 1.5
|
|
// (10/16)x + y = 1.2
|
|
// We solve for x and y and end up with a final formula:
|
|
final float x =
|
|
(WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
|
|
(ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
|
|
final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
|
|
return x * aspectRatio + y;
|
|
}
|
|
|
|
public interface OnIDPChangeListener {
|
|
|
|
void onIdpChanged(int changeFlags, InvariantDeviceProfile profile);
|
|
}
|
|
|
|
|
|
public static final class GridOption {
|
|
|
|
public static final String TAG_NAME = "grid-option";
|
|
|
|
public final String name;
|
|
public final int numRows;
|
|
public final int numColumns;
|
|
|
|
private final int numFolderRows;
|
|
private final int numFolderColumns;
|
|
|
|
private final int numHotseatIcons;
|
|
|
|
private final int defaultLayoutId;
|
|
private final int demoModeLayoutId;
|
|
|
|
private final SparseArray<TypedValue> extraAttrs;
|
|
|
|
public GridOption(Context context, AttributeSet attrs) {
|
|
TypedArray a = context.obtainStyledAttributes(
|
|
attrs, R.styleable.GridDisplayOption);
|
|
name = a.getString(R.styleable.GridDisplayOption_name);
|
|
numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
|
|
numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
|
|
|
|
defaultLayoutId = a.getResourceId(
|
|
R.styleable.GridDisplayOption_defaultLayoutId, 0);
|
|
demoModeLayoutId = a.getResourceId(
|
|
R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
|
|
numHotseatIcons = a.getInt(
|
|
R.styleable.GridDisplayOption_numHotseatIcons, numColumns);
|
|
numFolderRows = a.getInt(
|
|
R.styleable.GridDisplayOption_numFolderRows, numRows);
|
|
numFolderColumns = a.getInt(
|
|
R.styleable.GridDisplayOption_numFolderColumns, numColumns);
|
|
a.recycle();
|
|
|
|
extraAttrs = Themes.createValueMap(context, attrs,
|
|
IntArray.wrap(R.styleable.GridDisplayOption));
|
|
}
|
|
}
|
|
|
|
private static final class DisplayOption {
|
|
private final GridOption grid;
|
|
|
|
private final String name;
|
|
private final float minWidthDps;
|
|
private final float minHeightDps;
|
|
private final boolean canBeDefault;
|
|
|
|
private float iconSize;
|
|
private float landscapeIconSize;
|
|
private float iconTextSize;
|
|
|
|
DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
|
|
this.grid = grid;
|
|
|
|
TypedArray a = context.obtainStyledAttributes(
|
|
attrs, R.styleable.ProfileDisplayOption);
|
|
|
|
name = a.getString(R.styleable.ProfileDisplayOption_name);
|
|
minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
|
|
minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
|
|
canBeDefault = a.getBoolean(
|
|
R.styleable.ProfileDisplayOption_canBeDefault, false);
|
|
|
|
iconSize = a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
|
|
landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
|
|
iconSize);
|
|
iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
|
|
a.recycle();
|
|
}
|
|
|
|
DisplayOption() {
|
|
grid = null;
|
|
name = null;
|
|
minWidthDps = 0;
|
|
minHeightDps = 0;
|
|
canBeDefault = false;
|
|
}
|
|
|
|
private DisplayOption multiply(float w) {
|
|
iconSize *= w;
|
|
landscapeIconSize *= w;
|
|
iconTextSize *= w;
|
|
return this;
|
|
}
|
|
|
|
private DisplayOption add(DisplayOption p) {
|
|
iconSize += p.iconSize;
|
|
landscapeIconSize += p.landscapeIconSize;
|
|
iconTextSize += p.iconTextSize;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
private class OverlayMonitor extends BroadcastReceiver {
|
|
|
|
private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
|
|
|
|
OverlayMonitor(Context context) {
|
|
context.registerReceiver(this, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
|
|
}
|
|
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
onConfigChanged(context);
|
|
}
|
|
}
|
|
} |