1 /* 2 * Copyright 2019 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.server.usage; 17 18 import android.util.ArrayMap; 19 import android.util.Slog; 20 import android.util.SparseArray; 21 22 import java.util.ArrayList; 23 24 /** 25 * An object holding data defining the obfuscated packages and their token mappings. 26 * Used by {@link UsageStatsDatabase}. 27 * 28 * @hide 29 */ 30 public final class PackagesTokenData { 31 /** 32 * The package name is always stored at index 0 in {@code tokensToPackagesMap}. 33 */ 34 private static final int PACKAGE_NAME_INDEX = 0; 35 36 /** 37 * The default token for any string that hasn't been tokenized yet. 38 */ 39 public static final int UNASSIGNED_TOKEN = -1; 40 41 /** 42 * The main token counter for each package. 43 */ 44 public int counter = 1; 45 /** 46 * Stores a hierarchy of token to string mappings for each package, indexed by the main 47 * package token. The 0th index within the array list will always hold the package name. 48 */ 49 public final SparseArray<ArrayList<String>> tokensToPackagesMap = new SparseArray<>(); 50 /** 51 * Stores a hierarchy of strings to token mappings for each package. This is simply an inverse 52 * map of the {@code tokenToPackagesMap} in this class, mainly for an O(1) access to the tokens. 53 */ 54 public final ArrayMap<String, ArrayMap<String, Integer>> packagesToTokensMap = new ArrayMap<>(); 55 /** 56 * Stores a map of packages that were removed and when they were removed. 57 */ 58 public final ArrayMap<String, Long> removedPackagesMap = new ArrayMap<>(); 59 PackagesTokenData()60 public PackagesTokenData() { 61 } 62 63 /** 64 * Fetches the token mapped to the given package name. If there is no mapping, a new token is 65 * created and the relevant mappings are updated. 66 * 67 * @param packageName the package name whose token is being fetched 68 * @param timeStamp the time stamp of the event or end time of the usage stats; used to verify 69 * the package hasn't been removed 70 * @return the mapped token 71 */ getPackageTokenOrAdd(String packageName, long timeStamp)72 public int getPackageTokenOrAdd(String packageName, long timeStamp) { 73 final Long timeRemoved = removedPackagesMap.get(packageName); 74 if (timeRemoved != null && timeRemoved > timeStamp) { 75 return UNASSIGNED_TOKEN; // package was removed 76 /* 77 Note: instead of querying Package Manager each time for a list of packages to verify 78 if this package is still installed, it's more efficient to check the internal list of 79 removed packages and verify with the incoming time stamp. Although rare, it is possible 80 that some asynchronous function is triggered after a package is removed and the 81 time stamp passed into this function is not accurate. We'll have to keep the respective 82 event/usage stat until the next time the device reboots and the mappings are cleaned. 83 Additionally, this is a data class with some helper methods - it doesn't make sense to 84 overload it with references to other services. 85 */ 86 } 87 88 ArrayMap<String, Integer> packageTokensMap = packagesToTokensMap.get(packageName); 89 if (packageTokensMap == null) { 90 packageTokensMap = new ArrayMap<>(); 91 packagesToTokensMap.put(packageName, packageTokensMap); 92 } 93 int token = packageTokensMap.getOrDefault(packageName, UNASSIGNED_TOKEN); 94 if (token == UNASSIGNED_TOKEN) { 95 token = counter++; 96 // package name should always be at index 0 in the sub-mapping 97 ArrayList<String> tokenPackages = new ArrayList<>(); 98 tokenPackages.add(packageName); 99 packageTokensMap.put(packageName, token); 100 tokensToPackagesMap.put(token, tokenPackages); 101 } 102 return token; 103 } 104 105 /** 106 * Fetches the token mapped to the given key within the package's context. If there is no 107 * mapping, a new token is created and the relevant mappings are updated. 108 * 109 * @param packageToken the package token for which the given key belongs to 110 * @param packageName the package name for which the given key belongs to 111 * @param key the key whose token is being fetched 112 * @return the mapped token 113 */ getTokenOrAdd(int packageToken, String packageName, String key)114 public int getTokenOrAdd(int packageToken, String packageName, String key) { 115 if (packageName.equals(key)) { 116 return PACKAGE_NAME_INDEX; 117 } 118 int token = packagesToTokensMap.get(packageName).getOrDefault(key, UNASSIGNED_TOKEN); 119 if (token == UNASSIGNED_TOKEN) { 120 token = tokensToPackagesMap.get(packageToken).size(); 121 packagesToTokensMap.get(packageName).put(key, token); 122 tokensToPackagesMap.get(packageToken).add(key); 123 } 124 return token; 125 } 126 127 /** 128 * Fetches the package name for the given token. 129 * 130 * @param packageToken the package token representing the package name 131 * @return the string representing the given token or {@code null} if not found 132 */ getPackageString(int packageToken)133 public String getPackageString(int packageToken) { 134 final ArrayList<String> packageStrings = tokensToPackagesMap.get(packageToken); 135 if (packageStrings == null) { 136 return null; 137 } 138 return packageStrings.get(PACKAGE_NAME_INDEX); 139 } 140 141 /** 142 * Fetches the string represented by the given token. 143 * 144 * @param packageToken the package token for which this token belongs to 145 * @param token the token whose string needs to be fetched 146 * @return the string representing the given token or {@code null} if not found 147 */ getString(int packageToken, int token)148 public String getString(int packageToken, int token) { 149 try { 150 return tokensToPackagesMap.get(packageToken).get(token); 151 } catch (NullPointerException npe) { 152 Slog.e("PackagesTokenData", 153 "Unable to find tokenized strings for package " + packageToken, npe); 154 return null; 155 } catch (IndexOutOfBoundsException e) { 156 return null; 157 } 158 } 159 160 /** 161 * Removes the package from all known mappings. 162 * 163 * @param packageName the package to be removed 164 * @param timeRemoved the time stamp of when the package was removed 165 * @return the token mapped to the package removed or {@code PackagesTokenData.UNASSIGNED_TOKEN} 166 * if not mapped 167 */ removePackage(String packageName, long timeRemoved)168 public int removePackage(String packageName, long timeRemoved) { 169 removedPackagesMap.put(packageName, timeRemoved); 170 171 if (!packagesToTokensMap.containsKey(packageName)) { 172 return UNASSIGNED_TOKEN; 173 } 174 final int packageToken = packagesToTokensMap.get(packageName).get(packageName); 175 packagesToTokensMap.remove(packageName); 176 tokensToPackagesMap.delete(packageToken); 177 return packageToken; 178 } 179 } 180