From e9d83796ac1b70d86f80ab3a67ca44d6e7a8f920 Mon Sep 17 00:00:00 2001 From: Dario Freni Date: Wed, 11 Jul 2018 10:52:53 +0100 Subject: [PATCH 1/6] Include NOTICE file in /product partition. Test: flashed on a device with /product partition and verified licences are correctly loaded Bug: 111179267 Change-Id: I2667029128a56baefbec376b0ec4e4ecdb3c3ad4 Merged-In: I12e9169b1d3d313a6c5da0d575a6526327268381 --- src/com/android/settings/LicenseHtmlLoader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/LicenseHtmlLoader.java b/src/com/android/settings/LicenseHtmlLoader.java index 97179269013..fa8f4c23edb 100644 --- a/src/com/android/settings/LicenseHtmlLoader.java +++ b/src/com/android/settings/LicenseHtmlLoader.java @@ -36,7 +36,8 @@ class LicenseHtmlLoader extends AsyncLoader { "/system/etc/NOTICE.xml.gz", "/vendor/etc/NOTICE.xml.gz", "/odm/etc/NOTICE.xml.gz", - "/oem/etc/NOTICE.xml.gz"}; + "/oem/etc/NOTICE.xml.gz", + "/product/etc/NOTICE.xml.gz"}; private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html"; private Context mContext; From 104e04dab4248c6fd388a26d934654c870c5cf95 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Wed, 18 Jul 2018 16:39:29 +0800 Subject: [PATCH 2/6] Delegate PrintServicesLoader to the framework's one 1. Remove the duplicated PrintServicesLoader in Settings 2. Make a androidx.loader in Settings and internally just delegate all calls to the real PrintServicesLoader 3. Get the result from the real loader Test: manual Fixes: 111581651 Change-Id: I2a9b3653f5c68f8383a468cd16ef5f7c3fd4bc3a --- .../print/PrintServiceSettingsFragment.java | 2 +- .../settings/print/PrintServicesLoader.java | 126 ------------------ .../settings/print/PrintSettingsFragment.java | 2 +- .../print/SettingsPrintServicesLoader.java | 82 ++++++++++++ 4 files changed, 84 insertions(+), 128 deletions(-) delete mode 100644 src/com/android/settings/print/PrintServicesLoader.java create mode 100644 src/com/android/settings/print/SettingsPrintServicesLoader.java 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(); + } +} From 75bafefa49df27276eece750b863e6b6adfb6f89 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 23 Jul 2018 13:51:40 -0700 Subject: [PATCH 3/6] Move CategoryManager to Settings. Bug: 77600770 Test: robo Change-Id: Id4a0c89938d43d21147944b820a191486c589238 --- .../settings/core/SettingsBaseActivity.java | 8 +- .../settings/dashboard/CategoryManager.java | 232 ++++++++++++ .../DashboardFeatureProviderImpl.java | 1 - .../dashboard/CategoryManagerTest.java | 345 ++++++++++++++++++ .../DashboardFeatureProviderImplTest.java | 1 - 5 files changed, 579 insertions(+), 8 deletions(-) create mode 100644 src/com/android/settings/dashboard/CategoryManager.java create mode 100644 tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java 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/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; From ce5f9c07dac577ab58cf053a9499f704f95c604a Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Mon, 23 Jul 2018 16:13:39 -0700 Subject: [PATCH 4/6] Fix new account not shown for work profile. When refreshing the Accounts settings UI, we uses the cached user info for checking user status. However, when the work profile is being updated, the UserInfo obejct for the user might be updated even the user id is the same. Using the cached data causes stale info to be returned for the user and results in the latest account data not being shown properly for the user. Update the cache to the latest user info retrieved from user manager. Change-Id: Ic0127842203f0288f2fdea6c6346cd11e42a8bf0 Fix: 38302246 Test: make RunSettingsRoboTests --- .../accounts/AccountPreferenceController.java | 11 +++--- .../AccountPreferenceControllerTest.java | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) 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/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()); } From ea846c64829fb9f6bafd7588a00408e27340af53 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Tue, 24 Jul 2018 11:56:49 +0800 Subject: [PATCH 5/6] Modified the char limit - Modified the comment in strings.xml and extended the length of the CHAR LIMIT Change-Id: I39d065aad7473bb1111db690c7b7a1ff81de459c Fixes: 111697704 Test: visual --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index f284e308070..eabb57f783b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5943,7 +5943,7 @@ Data usage - + Mobile data & Wi\u2011Fi Carrier data accounting may differ from your device. From eb0de79ffc5aa1725abea3b11c6181ed3994ea48 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Mon, 23 Jul 2018 18:05:59 -0700 Subject: [PATCH 6/6] Update Security & location preference summary depending on biometrics Fixes: 110960063 Test: manual Test: make -j56 SettingsRoboTests ROBOTEST_FILTER=SecuritySettingsTest Change-Id: Ia9186af37c84f4ff0391d1b72043948934ed997b --- res/values/strings.xml | 4 +- .../settings/security/SecuritySettings.java | 8 ++- .../security/SecuritySettingsTest.java | 52 +++++++++++++++++-- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index f284e308070..cc4739d9ddf 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 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/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); + } }