CategoryManager.java revision b8d2cd41e9472d6f860f35ee9a073b89ba9507f0
1f46c533931224296b11d98798344c049f88db9a1mukesh agrawal/** 2f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * Copyright (C) 2016 The Android Open Source Project 3f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * 4f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * Licensed under the Apache License, Version 2.0 (the "License"); 5f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * you may not use this file except in compliance with the License. 6f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * You may obtain a copy of the License at 7f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * 8f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * http://www.apache.org/licenses/LICENSE-2.0 9f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * 10f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * Unless required by applicable law or agreed to in writing, software 11f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * distributed under the License is distributed on an "AS IS" BASIS, 12f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * See the License for the specific language governing permissions and 14f46c533931224296b11d98798344c049f88db9a1mukesh agrawal * limitations under the License. 15f46c533931224296b11d98798344c049f88db9a1mukesh agrawal */ 16f46c533931224296b11d98798344c049f88db9a1mukesh agrawalpackage com.android.settingslib.drawer; 17f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 18f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport android.content.ComponentName; 19f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport android.content.Context; 20f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport android.support.annotation.VisibleForTesting; 21f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport android.util.ArrayMap; 22f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport android.util.ArraySet; 23f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport android.util.Log; 24f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport android.util.Pair; 25f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 26f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport com.android.settingslib.applications.InterestingConfigChanges; 27f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 28f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport java.util.ArrayList; 29f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport java.util.HashMap; 30f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport java.util.List; 31f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport java.util.Map; 32f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport java.util.Map.Entry; 33f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport java.util.Set; 34f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 35f46c533931224296b11d98798344c049f88db9a1mukesh agrawalimport static java.lang.String.CASE_INSENSITIVE_ORDER; 36f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 37f46c533931224296b11d98798344c049f88db9a1mukesh agrawalpublic class CategoryManager { 38f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 39f46c533931224296b11d98798344c049f88db9a1mukesh agrawal private static final String TAG = "CategoryManager"; 40f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 41f46c533931224296b11d98798344c049f88db9a1mukesh agrawal private static CategoryManager sInstance; 42f46c533931224296b11d98798344c049f88db9a1mukesh agrawal private final InterestingConfigChanges mInterestingConfigChanges; 43f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 44f46c533931224296b11d98798344c049f88db9a1mukesh agrawal // Tile cache (key: <packageName, activityName>, value: tile) 45bd9be6d8cab19c78a6bf14b62c4bbb6ef8d13769mukesh agrawal private final Map<Pair<String, String>, Tile> mTileByComponentCache; 46bd9be6d8cab19c78a6bf14b62c4bbb6ef8d13769mukesh agrawal 47bd9be6d8cab19c78a6bf14b62c4bbb6ef8d13769mukesh agrawal // Tile cache (key: category key, value: category) 48bd9be6d8cab19c78a6bf14b62c4bbb6ef8d13769mukesh agrawal private final Map<String, DashboardCategory> mCategoryByKeyMap; 49bd9be6d8cab19c78a6bf14b62c4bbb6ef8d13769mukesh agrawal 50f46c533931224296b11d98798344c049f88db9a1mukesh agrawal private List<DashboardCategory> mCategories; 51f46c533931224296b11d98798344c049f88db9a1mukesh agrawal private String mExtraAction; 52f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 53f46c533931224296b11d98798344c049f88db9a1mukesh agrawal public static CategoryManager get(Context context) { 54f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal return get(context, null); 55f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal } 56f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal 57f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal public static CategoryManager get(Context context, String action) { 58f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal if (sInstance == null) { 59f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal sInstance = new CategoryManager(context, action); 60f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal } 61f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal return sInstance; 62f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal } 63f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal 64f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal CategoryManager(Context context, String action) { 65f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal mTileByComponentCache = new ArrayMap<>(); 66f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal mCategoryByKeyMap = new ArrayMap<>(); 67f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal mInterestingConfigChanges = new InterestingConfigChanges(); 68f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal mInterestingConfigChanges.applyNewConfig(context.getResources()); 69f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal mExtraAction = action; 70f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal } 71f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal 72f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) { 73f5d90be206db98bbd5894afc8d757dd32360b2d9mukesh agrawal return getTilesByCategory(context, categoryKey, TileUtils.SETTING_PKG); 74f46c533931224296b11d98798344c049f88db9a1mukesh agrawal } 75f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 76f46c533931224296b11d98798344c049f88db9a1mukesh agrawal public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey, 77f46c533931224296b11d98798344c049f88db9a1mukesh agrawal String settingPkg) { 78f46c533931224296b11d98798344c049f88db9a1mukesh agrawal tryInitCategories(context, settingPkg); 79f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 80f46c533931224296b11d98798344c049f88db9a1mukesh agrawal return mCategoryByKeyMap.get(categoryKey); 81f46c533931224296b11d98798344c049f88db9a1mukesh agrawal } 82f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 83f46c533931224296b11d98798344c049f88db9a1mukesh agrawal public synchronized List<DashboardCategory> getCategories(Context context) { 84f46c533931224296b11d98798344c049f88db9a1mukesh agrawal return getCategories(context, TileUtils.SETTING_PKG); 85f46c533931224296b11d98798344c049f88db9a1mukesh agrawal } 86f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 87f46c533931224296b11d98798344c049f88db9a1mukesh agrawal public synchronized List<DashboardCategory> getCategories(Context context, String settingPkg) { 88f46c533931224296b11d98798344c049f88db9a1mukesh agrawal tryInitCategories(context, settingPkg); 89f46c533931224296b11d98798344c049f88db9a1mukesh agrawal return mCategories; 90f46c533931224296b11d98798344c049f88db9a1mukesh agrawal } 91f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 92f46c533931224296b11d98798344c049f88db9a1mukesh agrawal public synchronized void reloadAllCategories(Context context, String settingPkg) { 93f46c533931224296b11d98798344c049f88db9a1mukesh agrawal final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig( 94f46c533931224296b11d98798344c049f88db9a1mukesh agrawal context.getResources()); 95f46c533931224296b11d98798344c049f88db9a1mukesh agrawal mCategories = null; 96f46c533931224296b11d98798344c049f88db9a1mukesh agrawal tryInitCategories(context, forceClearCache, settingPkg); 97f46c533931224296b11d98798344c049f88db9a1mukesh agrawal } 98f46c533931224296b11d98798344c049f88db9a1mukesh agrawal 99f46c533931224296b11d98798344c049f88db9a1mukesh agrawal public synchronized void updateCategoryFromBlacklist(Set<ComponentName> tileBlacklist) { 100 if (mCategories == null) { 101 Log.w(TAG, "Category is null, skipping blacklist update"); 102 } 103 for (int i = 0; i < mCategories.size(); i++) { 104 DashboardCategory category = mCategories.get(i); 105 for (int j = 0; j < category.getTilesCount(); j++) { 106 Tile tile = category.getTile(j); 107 if (tileBlacklist.contains(tile.intent.getComponent())) { 108 category.removeTile(j--); 109 } 110 } 111 } 112 } 113 114 private synchronized void tryInitCategories(Context context, String settingPkg) { 115 // Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange 116 // happens. 117 tryInitCategories(context, false /* forceClearCache */, settingPkg); 118 } 119 120 private synchronized void tryInitCategories(Context context, boolean forceClearCache, 121 String settingPkg) { 122 if (mCategories == null) { 123 if (forceClearCache) { 124 mTileByComponentCache.clear(); 125 } 126 mCategoryByKeyMap.clear(); 127 mCategories = TileUtils.getCategories(context, mTileByComponentCache, 128 false /* categoryDefinedInManifest */, mExtraAction, settingPkg); 129 for (DashboardCategory category : mCategories) { 130 mCategoryByKeyMap.put(category.key, category); 131 } 132 backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap); 133 sortCategories(context, mCategoryByKeyMap); 134 filterDuplicateTiles(mCategoryByKeyMap); 135 } 136 } 137 138 @VisibleForTesting 139 synchronized void backwardCompatCleanupForCategory( 140 Map<Pair<String, String>, Tile> tileByComponentCache, 141 Map<String, DashboardCategory> categoryByKeyMap) { 142 // A package can use a) CategoryKey, b) old category keys, c) both. 143 // Check if a package uses old category key only. 144 // If yes, map them to new category key. 145 146 // Build a package name -> tile map first. 147 final Map<String, List<Tile>> packageToTileMap = new HashMap<>(); 148 for (Entry<Pair<String, String>, Tile> tileEntry : tileByComponentCache.entrySet()) { 149 final String packageName = tileEntry.getKey().first; 150 List<Tile> tiles = packageToTileMap.get(packageName); 151 if (tiles == null) { 152 tiles = new ArrayList<>(); 153 packageToTileMap.put(packageName, tiles); 154 } 155 tiles.add(tileEntry.getValue()); 156 } 157 158 for (Entry<String, List<Tile>> entry : packageToTileMap.entrySet()) { 159 final List<Tile> tiles = entry.getValue(); 160 // Loop map, find if all tiles from same package uses old key only. 161 boolean useNewKey = false; 162 boolean useOldKey = false; 163 for (Tile tile : tiles) { 164 if (CategoryKey.KEY_COMPAT_MAP.containsKey(tile.category)) { 165 useOldKey = true; 166 } else { 167 useNewKey = true; 168 break; 169 } 170 } 171 // Uses only old key, map them to new keys one by one. 172 if (useOldKey && !useNewKey) { 173 for (Tile tile : tiles) { 174 final String newCategoryKey = CategoryKey.KEY_COMPAT_MAP.get(tile.category); 175 tile.category = newCategoryKey; 176 // move tile to new category. 177 DashboardCategory newCategory = categoryByKeyMap.get(newCategoryKey); 178 if (newCategory == null) { 179 newCategory = new DashboardCategory(); 180 categoryByKeyMap.put(newCategoryKey, newCategory); 181 } 182 newCategory.addTile(tile); 183 } 184 } 185 } 186 } 187 188 /** 189 * Sort the tiles injected from all apps such that if they have the same priority value, 190 * they wil lbe sorted by package name. 191 * <p/> 192 * A list of tiles are considered sorted when their priority value decreases in a linear 193 * scan. 194 */ 195 @VisibleForTesting 196 synchronized void sortCategories(Context context, 197 Map<String, DashboardCategory> categoryByKeyMap) { 198 for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { 199 categoryEntry.getValue().sortTiles(context.getPackageName()); 200 } 201 } 202 203 /** 204 * Filter out duplicate tiles from category. Duplicate tiles are the ones pointing to the 205 * same intent. 206 */ 207 @VisibleForTesting 208 synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) { 209 for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { 210 final DashboardCategory category = categoryEntry.getValue(); 211 final int count = category.getTilesCount(); 212 final Set<ComponentName> components = new ArraySet<>(); 213 for (int i = count - 1; i >= 0; i--) { 214 final Tile tile = category.getTile(i); 215 if (tile.intent == null) { 216 continue; 217 } 218 final ComponentName tileComponent = tile.intent.getComponent(); 219 if (components.contains(tileComponent)) { 220 category.removeTile(i); 221 } else { 222 components.add(tileComponent); 223 } 224 } 225 } 226 } 227 228 /** 229 * Sort priority value for tiles within a single {@code DashboardCategory}. 230 * 231 * @see #sortCategories(Context, Map) 232 */ 233 private synchronized void sortCategoriesForExternalTiles(Context context, 234 DashboardCategory dashboardCategory) { 235 dashboardCategory.sortTiles(context.getPackageName()); 236 237 } 238} 239