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