Home | History | Annotate | Download | only in drawer
      1 /**
      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  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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  */
     16 package com.android.settingslib.drawer;
     17 
     18 import android.content.ComponentName;
     19 import android.content.Context;
     20 import android.support.annotation.VisibleForTesting;
     21 import android.util.ArrayMap;
     22 import android.util.ArraySet;
     23 import android.util.Log;
     24 import android.util.Pair;
     25 
     26 import com.android.settingslib.applications.InterestingConfigChanges;
     27 
     28 import java.util.ArrayList;
     29 import java.util.HashMap;
     30 import java.util.List;
     31 import java.util.Map;
     32 import java.util.Map.Entry;
     33 import java.util.Set;
     34 
     35 import static java.lang.String.CASE_INSENSITIVE_ORDER;
     36 
     37 public class CategoryManager {
     38 
     39     private static final String TAG = "CategoryManager";
     40 
     41     private static CategoryManager sInstance;
     42     private final InterestingConfigChanges mInterestingConfigChanges;
     43 
     44     // Tile cache (key: <packageName, activityName>, value: tile)
     45     private final Map<Pair<String, String>, Tile> mTileByComponentCache;
     46 
     47     // Tile cache (key: category key, value: category)
     48     private final Map<String, DashboardCategory> mCategoryByKeyMap;
     49 
     50     private List<DashboardCategory> mCategories;
     51     private String mExtraAction;
     52 
     53     public static CategoryManager get(Context context) {
     54         return get(context, null);
     55     }
     56 
     57     public static CategoryManager get(Context context, String action) {
     58         if (sInstance == null) {
     59             sInstance = new CategoryManager(context, action);
     60         }
     61         return sInstance;
     62     }
     63 
     64     CategoryManager(Context context, String action) {
     65         mTileByComponentCache = new ArrayMap<>();
     66         mCategoryByKeyMap = new ArrayMap<>();
     67         mInterestingConfigChanges = new InterestingConfigChanges();
     68         mInterestingConfigChanges.applyNewConfig(context.getResources());
     69         mExtraAction = action;
     70     }
     71 
     72     public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
     73         return getTilesByCategory(context, categoryKey, TileUtils.SETTING_PKG);
     74     }
     75 
     76     public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey,
     77             String settingPkg) {
     78         tryInitCategories(context, settingPkg);
     79 
     80         return mCategoryByKeyMap.get(categoryKey);
     81     }
     82 
     83     public synchronized List<DashboardCategory> getCategories(Context context) {
     84         return getCategories(context, TileUtils.SETTING_PKG);
     85     }
     86 
     87     public synchronized List<DashboardCategory> getCategories(Context context, String settingPkg) {
     88         tryInitCategories(context, settingPkg);
     89         return mCategories;
     90     }
     91 
     92     public synchronized void reloadAllCategories(Context context, String settingPkg) {
     93         final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig(
     94                 context.getResources());
     95         mCategories = null;
     96         tryInitCategories(context, forceClearCache, settingPkg);
     97     }
     98 
     99     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