Merge "Change floating search button icon"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f284e30..2bef192 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -841,7 +841,9 @@
     <string name="disabled_by_administrator_summary">Disabled by admin</string>
     <!-- In the security screen, the header title for security statuses -->
     <string name="security_status_title">Security status</string>
-    <!-- Summary for Security settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
+    <!-- Summary for Security settings, explaining a few important settings under it [CHAR LIMIT=NONE] -->
+    <string name="security_dashboard_summary_face">Screen lock, face unlock</string>
+    <!-- Summary for Security settings, explaining a few important settings under it [CHAR LIMIT=NONE] -->
     <string name="security_dashboard_summary">Screen lock, fingerprint</string>
     <!-- Summary for Security settings when fingerprint is not supported [CHAR LIMIT=NONE]-->
     <string name="security_dashboard_summary_no_fingerprint">Screen lock</string>
@@ -5943,7 +5945,7 @@
 
     <!-- Activity title for network data usage summary. [CHAR LIMIT=25] -->
     <string name="data_usage_summary_title">Data usage</string>
-    <!-- Activity title Mobile data & WI-FI summary. [CHAR LIMIT=25] -->
+    <!-- Activity title Mobile data & WI-FI summary. [CHAR LIMIT=40] -->
     <string name="data_usage_app_summary_title">Mobile data &amp; Wi\u2011Fi</string>
     <!-- Message about carrier data accounting.  [CHAR LIMIT=100] -->
     <string name="data_usage_accounting">Carrier data accounting may differ from your device.</string>
diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java
index 904dea3..46a3e6b 100644
--- a/src/com/android/settings/accounts/AccountPreferenceController.java
+++ b/src/com/android/settings/accounts/AccountPreferenceController.java
@@ -192,12 +192,10 @@
                         data.screenTitle = screenTitle;
                         rawData.add(data);
                     }
-                    {
-                        SearchIndexableRaw data = new SearchIndexableRaw(mContext);
-                        data.title = res.getString(R.string.managed_profile_settings_title);
-                        data.screenTitle = screenTitle;
-                        rawData.add(data);
-                    }
+                    SearchIndexableRaw data = new SearchIndexableRaw(mContext);
+                    data.title = res.getString(R.string.managed_profile_settings_title);
+                    data.screenTitle = screenTitle;
+                    rawData.add(data);
                 }
             }
         }
