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