diff options
3 files changed, 274 insertions, 39 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d43ff4e04bcb..d4ff766c6063 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7786,6 +7786,31 @@ public final class Settings { public static final String ALARM_MANAGER_CONSTANTS = "alarm_manager_constants"; /** + * ShortcutManager specific settings. + * This is encoded as a key=value list, separated by commas. Ex: + * + * "reset_interval_sec=86400,max_daily_updates=5" + * + * The following keys are supported: + * + * <pre> + * reset_interval_sec (long) + * max_daily_updates (int) + * max_icon_dimension_dp (int, DP) + * max_icon_dimension_dp_lowram (int, DP) + * max_shortcuts (int) + * icon_quality (int, 0-100) + * icon_format (String) + * </pre> + * + * <p> + * Type: string + * @hide + * @see com.android.server.pm.ShortcutService.ConfigConstants + */ + public static final String SHORTCUT_MANAGER_CONSTANTS = "shortcut_manager_constants"; + + /** * Get the key that retrieves a bluetooth headset's priority. * @hide */ diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 8982632666b7..9d8def66c72a 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -56,6 +56,7 @@ import android.text.format.Time; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.KeyValueListParser; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; @@ -92,8 +93,6 @@ import java.util.function.Predicate; /** * TODO: * - * - Implement launchShortcut - * * - Detect when already registered instances are passed to APIs again, which might break * internal bitmap handling. * @@ -103,8 +102,6 @@ import java.util.function.Predicate; * * - Pinned per each launcher package (multiple launchers) * - * - Load config from settings - * * - Make save async (should we?) * * - Scan and remove orphan bitmaps (just in case). @@ -117,11 +114,26 @@ public class ShortcutService extends IShortcutService.Stub { private static final boolean DEBUG = true; // STOPSHIP if true private static final boolean DEBUG_LOAD = true; // STOPSHIP if true - private static final int DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day - private static final int DEFAULT_MAX_DAILY_UPDATES = 10; - private static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; - private static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; - private static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48; + @VisibleForTesting + static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day + + @VisibleForTesting + static final int DEFAULT_MAX_DAILY_UPDATES = 10; + + @VisibleForTesting + static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; + + @VisibleForTesting + static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; + + @VisibleForTesting + static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48; + + @VisibleForTesting + static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name(); + + @VisibleForTesting + static final int DEFAULT_ICON_PERSIST_QUALITY = 100; private static final int SAVE_DELAY_MS = 5000; // in milliseconds. @@ -158,6 +170,44 @@ public class ShortcutService extends IShortcutService.Stub { private static final String ATTR_ICON_RES = "icon-res"; private static final String ATTR_BITMAP_PATH = "bitmap-path"; + @VisibleForTesting + interface ConfigConstants { + /** + * Key name for the throttling reset interval, in seconds. (long) + */ + String KEY_RESET_INTERVAL_SEC = "reset_interval_sec"; + + /** + * Key name for the max number of modifying API calls per app for every interval. (int) + */ + String KEY_MAX_DAILY_UPDATES = "max_daily_updates"; + + /** + * Key name for the max icon dimensions in DP, for non-low-memory devices. + */ + String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp"; + + /** + * Key name for the max icon dimensions in DP, for low-memory devices. + */ + String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram"; + + /** + * Key name for the max dynamic shortcuts per app. (int) + */ + String KEY_MAX_SHORTCUTS = "max_shortcuts"; + + /** + * Key name for icom compression quality, 0-100. + */ + String KEY_ICON_QUALITY = "icon_quality"; + + /** + * Key name for icon compression format: "PNG", "JPEG" or "WEBP" + */ + String KEY_ICON_FORMAT = "icon_format"; + } + private final Context mContext; private final Object mLock = new Object(); @@ -416,9 +466,8 @@ public class ShortcutService extends IShortcutService.Stub { */ private int mMaxIconDimension; - private CompressFormat mIconPersistFormat = CompressFormat.PNG; - - private int mIconPersistQuality = 100; + private CompressFormat mIconPersistFormat; + private int mIconPersistQuality; public ShortcutService(Context context) { mContext = Preconditions.checkNotNull(context); @@ -498,23 +547,76 @@ public class ShortcutService extends IShortcutService.Stub { */ private void initialize() { synchronized (mLock) { - injectLoadConfigurationLocked(); + loadConfigurationLocked(); loadBaseStateLocked(); } } - // Test overrides it to inject different values. + /** + * Load the configuration from Settings. + */ + private void loadConfigurationLocked() { + updateConfigurationLocked(injectShortcutManagerConstants()); + } + + /** + * Load the configuration from Settings. + */ + @VisibleForTesting + boolean updateConfigurationLocked(String config) { + boolean result = true; + + final KeyValueListParser parser = new KeyValueListParser(','); + try { + parser.setString(config); + } catch (IllegalArgumentException e) { + // Failed to parse the settings string, log this and move on + // with defaults. + Slog.e(TAG, "Bad shortcut manager settings", e); + result = false; + } + + mResetInterval = parser.getLong( + ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) + * 1000L; + + mMaxDailyUpdates = (int) parser.getLong( + ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES); + + mMaxDynamicShortcuts = (int) parser.getLong( + ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP); + + final int iconDimensionDp = injectIsLowRamDevice() + ? (int) parser.getLong( + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, + DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) + : (int) parser.getLong( + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, + DEFAULT_MAX_ICON_DIMENSION_DP); + + mMaxIconDimension = injectDipToPixel(iconDimensionDp); + + mIconPersistFormat = CompressFormat.valueOf( + parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT)); + + mIconPersistQuality = (int) parser.getLong( + ConfigConstants.KEY_ICON_QUALITY, + DEFAULT_ICON_PERSIST_QUALITY); + + return result; + } + @VisibleForTesting - void injectLoadConfigurationLocked() { - mResetInterval = DEFAULT_RESET_INTERVAL_SEC * 1000L; - mMaxDailyUpdates = DEFAULT_MAX_DAILY_UPDATES; - mMaxDynamicShortcuts = DEFAULT_MAX_SHORTCUTS_PER_APP; + String injectShortcutManagerConstants() { + return android.provider.Settings.Global.getString( + mContext.getContentResolver(), + android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS); + } - final int iconDimensionDp = (injectIsLowRamDevice() - ? DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP : DEFAULT_MAX_ICON_DIMENSION_DP); - mMaxIconDimension = - (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, iconDimensionDp, - mContext.getResources().getDisplayMetrics()); + @VisibleForTesting + int injectDipToPixel(int dip) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, + mContext.getResources().getDisplayMetrics()); } // === Persisting === @@ -1829,14 +1931,27 @@ public class ShortcutService extends IShortcutService.Stub { return handleDefaultCommands(cmd); } final PrintWriter pw = getOutPrintWriter(); - switch(cmd) { + int ret = 1; + switch (cmd) { case "reset-package-throttling": - return handleResetPackageThrottling(); + ret = handleResetPackageThrottling(); + break; case "reset-throttling": - return handleResetThrottling(); + ret = handleResetThrottling(); + break; + case "override-config": + ret = handleOverrideConfig(); + break; + case "reset-config": + ret = handleResetConfig(); + break; default: return handleDefaultCommands(cmd); } + if (ret == 0) { + pw.println("Success"); + } + return ret; } @Override @@ -1850,6 +1965,12 @@ public class ShortcutService extends IShortcutService.Stub { pw.println("cmd shortcut reset-throttling"); pw.println(" Reset throttling for all packages and users"); pw.println(); + pw.println("cmd shortcut override-config CONFIG"); + pw.println(" Override the configuration for testing (will last until reboot)"); + pw.println(); + pw.println("cmd shortcut reset-config"); + pw.println(" Reset the configuration set with \"update-config\""); + pw.println(); } private int handleResetThrottling() { @@ -1881,6 +2002,26 @@ public class ShortcutService extends IShortcutService.Stub { return 0; } + + private int handleOverrideConfig() { + final PrintWriter pw = getOutPrintWriter(); + final String config = getNextArgRequired(); + + synchronized (mLock) { + if (!updateConfigurationLocked(config)) { + pw.println("override-config failed. See logcat for details."); + return 1; + } + } + return 0; + } + + private int handleResetConfig() { + synchronized (mLock) { + loadConfigurationLocked(); + } + return 0; + } } // === Unit test support === @@ -1903,6 +2044,7 @@ public class ShortcutService extends IShortcutService.Stub { return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); } + @VisibleForTesting boolean injectIsLowRamDevice() { return ActivityManager.isLowRamDeviceStatic(); } @@ -1917,22 +2059,32 @@ public class ShortcutService extends IShortcutService.Stub { } @VisibleForTesting - void setMaxDynamicShortcutsForTest(int max) { - mMaxDynamicShortcuts = max; + int getMaxDynamicShortcutsForTest() { + return mMaxDynamicShortcuts; + } + + @VisibleForTesting + int getMaxDailyUpdatesForTest() { + return mMaxDailyUpdates; + } + + @VisibleForTesting + long getResetIntervalForTest() { + return mResetInterval; } @VisibleForTesting - void setMaxDailyUpdatesForTest(int max) { - mMaxDailyUpdates = max; + int getMaxIconDimensionForTest() { + return mMaxIconDimension; } @VisibleForTesting - void setMaxIconDimensionForTest(int dimension) { - mMaxIconDimension = dimension; + CompressFormat getIconPersistFormatForTest() { + return mIconPersistFormat; } @VisibleForTesting - public void setResetIntervalForTest(long interval) { - mResetInterval = interval; + int getIconPersistQualityForTest() { + return mIconPersistQuality; } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index 21daa1b47d8c..036cc66de4cd 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -27,6 +27,7 @@ import android.content.pm.ShortcutManager; import android.content.pm.ShortcutServiceInternal; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -42,6 +43,7 @@ import com.android.frameworks.servicestests.R; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.ShortcutService.ConfigConstants; import com.android.server.pm.ShortcutService.FileOutputStreamWithPath; import libcore.io.IoUtils; @@ -114,11 +116,20 @@ public class ShortcutManagerTest extends AndroidTestCase { } @Override - void injectLoadConfigurationLocked() { - setResetIntervalForTest(INTERVAL); - setMaxDynamicShortcutsForTest(MAX_SHORTCUTS); - setMaxDailyUpdatesForTest(MAX_DAILY_UPDATES); - setMaxIconDimensionForTest(MAX_ICON_DIMENSION); + String injectShortcutManagerConstants() { + return ConfigConstants.KEY_RESET_INTERVAL_SEC + "=" + (INTERVAL / 1000) + "," + + ConfigConstants.KEY_MAX_SHORTCUTS + "=" + MAX_SHORTCUTS + "," + + ConfigConstants.KEY_MAX_DAILY_UPDATES + "=" + MAX_DAILY_UPDATES + "," + + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=" + MAX_ICON_DIMENSION + "," + + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=" + + MAX_ICON_DIMENSION_LOWRAM + "," + + ConfigConstants.KEY_ICON_FORMAT + "=PNG," + + ConfigConstants.KEY_ICON_QUALITY + "=100"; + } + + @Override + int injectDipToPixel(int dip) { + return dip; } @Override @@ -151,6 +162,11 @@ public class ShortcutManagerTest extends AndroidTestCase { void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { // Can't check } + + @Override + boolean injectIsLowRamDevice() { + return mInjectdIsLowRamDevice; + } } /** ShortcutManager with injection override methods. */ @@ -186,6 +202,8 @@ public class ShortcutManagerTest extends AndroidTestCase { private long mInjectedCurrentTimeLillis; + private boolean mInjectdIsLowRamDevice; + private int mInjectedCallingUid; private String mInjectedClientPackage; @@ -216,6 +234,8 @@ public class ShortcutManagerTest extends AndroidTestCase { private static final int MAX_ICON_DIMENSION = 128; + private static final int MAX_ICON_DIMENSION_LOWRAM = 32; + @Override protected void setUp() throws Exception { super.setUp(); @@ -676,6 +696,44 @@ public class ShortcutManagerTest extends AndroidTestCase { // TODO Add various broken cases. } + public void testLoadConfig() { + mService.updateConfigurationLocked( + ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123," + + ConfigConstants.KEY_MAX_SHORTCUTS + "=4," + + ConfigConstants.KEY_MAX_DAILY_UPDATES + "=5," + + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100," + + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50," + + ConfigConstants.KEY_ICON_FORMAT + "=WEBP," + + ConfigConstants.KEY_ICON_QUALITY + "=75"); + assertEquals(123000, mService.getResetIntervalForTest()); + assertEquals(4, mService.getMaxDynamicShortcutsForTest()); + assertEquals(5, mService.getMaxDailyUpdatesForTest()); + assertEquals(100, mService.getMaxIconDimensionForTest()); + assertEquals(CompressFormat.WEBP, mService.getIconPersistFormatForTest()); + assertEquals(75, mService.getIconPersistQualityForTest()); + + mInjectdIsLowRamDevice = true; + mService.updateConfigurationLocked( + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100," + + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50," + + ConfigConstants.KEY_ICON_FORMAT + "=JPEG"); + assertEquals(ShortcutService.DEFAULT_RESET_INTERVAL_SEC * 1000, + mService.getResetIntervalForTest()); + + assertEquals(ShortcutService.DEFAULT_MAX_SHORTCUTS_PER_APP, + mService.getMaxDynamicShortcutsForTest()); + + assertEquals(ShortcutService.DEFAULT_MAX_DAILY_UPDATES, + mService.getMaxDailyUpdatesForTest()); + + assertEquals(50, mService.getMaxIconDimensionForTest()); + + assertEquals(CompressFormat.JPEG, mService.getIconPersistFormatForTest()); + + assertEquals(ShortcutService.DEFAULT_ICON_PERSIST_QUALITY, + mService.getIconPersistQualityForTest()); + } + // === Test for app side APIs === /** Test for {@link android.content.pm.ShortcutManager#getMaxDynamicShortcutCount()} */ |