blob: 95edc5e1e31f71e93dc3f62e41fd2e9ad4a7f60a [file] [log] [blame]
Fan Zhang22a56d72016-09-27 17:52:00 -07001/**
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
Fan Zhangc8a5b792016-10-03 15:05:53 -07008 * http://www.apache.org/licenses/LICENSE-2.0
Fan Zhang22a56d72016-09-27 17:52:00 -07009 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.settingslib.drawer;
17
18import android.content.ComponentName;
19import android.content.Context;
20import android.util.ArrayMap;
Fan Zhang5fa4af02016-11-16 12:40:35 -080021import android.util.ArraySet;
Fan Zhang22a56d72016-09-27 17:52:00 -070022import android.util.Log;
23import android.util.Pair;
24
Fan Zhang1b0dfa32018-07-20 12:57:55 -070025import androidx.annotation.VisibleForTesting;
26
Fan Zhang5adc2662016-10-04 15:12:58 -070027import com.android.settingslib.applications.InterestingConfigChanges;
28
Fan Zhang31f4c542016-11-01 15:02:46 -070029import java.util.ArrayList;
30import java.util.HashMap;
Fan Zhang22a56d72016-09-27 17:52:00 -070031import java.util.List;
32import java.util.Map;
Fan Zhang31f4c542016-11-01 15:02:46 -070033import java.util.Map.Entry;
Fan Zhang22a56d72016-09-27 17:52:00 -070034import java.util.Set;
35
36public class CategoryManager {
37
Fan Zhang1b0dfa32018-07-20 12:57:55 -070038 public static final String SETTING_PKG = "com.android.settings";
39
Fan Zhang22a56d72016-09-27 17:52:00 -070040 private static final String TAG = "CategoryManager";
41
42 private static CategoryManager sInstance;
Fan Zhang5adc2662016-10-04 15:12:58 -070043 private final InterestingConfigChanges mInterestingConfigChanges;
Fan Zhang22a56d72016-09-27 17:52:00 -070044
Fan Zhang22a56d72016-09-27 17:52:00 -070045 // Tile cache (key: <packageName, activityName>, value: tile)
46 private final Map<Pair<String, String>, Tile> mTileByComponentCache;
47
48 // Tile cache (key: category key, value: category)
49 private final Map<String, DashboardCategory> mCategoryByKeyMap;
50
51 private List<DashboardCategory> mCategories;
Doris Ling485df112016-12-19 10:45:47 -080052 private String mExtraAction;
Fan Zhang22a56d72016-09-27 17:52:00 -070053
Fan Zhang5adc2662016-10-04 15:12:58 -070054 public static CategoryManager get(Context context) {
Doris Ling485df112016-12-19 10:45:47 -080055 return get(context, null);
56 }
57
58 public static CategoryManager get(Context context, String action) {
Fan Zhang22a56d72016-09-27 17:52:00 -070059 if (sInstance == null) {
Doris Ling485df112016-12-19 10:45:47 -080060 sInstance = new CategoryManager(context, action);
Fan Zhang22a56d72016-09-27 17:52:00 -070061 }
62 return sInstance;
63 }
64
Doris Ling485df112016-12-19 10:45:47 -080065 CategoryManager(Context context, String action) {
Fan Zhang22a56d72016-09-27 17:52:00 -070066 mTileByComponentCache = new ArrayMap<>();
67 mCategoryByKeyMap = new ArrayMap<>();
Fan Zhang5adc2662016-10-04 15:12:58 -070068 mInterestingConfigChanges = new InterestingConfigChanges();
69 mInterestingConfigChanges.applyNewConfig(context.getResources());
Doris Ling485df112016-12-19 10:45:47 -080070 mExtraAction = action;
Fan Zhang22a56d72016-09-27 17:52:00 -070071 }
72
Fan Zhangc8a5b792016-10-03 15:05:53 -070073 public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
Fan Zhang1b0dfa32018-07-20 12:57:55 -070074 tryInitCategories(context, SETTING_PKG);
Fan Zhang22a56d72016-09-27 17:52:00 -070075
Fan Zhang45c891b2016-10-10 18:11:49 -070076 return mCategoryByKeyMap.get(categoryKey);
Fan Zhang22a56d72016-09-27 17:52:00 -070077 }
78
Fan Zhangc8a5b792016-10-03 15:05:53 -070079 public synchronized List<DashboardCategory> getCategories(Context context) {
Fan Zhang1b0dfa32018-07-20 12:57:55 -070080 tryInitCategories(context, SETTING_PKG);
Fan Zhang22a56d72016-09-27 17:52:00 -070081 return mCategories;
82 }
83
roger xue8f06ab02016-12-08 14:09:50 -080084 public synchronized void reloadAllCategories(Context context, String settingPkg) {
Fan Zhang5adc2662016-10-04 15:12:58 -070085 final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig(
86 context.getResources());
Fan Zhangc8a5b792016-10-03 15:05:53 -070087 mCategories = null;
roger xue8f06ab02016-12-08 14:09:50 -080088 tryInitCategories(context, forceClearCache, settingPkg);
Fan Zhang22a56d72016-09-27 17:52:00 -070089 }
90
Fan Zhangc8a5b792016-10-03 15:05:53 -070091 public synchronized void updateCategoryFromBlacklist(Set<ComponentName> tileBlacklist) {
Fan Zhang22a56d72016-09-27 17:52:00 -070092 if (mCategories == null) {
93 Log.w(TAG, "Category is null, skipping blacklist update");
94 }
95 for (int i = 0; i < mCategories.size(); i++) {
96 DashboardCategory category = mCategories.get(i);
Doris Lingb8d2cd42017-11-27 12:24:09 -080097 for (int j = 0; j < category.getTilesCount(); j++) {
98 Tile tile = category.getTile(j);
Fan Zhang22a56d72016-09-27 17:52:00 -070099 if (tileBlacklist.contains(tile.intent.getComponent())) {
Doris Lingb8d2cd42017-11-27 12:24:09 -0800100 category.removeTile(j--);
Fan Zhang22a56d72016-09-27 17:52:00 -0700101 }
102 }
103 }
104 }
105
roger xue8f06ab02016-12-08 14:09:50 -0800106 private synchronized void tryInitCategories(Context context, String settingPkg) {
Fan Zhang5adc2662016-10-04 15:12:58 -0700107 // Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
108 // happens.
roger xue8f06ab02016-12-08 14:09:50 -0800109 tryInitCategories(context, false /* forceClearCache */, settingPkg);
Fan Zhang5adc2662016-10-04 15:12:58 -0700110 }
111
roger xue8f06ab02016-12-08 14:09:50 -0800112 private synchronized void tryInitCategories(Context context, boolean forceClearCache,
113 String settingPkg) {
Fan Zhang22a56d72016-09-27 17:52:00 -0700114 if (mCategories == null) {
Fan Zhang5adc2662016-10-04 15:12:58 -0700115 if (forceClearCache) {
116 mTileByComponentCache.clear();
117 }
Fan Zhang22a56d72016-09-27 17:52:00 -0700118 mCategoryByKeyMap.clear();
119 mCategories = TileUtils.getCategories(context, mTileByComponentCache,
Fan Zhangc75174b2018-07-19 14:07:58 -0700120 mExtraAction, settingPkg);
Fan Zhang22a56d72016-09-27 17:52:00 -0700121 for (DashboardCategory category : mCategories) {
122 mCategoryByKeyMap.put(category.key, category);
123 }
Fan Zhang914afbf2016-11-01 15:36:25 -0700124 backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
Doris Lingce311592017-03-13 12:56:10 -0700125 sortCategories(context, mCategoryByKeyMap);
Fan Zhang5fa4af02016-11-16 12:40:35 -0800126 filterDuplicateTiles(mCategoryByKeyMap);
Fan Zhang22a56d72016-09-27 17:52:00 -0700127 }
128 }
129
Fan Zhang914afbf2016-11-01 15:36:25 -0700130 @VisibleForTesting
131 synchronized void backwardCompatCleanupForCategory(
132 Map<Pair<String, String>, Tile> tileByComponentCache,
133 Map<String, DashboardCategory> categoryByKeyMap) {
Fan Zhang31f4c542016-11-01 15:02:46 -0700134 // A package can use a) CategoryKey, b) old category keys, c) both.
135 // Check if a package uses old category key only.
136 // If yes, map them to new category key.
137
138 // Build a package name -> tile map first.
139 final Map<String, List<Tile>> packageToTileMap = new HashMap<>();
Fan Zhang914afbf2016-11-01 15:36:25 -0700140 for (Entry<Pair<String, String>, Tile> tileEntry : tileByComponentCache.entrySet()) {
Fan Zhang31f4c542016-11-01 15:02:46 -0700141 final String packageName = tileEntry.getKey().first;
142 List<Tile> tiles = packageToTileMap.get(packageName);
143 if (tiles == null) {
144 tiles = new ArrayList<>();
145 packageToTileMap.put(packageName, tiles);
146 }
147 tiles.add(tileEntry.getValue());
148 }
149
150 for (Entry<String, List<Tile>> entry : packageToTileMap.entrySet()) {
151 final List<Tile> tiles = entry.getValue();
152 // Loop map, find if all tiles from same package uses old key only.
153 boolean useNewKey = false;
154 boolean useOldKey = false;
155 for (Tile tile : tiles) {
156 if (CategoryKey.KEY_COMPAT_MAP.containsKey(tile.category)) {
157 useOldKey = true;
158 } else {
159 useNewKey = true;
160 break;
161 }
162 }
163 // Uses only old key, map them to new keys one by one.
164 if (useOldKey && !useNewKey) {
165 for (Tile tile : tiles) {
166 final String newCategoryKey = CategoryKey.KEY_COMPAT_MAP.get(tile.category);
167 tile.category = newCategoryKey;
168 // move tile to new category.
Fan Zhang914afbf2016-11-01 15:36:25 -0700169 DashboardCategory newCategory = categoryByKeyMap.get(newCategoryKey);
170 if (newCategory == null) {
171 newCategory = new DashboardCategory();
172 categoryByKeyMap.put(newCategoryKey, newCategory);
173 }
Doris Lingb8d2cd42017-11-27 12:24:09 -0800174 newCategory.addTile(tile);
Fan Zhang31f4c542016-11-01 15:02:46 -0700175 }
176 }
177 }
178 }
Fan Zhangfc76ab32016-11-14 09:48:06 -0800179
180 /**
Doris Lingce311592017-03-13 12:56:10 -0700181 * Sort the tiles injected from all apps such that if they have the same priority value,
182 * they wil lbe sorted by package name.
Fan Zhangfc76ab32016-11-14 09:48:06 -0800183 * <p/>
Doris Lingce311592017-03-13 12:56:10 -0700184 * A list of tiles are considered sorted when their priority value decreases in a linear
Fan Zhangfc76ab32016-11-14 09:48:06 -0800185 * scan.
186 */
187 @VisibleForTesting
Doris Lingce311592017-03-13 12:56:10 -0700188 synchronized void sortCategories(Context context,
Fan Zhangfc76ab32016-11-14 09:48:06 -0800189 Map<String, DashboardCategory> categoryByKeyMap) {
190 for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
Doris Lingb8d2cd42017-11-27 12:24:09 -0800191 categoryEntry.getValue().sortTiles(context.getPackageName());
Fan Zhangfc76ab32016-11-14 09:48:06 -0800192 }
193 }
194
195 /**
Fan Zhang5fa4af02016-11-16 12:40:35 -0800196 * Filter out duplicate tiles from category. Duplicate tiles are the ones pointing to the
197 * same intent.
198 */
199 @VisibleForTesting
200 synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
201 for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
202 final DashboardCategory category = categoryEntry.getValue();
Doris Lingb8d2cd42017-11-27 12:24:09 -0800203 final int count = category.getTilesCount();
Fan Zhang5fa4af02016-11-16 12:40:35 -0800204 final Set<ComponentName> components = new ArraySet<>();
205 for (int i = count - 1; i >= 0; i--) {
Doris Lingb8d2cd42017-11-27 12:24:09 -0800206 final Tile tile = category.getTile(i);
Fan Zhang5fa4af02016-11-16 12:40:35 -0800207 if (tile.intent == null) {
208 continue;
209 }
210 final ComponentName tileComponent = tile.intent.getComponent();
211 if (components.contains(tileComponent)) {
Doris Lingb8d2cd42017-11-27 12:24:09 -0800212 category.removeTile(i);
Fan Zhang5fa4af02016-11-16 12:40:35 -0800213 } else {
214 components.add(tileComponent);
215 }
216 }
217 }
218 }
219
220 /**
Doris Lingce311592017-03-13 12:56:10 -0700221 * Sort priority value for tiles within a single {@code DashboardCategory}.
Fan Zhangfc76ab32016-11-14 09:48:06 -0800222 *
Doris Lingce311592017-03-13 12:56:10 -0700223 * @see #sortCategories(Context, Map)
Fan Zhangfc76ab32016-11-14 09:48:06 -0800224 */
Doris Lingce311592017-03-13 12:56:10 -0700225 private synchronized void sortCategoriesForExternalTiles(Context context,
Fan Zhangfc76ab32016-11-14 09:48:06 -0800226 DashboardCategory dashboardCategory) {
Doris Lingb8d2cd42017-11-27 12:24:09 -0800227 dashboardCategory.sortTiles(context.getPackageName());
Fan Zhangfc76ab32016-11-14 09:48:06 -0800228
Fan Zhangfc76ab32016-11-14 09:48:06 -0800229 }
Fan Zhang22a56d72016-09-27 17:52:00 -0700230}