ShortcutManaegr: load config from settings, also...

support "cmd override-config" and "cmd reset-config" to help CTS.

Bug 27548047

Change-Id: Id6c9e6f41a2238856dd3470d88d88d0e7e686f26
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d43ff4e..d4ff766 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7786,6 +7786,31 @@
         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 8982632..9d8def6 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.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 @@
 /**
  * TODO:
  *
- * - Implement launchShortcut
- *
  * - Detect when already registered instances are passed to APIs again, which might break
  *   internal bitmap handling.
  *
@@ -103,8 +102,6 @@
  *
  * - 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 @@
     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 @@
     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 @@
      */
     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 @@
      */
     private void initialize() {
         synchronized (mLock) {
-            injectLoadConfigurationLocked();
+            loadConfigurationLocked();
             loadBaseStateLocked();
         }
     }
 
-    // Test overrides it to inject different values.
-    @VisibleForTesting
-    void injectLoadConfigurationLocked() {
-        mResetInterval = DEFAULT_RESET_INTERVAL_SEC * 1000L;
-        mMaxDailyUpdates = DEFAULT_MAX_DAILY_UPDATES;
-        mMaxDynamicShortcuts = DEFAULT_MAX_SHORTCUTS_PER_APP;
+    /**
+     * Load the configuration from Settings.
+     */
+    private void loadConfigurationLocked() {
+        updateConfigurationLocked(injectShortcutManagerConstants());
+    }
 
-        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());
+    /**
+     * 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
+    String injectShortcutManagerConstants() {
+        return android.provider.Settings.Global.getString(
+                mContext.getContentResolver(),
+                android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
+    }
+
+    @VisibleForTesting
+    int injectDipToPixel(int dip) {
+        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
+                mContext.getResources().getDisplayMetrics());
     }
 
     // === Persisting ===
@@ -1829,14 +1931,27 @@
                 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 @@
             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 @@
 
             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 @@
         return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
     }
 
+    @VisibleForTesting
     boolean injectIsLowRamDevice() {
         return ActivityManager.isLowRamDeviceStatic();
     }
@@ -1917,22 +2059,32 @@
     }
 
     @VisibleForTesting
-    void setMaxDynamicShortcutsForTest(int max) {
-        mMaxDynamicShortcuts = max;
+    int getMaxDynamicShortcutsForTest() {
+        return mMaxDynamicShortcuts;
     }
 
     @VisibleForTesting
-    void setMaxDailyUpdatesForTest(int max) {
-        mMaxDailyUpdates = max;
+    int getMaxDailyUpdatesForTest() {
+        return mMaxDailyUpdates;
     }
 
     @VisibleForTesting
-    void setMaxIconDimensionForTest(int dimension) {
-        mMaxIconDimension = dimension;
+    long getResetIntervalForTest() {
+        return mResetInterval;
     }
 
     @VisibleForTesting
-    public void setResetIntervalForTest(long interval) {
-        mResetInterval = interval;
+    int getMaxIconDimensionForTest() {
+        return mMaxIconDimension;
+    }
+
+    @VisibleForTesting
+    CompressFormat getIconPersistFormatForTest() {
+        return mIconPersistFormat;
+    }
+
+    @VisibleForTesting
+    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 21daa1b..036cc66 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.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.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 @@
         }
 
         @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 @@
         void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
             // Can't check
         }
+
+        @Override
+        boolean injectIsLowRamDevice() {
+            return mInjectdIsLowRamDevice;
+        }
     }
 
     /** ShortcutManager with injection override methods. */
@@ -186,6 +202,8 @@
 
     private long mInjectedCurrentTimeLillis;
 
+    private boolean mInjectdIsLowRamDevice;
+
     private int mInjectedCallingUid;
     private String mInjectedClientPackage;
 
@@ -216,6 +234,8 @@
 
     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 @@
         // 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()} */