1 /*
2  * Copyright (C) 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 
17 package com.android.internal.infra;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.util.ArrayMap;
23 import android.util.ArraySet;
24 import android.util.Log;
25 
26 import java.io.PrintWriter;
27 import java.util.List;
28 import java.util.Objects;
29 
30 /**
31  * Helper class for keeping track of allowlisted packages/activities.
32  *
33  * <p><b>NOTE: </b>this class is not thread safe.
34  * @hide
35  */
36 public final class WhitelistHelper {
37 
38     private static final String TAG = "WhitelistHelper";
39 
40     /**
41      * Map of allowlisted packages/activities. The whole package is allowlisted if its
42      * corresponding value is {@code null}.
43      */
44     @Nullable
45     private ArrayMap<String, ArraySet<ComponentName>> mWhitelistedPackages;
46 
47     /**
48      * Sets the allowlist with the given packages and activities. The list is cleared if both
49      * packageNames and components are {@code null}.
50      *
51      * @param packageNames packages to be allowlisted.
52      * @param components activities to be allowlisted.
53      *
54      * @throws IllegalArgumentException if packages or components are empty.
55      */
setWhitelist(@ullable ArraySet<String> packageNames, @Nullable ArraySet<ComponentName> components)56     public void setWhitelist(@Nullable ArraySet<String> packageNames,
57             @Nullable ArraySet<ComponentName> components) {
58         mWhitelistedPackages = null;
59         if (packageNames == null && components == null) return;
60 
61         if ((packageNames != null && packageNames.isEmpty())
62                 || (components != null && components.isEmpty())) {
63             throw new IllegalArgumentException("Packages or Components cannot be empty.");
64         }
65 
66         mWhitelistedPackages = new ArrayMap<>();
67 
68         if (packageNames != null) {
69             for (int i = 0; i < packageNames.size(); i++) {
70                 mWhitelistedPackages.put(packageNames.valueAt(i), null);
71             }
72         }
73 
74         if (components != null) {
75             for (int i = 0; i < components.size(); i++) {
76                 final ComponentName component = components.valueAt(i);
77                 if (component == null) {
78                     Log.w(TAG, "setWhitelist(): component is null");
79                     continue;
80                 }
81 
82                 final String packageName = component.getPackageName();
83                 ArraySet<ComponentName> set = mWhitelistedPackages.get(packageName);
84                 if (set == null) {
85                     set = new ArraySet<>();
86                     mWhitelistedPackages.put(packageName, set);
87                 }
88                 set.add(component);
89             }
90         }
91     }
92 
93     /**
94      * Helper to use {@link #setWhitelist(ArraySet, ArraySet)} with {@link List Lists}.
95      */
setWhitelist(@ullable List<String> packageNames, @Nullable List<ComponentName> components)96     public void setWhitelist(@Nullable List<String> packageNames,
97             @Nullable List<ComponentName> components) {
98         final ArraySet<String> packageNamesSet = packageNames == null ? null
99                 : new ArraySet<>(packageNames);
100         final ArraySet<ComponentName> componentsSet = components == null ? null
101                 : new ArraySet<>(components);
102         setWhitelist(packageNamesSet, componentsSet);
103     }
104 
105     /**
106      * Returns {@code true} if the entire package is allowlisted.
107      */
isWhitelisted(@onNull String packageName)108     public boolean isWhitelisted(@NonNull String packageName) {
109         Objects.requireNonNull(packageName);
110 
111         if (mWhitelistedPackages == null) return false;
112 
113         return mWhitelistedPackages.containsKey(packageName)
114                 && mWhitelistedPackages.get(packageName) == null;
115     }
116 
117     /**
118      * Returns {@code true} if the specified activity is allowlisted.
119      */
isWhitelisted(@onNull ComponentName componentName)120     public boolean isWhitelisted(@NonNull ComponentName componentName) {
121         Objects.requireNonNull(componentName);
122 
123         final String packageName = componentName.getPackageName();
124         final ArraySet<ComponentName> whitelistedComponents = getWhitelistedComponents(packageName);
125         if (whitelistedComponents != null) {
126             return whitelistedComponents.contains(componentName);
127         }
128 
129         return isWhitelisted(packageName);
130     }
131 
132     /**
133      * Returns a set of allowlisted components with the given package, or null if nothing is
134      * allowlisted.
135      */
136     @Nullable
getWhitelistedComponents(@onNull String packageName)137     public ArraySet<ComponentName> getWhitelistedComponents(@NonNull String packageName) {
138         Objects.requireNonNull(packageName);
139 
140         return mWhitelistedPackages == null ? null : mWhitelistedPackages.get(packageName);
141     }
142 
143     /**
144      * Returns a set of all packages that are either entirely allowlisted or have components that
145      * are allowlisted.
146      */
147     @Nullable
getWhitelistedPackages()148     public ArraySet<String> getWhitelistedPackages() {
149         return mWhitelistedPackages == null ? null : new ArraySet<>(mWhitelistedPackages.keySet());
150     }
151 
152     @Override
toString()153     public String toString() {
154         return "WhitelistHelper[" + mWhitelistedPackages + ']';
155     }
156 
157     /**
158      * Dumps it!
159      */
dump(@onNull String prefix, @NonNull String message, @NonNull PrintWriter pw)160     public void dump(@NonNull String prefix, @NonNull String message, @NonNull PrintWriter pw) {
161         if (mWhitelistedPackages == null || mWhitelistedPackages.size() == 0) {
162             pw.print(prefix); pw.print(message); pw.println(": (no whitelisted packages)");
163             return;
164         }
165 
166         final String prefix2 = prefix + "  ";
167         final int size = mWhitelistedPackages.size();
168         pw.print(prefix); pw.print(message); pw.print(": "); pw.print(size);
169         pw.println(" packages");
170         for (int i = 0; i < mWhitelistedPackages.size(); i++) {
171             final String packageName = mWhitelistedPackages.keyAt(i);
172             final ArraySet<ComponentName> components = mWhitelistedPackages.valueAt(i);
173             pw.print(prefix2); pw.print(i); pw.print("."); pw.print(packageName); pw.print(": ");
174             if (components == null) {
175                 pw.println("(whole package)");
176                 continue;
177             }
178 
179             pw.print("["); pw.print(components.valueAt(0));
180             for (int j = 1; j < components.size(); j++) {
181                 pw.print(", "); pw.print(components.valueAt(j));
182             }
183             pw.println("]");
184         }
185     }
186 }
187