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