@@ -300,6 +298,7 @@
         final ProfileData data = mProfiles.get(userInfo.id);
         if (data != null) {
             data.pendingRemoval = false;
+            data.userInfo = userInfo;
             if (userInfo.isEnabled()) {
                 // recreate the authentication helper to refresh the list of enabled accounts
                 data.authenticatorHelper =
diff --git a/src/com/android/settings/core/SettingsBaseActivity.java b/src/com/android/settings/core/SettingsBaseActivity.java
index 9672694..23eb860 100644
--- a/src/com/android/settings/core/SettingsBaseActivity.java
+++ b/src/com/android/settings/core/SettingsBaseActivity.java
@@ -38,7 +38,7 @@
 import androidx.fragment.app.FragmentActivity;
 
 import com.android.settings.R;
-import com.android.settingslib.drawer.CategoryManager;
+import com.android.settings.dashboard.CategoryManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -172,10 +172,6 @@
         new CategoriesUpdateTask().execute();
     }
 
-    public String getSettingPkg() {
-        return CategoryManager.SETTING_PKG;
-    }
-
     public interface CategoryListener {
         void onCategoriesChanged();
     }
@@ -190,7 +186,7 @@
 
         @Override
         protected Void doInBackground(Void... params) {
-            mCategoryManager.reloadAllCategories(SettingsBaseActivity.this, getSettingPkg());
+            mCategoryManager.reloadAllCategories(SettingsBaseActivity.this);
             return null;
         }
 
diff --git a/src/com/android/settings/dashboard/CategoryManager.java b/src/com/android/settings/dashboard/CategoryManager.java
new file mode 100644
index 0000000..f000458
--- /dev/null
+++ b/src/com/android/settings/dashboard/CategoryManager.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2016 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.settings.dashboard;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.TileUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class CategoryManager {
+
+    public static final String SETTING_PKG = "com.android.settings";
+
+    private static final String TAG = "CategoryManager";
+
+    private static CategoryManager sInstance;
+    private final InterestingConfigChanges mInterestingConfigChanges;
+
+    // Tile cache (key: <packageName, activityName>, value: tile)
+    private final Map<Pair<String, String>, Tile> mTileByComponentCache;
+
+    // Tile cache (key: category key, value: category)
+    private final Map<String, DashboardCategory> mCategoryByKeyMap;
+
+    private List<DashboardCategory> mCategories;
+    private String mExtraAction;
+
+    public static CategoryManager get(Context context) {
+        return get(context, null);
+    }
+
+    public static CategoryManager get(Context context, String action) {
+        if (sInstance == null) {
+            sInstance = new CategoryManager(context, action);
+        }
+        return sInstance;
+    }
+
+    CategoryManager(Context context, String action) {
+        mTileByComponentCache = new ArrayMap<>();
+        mCategoryByKeyMap = new ArrayMap<>();
+        mInterestingConfigChanges = new InterestingConfigChanges();
+        mInterestingConfigChanges.applyNewConfig(context.getResources());
+        mExtraAction = action;
+    }
+
+    public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
+        tryInitCategories(context);
+
+        return mCategoryByKeyMap.get(categoryKey);
+    }
+
+    public synchronized List<DashboardCategory> getCategories(Context context) {
+        tryInitCategories(context);
+        return mCategories;
+    }
+
+    public synchronized void reloadAllCategories(Context context) {
+        final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig(
+                context.getResources());
+        mCategories = null;
+        tryInitCategories(context, forceClearCache);
+    }
+
+    public synchronized void updateCategoryFromBlacklist(Set<ComponentName> tileBlacklist) {
+        if (mCategories == null) {
+            Log.w(TAG, "Category is null, skipping blacklist update");
+        }
+        for (int i = 0; i < mCategories.size(); i++) {
+            DashboardCategory category = mCategories.get(i);
+            for (int j = 0; j < category.getTilesCount(); j++) {
+                Tile tile = category.getTile(j);
+                if (tileBlacklist.contains(tile.intent.getComponent())) {
+                    category.removeTile(j--);
+                }
+            }
+        }
+    }
+
+    private synchronized void tryInitCategories(Context context) {
+        // Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
+        // happens.
+        tryInitCategories(context, false /* forceClearCache */);
+    }
+
+    private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
+        if (mCategories == null) {
+            if (forceClearCache) {
+                mTileByComponentCache.clear();
+            }
+            mCategoryByKeyMap.clear();
+            mCategories = TileUtils.getCategories(context, mTileByComponentCache, mExtraAction);
+            for (DashboardCategory category : mCategories) {
+                mCategoryByKeyMap.put(category.key, category);
+            }
+            backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
+            sortCategories(context, mCategoryByKeyMap);
+            filterDuplicateTiles(mCategoryByKeyMap);
+        }
+    }
+
+    @VisibleForTesting
+    synchronized void backwardCompatCleanupForCategory(
+            Map<Pair<String, String>, Tile> tileByComponentCache,
+            Map<String, DashboardCategory> categoryByKeyMap) {
+        // A package can use a) CategoryKey, b) old category keys, c) both.
+        // Check if a package uses old category key only.
+        // If yes, map them to new category key.
+
+        // Build a package name -> tile map first.
+        final Map<String, List<Tile>> packageToTileMap = new HashMap<>();
+        for (Entry<Pair<String, String>, Tile> tileEntry : tileByComponentCache.entrySet()) {
+            final String packageName = tileEntry.getKey().first;
+            List<Tile> tiles = packageToTileMap.get(packageName);
+            if (tiles == null) {
+                tiles = new ArrayList<>();
+                packageToTileMap.put(packageName, tiles);
+            }
+            tiles.add(tileEntry.getValue());
+        }
+
+        for (Entry<String, List<Tile>> entry : packageToTileMap.entrySet()) {
+            final List<Tile> tiles = entry.getValue();
+            // Loop map, find if all tiles from same package uses old key only.
+            boolean useNewKey = false;
+            boolean useOldKey = false;
+            for (Tile tile : tiles) {
+                if (CategoryKey.KEY_COMPAT_MAP.containsKey(tile.category)) {
+                    useOldKey = true;
+                } else {
+                    useNewKey = true;
+                    break;
+                }
+            }
+            // Uses only old key, map them to new keys one by one.
+            if (useOldKey && !useNewKey) {
+                for (Tile tile : tiles) {
+                    final String newCategoryKey = CategoryKey.KEY_COMPAT_MAP.get(tile.category);
+                    tile.category = newCategoryKey;
+                    // move tile to new category.
+                    DashboardCategory newCategory = categoryByKeyMap.get(newCategoryKey);
+                    if (newCategory == null) {
+                        newCategory = new DashboardCategory();
+                        categoryByKeyMap.put(newCategoryKey, newCategory);
+                    }
+                    newCategory.addTile(tile);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sort the tiles injected from all apps such that if they have the same priority value,
+     * they wil lbe sorted by package name.
+     * <p/>
+     * A list of tiles are considered sorted when their priority value decreases in a linear
+     * scan.
+     */
+    @VisibleForTesting
+    synchronized void sortCategories(Context context,
+            Map<String, DashboardCategory> categoryByKeyMap) {
+        for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
+            categoryEntry.getValue().sortTiles(context.getPackageName());
+        }
+    }
+
+    /**
+     * Filter out duplicate tiles from category. Duplicate tiles are the ones pointing to the
+     * same intent.
+     */
+    @VisibleForTesting
+    synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
+        for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
+            final DashboardCategory category = categoryEntry.getValue();
+            final int count = category.getTilesCount();
+            final Set<ComponentName> components = new ArraySet<>();
+            for (int i = count - 1; i >= 0; i--) {
+                final Tile tile = category.getTile(i);
+                if (tile.intent == null) {
+                    continue;
+                }
+                final ComponentName tileComponent = tile.intent.getComponent();
+                if (components.contains(tileComponent)) {
+                    category.removeTile(i);
+                } else {
+                    components.add(tileComponent);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sort priority value for tiles within a single {@code DashboardCategory}.
+     *
+     * @see #sortCategories(Context, Map)
+     */
+    private synchronized void sortCategoriesForExternalTiles(Context context,
+            DashboardCategory dashboardCategory) {
+        dashboardCategory.sortTiles(context.getPackageName());
+
+    }
+}
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
index a443355..46beac4 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
@@ -40,7 +40,6 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
-import com.android.settingslib.drawer.CategoryManager;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.ProfileSelectDialog;
 import com.android.settingslib.drawer.Tile;
diff --git a/src/com/android/settings/print/PrintServiceSettingsFragment.java b/src/com/android/settings/print/PrintServiceSettingsFragment.java
index 1311be0..345b4ae 100644
--- a/src/com/android/settings/print/PrintServiceSettingsFragment.java
+++ b/src/com/android/settings/print/PrintServiceSettingsFragment.java
@@ -286,7 +286,7 @@
 
     @Override
     public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
-        return new PrintServicesLoader(
+        return new SettingsPrintServicesLoader(
                 (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE), getContext(),
                 PrintManager.ALL_SERVICES);
     }
diff --git a/src/com/android/settings/print/PrintServicesLoader.java b/src/com/android/settings/print/PrintServicesLoader.java
deleted file mode 100644
index 57cddb9..0000000
--- a/src/com/android/settings/print/PrintServicesLoader.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2018 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.settings.print;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.print.PrintManager;
-import android.printservice.PrintServiceInfo;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-
-import androidx.loader.content.Loader;
-
-/**
- * Loader for the list of print services. Can be parametrized to select a subset.
- *
- */
-public class PrintServicesLoader extends Loader<List<PrintServiceInfo>> {
-    /** What type of services to load. */
-    private final int mSelectionFlags;
-
-    /** The print manager to be used by this object */
-    private final @NonNull PrintManager mPrintManager;
-
-    /** Handler to sequentialize the delivery of the results to the main thread */
-    private final @NonNull Handler mHandler;
-
-    /** Listens for updates to the data from the platform */
-    private PrintManager.PrintServicesChangeListener mListener;
-
-    /**
-     * Create a new PrintServicesLoader.
-     *
-     * @param printManager   The print manager supplying the data
-     * @param context        Context of the using object
-     * @param selectionFlags What type of services to load.
-     */
-    public PrintServicesLoader(@NonNull PrintManager printManager, @NonNull Context context,
-            int selectionFlags) {
-        super(Preconditions.checkNotNull(context));
-        mHandler = new MyHandler();
-        mPrintManager = Preconditions.checkNotNull(printManager);
-        mSelectionFlags = Preconditions.checkFlagsArgument(selectionFlags,
-                PrintManager.ALL_SERVICES);
-    }
-
-    @Override
-    protected void onForceLoad() {
-        queueNewResult();
-    }
-
-    /**
-     * Read the print services and queue it to be delivered on the main thread.
-     */
-    private void queueNewResult() {
-        Message m = mHandler.obtainMessage(0);
-        m.obj = mPrintManager.getPrintServices(mSelectionFlags);
-        mHandler.sendMessage(m);
-    }
-
-    @Override
-    protected void onStartLoading() {
-        mListener = new PrintManager.PrintServicesChangeListener() {
-            @Override public void onPrintServicesChanged() {
-                queueNewResult();
-            }
-        };
-
-        mPrintManager.addPrintServicesChangeListener(mListener, null);
-
-        // Immediately deliver a result
-        deliverResult(mPrintManager.getPrintServices(mSelectionFlags));
-    }
-
-    @Override
-    protected void onStopLoading() {
-        if (mListener != null) {
-            mPrintManager.removePrintServicesChangeListener(mListener);
-            mListener = null;
-        }
-
-        mHandler.removeMessages(0);
-    }
-
-    @Override
-    protected void onReset() {
-        onStopLoading();
-    }
-
-    /**
-     * Handler to sequentialize all the updates to the main thread.
-     */
-    private class MyHandler extends Handler {
-        /**
-         * Create a new handler on the main thread.
-         */
-        public MyHandler() {
-            super(getContext().getMainLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (isStarted()) {
-                deliverResult((List<PrintServiceInfo>) msg.obj);
-            }
-        }
-    }
-}
diff --git a/src/com/android/settings/print/PrintSettingsFragment.java b/src/com/android/settings/print/PrintSettingsFragment.java
index a6b3d7e..899acc7 100644
--- a/src/com/android/settings/print/PrintSettingsFragment.java
+++ b/src/com/android/settings/print/PrintSettingsFragment.java
@@ -168,7 +168,7 @@
             PrintManager printManager =
                     (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE);
             if (printManager != null) {
-                return new PrintServicesLoader(printManager, getContext(),
+                return new SettingsPrintServicesLoader(printManager, getContext(),
                         PrintManager.ALL_SERVICES);
             } else {
                 return null;
diff --git a/src/com/android/settings/print/SettingsPrintServicesLoader.java b/src/com/android/settings/print/SettingsPrintServicesLoader.java
new file mode 100644
index 0000000..758f4d3
--- /dev/null
+++ b/src/com/android/settings/print/SettingsPrintServicesLoader.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 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.settings.print;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.print.PrintManager;
+import android.print.PrintServicesLoader;
+import android.printservice.PrintServiceInfo;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+import androidx.loader.content.Loader;
+
+/**
+ * Loader for the list of print services. Can be parametrized to select a subset.
+ */
+public class SettingsPrintServicesLoader extends Loader<List<PrintServiceInfo>> {
+
+    private PrintServicesLoader mLoader;
+
+    public SettingsPrintServicesLoader(@NonNull PrintManager printManager, @NonNull Context context,
+            int selectionFlags) {
+        super(Preconditions.checkNotNull(context));
+
+        mLoader = new PrintServicesLoader(printManager, context, selectionFlags) {
+            @Override
+            public void deliverResult(List<PrintServiceInfo> data) {
+                super.deliverResult(data);
+
+                // deliver the result to outer Loader class
+                SettingsPrintServicesLoader.this.deliverResult(data);
+            }
+        };
+    }
+
+    @Override
+    protected void onForceLoad() {
+        mLoader.forceLoad();
+    }
+
+    @Override
+    protected void onStartLoading() {
+        mLoader.startLoading();
+    }
+
+    @Override
+    protected void onStopLoading() {
+        mLoader.stopLoading();
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        return mLoader.cancelLoad();
+    }
+
+    @Override
+    protected void onAbandon() {
+        mLoader.abandon();
+    }
+
+    @Override
+    protected void onReset() {
+        mLoader.reset();
+    }
+}
diff --git a/src/com/android/settings/security/SecuritySettings.java b/src/com/android/settings/security/SecuritySettings.java
index effbd51..72dd91b 100644
--- a/src/com/android/settings/security/SecuritySettings.java
+++ b/src/com/android/settings/security/SecuritySettings.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.provider.SearchIndexableResource;
 
@@ -181,7 +182,12 @@
             if (listening) {
                 final FingerprintManager fpm =
                         Utils.getFingerprintManagerOrNull(mContext);
-                if (fpm != null && fpm.isHardwareDetected()) {
+                final FaceManager faceManager =
+                        Utils.getFaceManagerOrNull(mContext);
+                if (faceManager != null && faceManager.isHardwareDetected()) {
+                    mSummaryLoader.setSummary(this,
+                            mContext.getString(R.string.security_dashboard_summary_face));
+                } else if (fpm != null && fpm.isHardwareDetected()) {
                     mSummaryLoader.setSummary(this,
                             mContext.getString(R.string.security_dashboard_summary));
                 } else {
diff --git a/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
index de558b3..e7902182 100644
--- a/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
@@ -563,6 +563,45 @@
         verify(preferenceGroup, times(1)).removePreference(argThat(titleMatches("Acct12")));
     }
 
+    @Test
+    public void onResume_userReEnabled_shouldAddOneAccountPreference() {
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", UserInfo.FLAG_DISABLED));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isRestrictedProfile()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+
+        Account[] accounts = {new Account("Acct1", "com.acct1")};
+        when(mAccountManager.getAccountsAsUser(1)).thenReturn(accounts);
+        when(mAccountManager.getAccountsByTypeAsUser(eq("com.acct1"), any(UserHandle.class)))
+            .thenReturn(accounts);
+
+        AuthenticatorDescription[] authDescs = {
+            new AuthenticatorDescription("com.acct1", "com.android.settings",
+                R.string.account_settings_title, 0 /* iconId */, 0 /* smallIconId */,
+                0 /* prefId */, false /* customTokens */)
+        };
+        when(mAccountManager.getAuthenticatorTypesAsUser(anyInt())).thenReturn(authDescs);
+
+        AccessiblePreferenceCategory preferenceGroup = mock(AccessiblePreferenceCategory.class);
+        when(preferenceGroup.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
+        when(mAccountHelper.createAccessiblePreferenceCategory(any(Context.class)))
+            .thenReturn(preferenceGroup);
+
+        // First time resume will build the UI with no account
+        mController.onResume();
+        verify(preferenceGroup, never()).addPreference(argThat(titleMatches("Acct1")));
+
+        // Enable the user
+        infos.remove(0 /* index */);
+        infos.add(new UserInfo(1, "user 1", 0 /* flags */));
+
+        // Resume should show the account for the user
+        mController.onResume();
+
+        verify(preferenceGroup).addPreference(argThat(titleMatches("Acct1")));
+    }
+
     private static ArgumentMatcher<Preference> titleMatches(String expected) {
         return preference -> TextUtils.equals(expected, preference.getTitle());
     }
diff --git a/tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java b/tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java
new file mode 100644
index 0000000..e22f07d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2016 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.settings.dashboard;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Pair;
+
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class CategoryManagerTest {
+
+    private Context mContext;
+    private CategoryManager mCategoryManager;
+    private Map<Pair<String, String>, Tile> mTileByComponentCache;
+    private Map<String, DashboardCategory> mCategoryByKeyMap;
+
+    @Before
+    public void setUp() {
+        mContext = ShadowApplication.getInstance().getApplicationContext();
+        mTileByComponentCache = new HashMap<>();
+        mCategoryByKeyMap = new HashMap<>();
+        mCategoryManager = CategoryManager.get(mContext);
+    }
+
+    @Test
+    public void getInstance_shouldBeSingleton() {
+        assertThat(mCategoryManager).isSameAs(CategoryManager.get(mContext));
+    }
+
+    @Test
+    public void backwardCompatCleanupForCategory_shouldNotChangeCategoryForNewKeys() {
+        final Tile tile1 = new Tile();
+        final Tile tile2 = new Tile();
+        tile1.category = CategoryKey.CATEGORY_ACCOUNT;
+        tile2.category = CategoryKey.CATEGORY_ACCOUNT;
+        final DashboardCategory category = new DashboardCategory();
+        category.addTile(tile1);
+        category.addTile(tile2);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_ACCOUNT, category);
+        mTileByComponentCache.put(new Pair<>("PACKAGE", "1"), tile1);
+        mTileByComponentCache.put(new Pair<>("PACKAGE", "2"), tile2);
+
+        mCategoryManager.backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
+
+        assertThat(mCategoryByKeyMap.size()).isEqualTo(1);
+        assertThat(mCategoryByKeyMap.get(CategoryKey.CATEGORY_ACCOUNT)).isNotNull();
+    }
+
+    @Test
+    public void backwardCompatCleanupForCategory_shouldNotChangeCategoryForMixedKeys() {
+        final Tile tile1 = new Tile();
+        final Tile tile2 = new Tile();
+        final String oldCategory = "com.android.settings.category.wireless";
+        tile1.category = CategoryKey.CATEGORY_ACCOUNT;
+        tile2.category = oldCategory;
+        final DashboardCategory category1 = new DashboardCategory();
+        category1.addTile(tile1);
+        final DashboardCategory category2 = new DashboardCategory();
+        category2.addTile(tile2);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_ACCOUNT, category1);
+        mCategoryByKeyMap.put(oldCategory, category2);
+        mTileByComponentCache.put(new Pair<>("PACKAGE", "CLASS1"), tile1);
+        mTileByComponentCache.put(new Pair<>("PACKAGE", "CLASS2"), tile2);
+
+        mCategoryManager.backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
+
+        assertThat(mCategoryByKeyMap.size()).isEqualTo(2);
+        assertThat(
+                mCategoryByKeyMap.get(CategoryKey.CATEGORY_ACCOUNT).getTilesCount()).isEqualTo(1);
+        assertThat(mCategoryByKeyMap.get(oldCategory).getTilesCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void backwardCompatCleanupForCategory_shouldChangeCategoryForOldKeys() {
+        final Tile tile1 = new Tile();
+        final String oldCategory = "com.android.settings.category.wireless";
+        tile1.category = oldCategory;
+        final DashboardCategory category1 = new DashboardCategory();
+        category1.addTile(tile1);
+        mCategoryByKeyMap.put(oldCategory, category1);
+        mTileByComponentCache.put(new Pair<>("PACKAGE", "CLASS1"), tile1);
+
+        mCategoryManager.backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
+
+        // Added 1 more category to category map.
+        assertThat(mCategoryByKeyMap.size()).isEqualTo(2);
+        // The new category map has CATEGORY_NETWORK type now, which contains 1 tile.
+        assertThat(
+                mCategoryByKeyMap.get(CategoryKey.CATEGORY_NETWORK).getTilesCount()).isEqualTo(1);
+        // Old category still exists.
+        assertThat(mCategoryByKeyMap.get(oldCategory).getTilesCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void sortCategories_singlePackage_shouldReorderBasedOnPriority() {
+        // Create some fake tiles that are not sorted.
+        final String testPackage = "com.android.test";
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile1.priority = 100;
+        final Tile tile2 = new Tile();
+        tile2.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class2"));
+        tile2.priority = 50;
+        final Tile tile3 = new Tile();
+        tile3.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class3"));
+        tile3.priority = 200;
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
+                mCategoryByKeyMap);
+
+        // Verify they are now sorted.
+        assertThat(category.getTile(0)).isSameAs(tile3);
+        assertThat(category.getTile(1)).isSameAs(tile1);
+        assertThat(category.getTile(2)).isSameAs(tile2);
+    }
+
+    @Test
+    public void sortCategories_multiPackage_shouldReorderBasedOnPackageAndPriority() {
+        // Create some fake tiles that are not sorted.
+        final String testPackage1 = "com.android.test1";
+        final String testPackage2 = "com.android.test2";
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent =
+                new Intent().setComponent(new ComponentName(testPackage2, "class1"));
+        tile1.priority = 100;
+        final Tile tile2 = new Tile();
+        tile2.intent =
+                new Intent().setComponent(new ComponentName(testPackage1, "class2"));
+        tile2.priority = 100;
+        final Tile tile3 = new Tile();
+        tile3.intent =
+                new Intent().setComponent(new ComponentName(testPackage1, "class3"));
+        tile3.priority = 50;
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
+                mCategoryByKeyMap);
+
+        // Verify they are now sorted.
+        assertThat(category.getTile(0)).isSameAs(tile2);
+        assertThat(category.getTile(1)).isSameAs(tile1);
+        assertThat(category.getTile(2)).isSameAs(tile3);
+    }
+
+    @Test
+    public void sortCategories_internalPackageTiles_shouldSkipTileForInternalPackage() {
+        // Create some fake tiles that are not sorted.
+        final String testPackage =
+                ShadowApplication.getInstance().getApplicationContext().getPackageName();
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile1.priority = 100;
+        final Tile tile2 = new Tile();
+        tile2.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class2"));
+        tile2.priority = 100;
+        final Tile tile3 = new Tile();
+        tile3.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class3"));
+        tile3.priority = 50;
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
+                mCategoryByKeyMap);
+
+        // Verify the sorting order is not changed
+        assertThat(category.getTile(0)).isSameAs(tile1);
+        assertThat(category.getTile(1)).isSameAs(tile2);
+        assertThat(category.getTile(2)).isSameAs(tile3);
+    }
+
+    @Test
+    public void sortCategories_internalAndExternalPackageTiles_shouldRetainPriorityOrdering() {
+        // Inject one external tile among internal tiles.
+        final String testPackage =
+                ShadowApplication.getInstance().getApplicationContext().getPackageName();
+        final String testPackage2 = "com.google.test2";
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent = new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile1.priority = 2;
+        final Tile tile2 = new Tile();
+        tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2"));
+        tile2.priority = 1;
+        final Tile tile3 = new Tile();
+        tile3.intent = new Intent().setComponent(new ComponentName(testPackage2, "class0"));
+        tile3.priority = 0;
+        final Tile tile4 = new Tile();
+        tile4.intent = new Intent().setComponent(new ComponentName(testPackage, "class3"));
+        tile4.priority = -1;
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        category.addTile(tile4);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
+                mCategoryByKeyMap);
+
+        // Verify the sorting order is not changed
+        assertThat(category.getTile(0)).isSameAs(tile1);
+        assertThat(category.getTile(1)).isSameAs(tile2);
+        assertThat(category.getTile(2)).isSameAs(tile3);
+        assertThat(category.getTile(3)).isSameAs(tile4);
+    }
+
+    @Test
+    public void sortCategories_samePriority_internalPackageTileShouldTakePrecedence() {
+        // Inject one external tile among internal tiles with same priority.
+        final String testPackage =
+                ShadowApplication.getInstance().getApplicationContext().getPackageName();
+        final String testPackage2 = "com.google.test2";
+        final String testPackage3 = "com.abcde.test3";
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent = new Intent().setComponent(new ComponentName(testPackage2, "class1"));
+        tile1.priority = 1;
+        final Tile tile2 = new Tile();
+        tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2"));
+        tile2.priority = 1;
+        final Tile tile3 = new Tile();
+        tile3.intent = new Intent().setComponent(new ComponentName(testPackage3, "class3"));
+        tile3.priority = 1;
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
+                mCategoryByKeyMap);
+
+        // Verify the sorting order is internal first, follow by package name ordering
+        assertThat(category.getTile(0)).isSameAs(tile2);
+        assertThat(category.getTile(1)).isSameAs(tile3);
+        assertThat(category.getTile(2)).isSameAs(tile1);
+    }
+
+    @Test
+    public void filterTiles_noDuplicate_noChange() {
+        // Create some unique tiles
+        final String testPackage =
+                ShadowApplication.getInstance().getApplicationContext().getPackageName();
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile1.priority = 100;
+        final Tile tile2 = new Tile();
+        tile2.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class2"));
+        tile2.priority = 100;
+        final Tile tile3 = new Tile();
+        tile3.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class3"));
+        tile3.priority = 50;
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
+
+        assertThat(category.getTilesCount()).isEqualTo(3);
+    }
+
+    @Test
+    public void filterTiles_hasDuplicate_shouldOnlyKeepUniqueTiles() {
+        // Create tiles pointing to same intent.
+        final String testPackage =
+                ShadowApplication.getInstance().getApplicationContext().getPackageName();
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile1.priority = 100;
+        final Tile tile2 = new Tile();
+        tile2.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile2.priority = 100;
+        final Tile tile3 = new Tile();
+        tile3.intent =
+                new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile3.priority = 50;
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
+
+        assertThat(category.getTilesCount()).isEqualTo(1);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
index bf1e0ff..e541b9f 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
@@ -53,7 +53,6 @@
 import com.android.settings.testutils.shadow.ShadowUserManager;
 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
 import com.android.settingslib.drawer.CategoryKey;
-import com.android.settingslib.drawer.CategoryManager;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.drawer.TileUtils;
diff --git a/tests/robotests/src/com/android/settings/security/SecuritySettingsTest.java b/tests/robotests/src/com/android/settings/security/SecuritySettingsTest.java
index 7d93ec2..f3cc459 100644
--- a/tests/robotests/src/com/android/settings/security/SecuritySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/security/SecuritySettingsTest.java
@@ -22,6 +22,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 
 import com.android.settings.R;
@@ -44,6 +45,8 @@
     private SummaryLoader mSummaryLoader;
     @Mock
     private FingerprintManager mFingerprintManager;
+    @Mock
+    private FaceManager mFaceManager;
     private SecuritySettings.SummaryProvider mSummaryProvider;
 
     @Before
@@ -51,7 +54,8 @@
         MockitoAnnotations.initMocks(this);
         when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
                 .thenReturn(mFingerprintManager);
-
+        when(mContext.getSystemService(Context.FACE_SERVICE))
+                .thenReturn(mFaceManager);
         mSummaryProvider = new SecuritySettings.SummaryProvider(mContext, mSummaryLoader);
     }
 
@@ -63,7 +67,20 @@
     }
 
     @Test
+    public void testSummaryProvider_hasFace_hasStaticSummary() {
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
+                .thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+        mSummaryProvider.setListening(true);
+
+        verify(mContext).getString(R.string.security_dashboard_summary_face);
+    }
+
+    @Test
     public void testSummaryProvider_hasFingerPrint_hasStaticSummary() {
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
+                .thenReturn(false);
         when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
                 .thenReturn(true);
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
@@ -74,9 +91,11 @@
     }
 
     @Test
-    public void testSummaryProvider_noFpFeature_shouldSetSummaryWithNoFingerprint() {
+    public void testSummaryProvider_noFpFeature_shouldSetSummaryWithNoBiometrics() {
         when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
                 .thenReturn(false);
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
+                .thenReturn(false);
 
         mSummaryProvider.setListening(true);
 
@@ -84,7 +103,9 @@
     }
 
     @Test
-    public void testSummaryProvider_noFpHardware_shouldSetSummaryWithNoFingerprint() {
+    public void testSummaryProvider_noFpHardware_shouldSetSummaryWithNoBiometrics() {
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
+                .thenReturn(false);
         when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
                 .thenReturn(true);
         when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
@@ -93,4 +114,29 @@
 
         verify(mContext).getString(R.string.security_dashboard_summary_no_fingerprint);
     }
+
+    @Test
+    public void testSummaryProvider_noFaceFeature_shouldSetSummaryWithNoBiometrics() {
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+                .thenReturn(false);
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
+                .thenReturn(false);
+
+        mSummaryProvider.setListening(true);
+
+        verify(mContext).getString(R.string.security_dashboard_summary_no_fingerprint);
+    }
+
+    @Test
+    public void testSummaryProvider_noFaceHardware_shouldSetSummaryWithNoBiometrics() {
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
+                .thenReturn(true);
+        when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+                .thenReturn(false);
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+
+        mSummaryProvider.setListening(true);
+
+        verify(mContext).getString(R.string.security_dashboard_summary_no_fingerprint);
+    }
 }