diff --git a/res/values/strings.xml b/res/values/strings.xml index f284e308070..2bef192f82d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -841,7 +841,9 @@ Disabled by admin Security status - + + Screen lock, face unlock + Screen lock, fingerprint Screen lock @@ -5943,7 +5945,7 @@ Data usage - + Mobile data & Wi\u2011Fi Carrier data accounting may differ from your device. diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java index 904dea32a76..46a3e6b7776 100644 --- a/src/com/android/settings/accounts/AccountPreferenceController.java +++ b/src/com/android/settings/accounts/AccountPreferenceController.java @@ -192,12 +192,10 @@ public class AccountPreferenceController extends AbstractPreferenceController 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 @@ public class AccountPreferenceController extends AbstractPreferenceController 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 967269476ad..23eb860d306 100644 --- a/src/com/android/settings/core/SettingsBaseActivity.java +++ b/src/com/android/settings/core/SettingsBaseActivity.java @@ -38,7 +38,7 @@ import android.widget.Toolbar; 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 @@ public class SettingsBaseActivity extends FragmentActivity { new CategoriesUpdateTask().execute(); } - public String getSettingPkg() { - return CategoryManager.SETTING_PKG; - } - public interface CategoryListener { void onCategoriesChanged(); } @@ -190,7 +186,7 @@ public class SettingsBaseActivity extends FragmentActivity { @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 00000000000..f0004583444 --- /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: , value: tile) + private final Map, Tile> mTileByComponentCache; + + // Tile cache (key: category key, value: category) + private final Map mCategoryByKeyMap; + + private List 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 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 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, Tile> tileByComponentCache, + Map 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> packageToTileMap = new HashMap<>(); + for (Entry, Tile> tileEntry : tileByComponentCache.entrySet()) { + final String packageName = tileEntry.getKey().first; + List tiles = packageToTileMap.get(packageName); + if (tiles == null) { + tiles = new ArrayList<>(); + packageToTileMap.put(packageName, tiles); + } + tiles.add(tileEntry.getValue()); + } + + for (Entry> entry : packageToTileMap.entrySet()) { + final List 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. + *

+ * A list of tiles are considered sorted when their priority value decreases in a linear + * scan. + */ + @VisibleForTesting + synchronized void sortCategories(Context context, + Map categoryByKeyMap) { + for (Entry 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 categoryByKeyMap) { + for (Entry categoryEntry : categoryByKeyMap.entrySet()) { + final DashboardCategory category = categoryEntry.getValue(); + final int count = category.getTilesCount(); + final Set 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 a44335518e9..46beac4af47 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.SettingsActivity; 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 1311be0af87..345b4aea927 100644 --- a/src/com/android/settings/print/PrintServiceSettingsFragment.java +++ b/src/com/android/settings/print/PrintServiceSettingsFragment.java @@ -286,7 +286,7 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment @Override public Loader> 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 57cddb9b167..00000000000 --- 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> { - /** 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) msg.obj); - } - } - } -} diff --git a/src/com/android/settings/print/PrintSettingsFragment.java b/src/com/android/settings/print/PrintSettingsFragment.java index a6b3d7eea07..899acc70942 100644 --- a/src/com/android/settings/print/PrintSettingsFragment.java +++ b/src/com/android/settings/print/PrintSettingsFragment.java @@ -168,7 +168,7 @@ public class PrintSettingsFragment extends ProfileSettingsPreferenceFragment 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 00000000000..758f4d3f4f0 --- /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> { + + 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 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 effbd513e7f..72dd91b8e67 100644 --- a/src/com/android/settings/security/SecuritySettings.java +++ b/src/com/android/settings/security/SecuritySettings.java @@ -21,6 +21,7 @@ import static com.android.settings.security.EncryptionStatusPreferenceController 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 @@ public class SecuritySettings extends DashboardFragment { 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 de558b34d16..e7902182095 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 @@ public class AccountPreferenceControllerTest { verify(preferenceGroup, times(1)).removePreference(argThat(titleMatches("Acct12"))); } + @Test + public void onResume_userReEnabled_shouldAddOneAccountPreference() { + final List 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 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 00000000000..e22f07d0ec7 --- /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, Tile> mTileByComponentCache; + private Map 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 bf1e0ff6c0b..e541b9fc953 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.ShadowTileUtils; 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 7d93ec21e79..f3cc4593ff5 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 static org.mockito.Mockito.when; 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 @@ public class SecuritySettingsTest { private SummaryLoader mSummaryLoader; @Mock private FingerprintManager mFingerprintManager; + @Mock + private FaceManager mFaceManager; private SecuritySettings.SummaryProvider mSummaryProvider; @Before @@ -51,7 +54,8 @@ public class SecuritySettingsTest { 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); } @@ -62,8 +66,21 @@ public class SecuritySettingsTest { verifyNoMoreInteractions(mSummaryLoader); } + @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 @@ public class SecuritySettingsTest { } @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 @@ public class SecuritySettingsTest { } @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 @@ public class SecuritySettingsTest { 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); + } }