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.server.om;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.text.TextUtils;
22 import android.util.ArrayMap;
23 import android.util.ArraySet;
24 import android.util.Pair;
25 import android.util.Slog;
26 
27 import com.android.internal.annotations.GuardedBy;
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.CollectionUtils;
30 import com.android.server.SystemConfig;
31 import com.android.server.pm.pkg.AndroidPackage;
32 
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Map;
38 import java.util.Set;
39 
40 /**
41  * Track visibility of a targets and overlays to actors.
42  *
43  * 4 cases to handle:
44  * <ol>
45  *     <li>Target adds/changes an overlayable to add a reference to an actor
46  *         <ul>
47  *             <li>Must expose target to actor</li>
48  *             <li>Must expose any overlays that pointed to that overlayable name to the actor</li>
49  *         </ul>
50  *     </li>
51  *     <li>Target removes/changes an overlayable to remove a reference to an actor
52  *         <ul>
53  *             <li>If this target has no other overlayables referencing the actor, hide the
54  *             target</li>
55  *             <li>For all overlays targeting this overlayable, if the overlay is only visible to
56  *             the actor through this overlayable, hide the overlay</li>
57  *         </ul>
58  *     </li>
59  *     <li>Overlay adds/changes an overlay tag to add a reference to an overlayable name
60  *         <ul>
61  *             <li>Expose this overlay to the actor defined by the target overlayable</li>
62  *         </ul>
63  *     </li>
64  *     <li>Overlay removes/changes an overlay tag to remove a reference to an overlayable name
65  *         <ul>
66  *             <li>If this overlay is only visible to an actor through this overlayable name's
67  *             target's actor</li>
68  *         </ul>
69  *     </li>
70  * </ol>
71  *
72  * In this class, the names "actor", "target", and "overlay" all refer to the ID representations.
73  * All other use cases are named appropriate. "actor" is actor name, "target" is target package
74  * name, and "overlay" is overlay package name.
75  */
76 public class OverlayReferenceMapper {
77 
78     private static final String TAG = "OverlayReferenceMapper";
79 
80     private final Object mLock = new Object();
81 
82     /**
83      * Keys are actors, values are maps which map target to a set of overlays targeting it.
84      * The presence of a target in the value map means the actor and targets are connected, even
85      * if the corresponding target's set is empty.
86      * See class comment for specific types.
87      */
88     @GuardedBy("mLock")
89     private final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mActorToTargetToOverlays =
90             new ArrayMap<>();
91 
92     /**
93      * Keys are actor package names, values are generic package names the actor should be able
94      * to see.
95      */
96     @GuardedBy("mLock")
97     private final ArrayMap<String, Set<String>> mActorPkgToPkgs = new ArrayMap<>();
98 
99     @GuardedBy("mLock")
100     private boolean mDeferRebuild;
101 
102     @NonNull
103     private final Provider mProvider;
104 
105     /**
106      * @param deferRebuild whether or not to defer rebuild calls on add/remove until first get call;
107      *                     useful during boot when multiple packages are added in rapid succession
108      *                     and queries in-between are not expected
109      */
OverlayReferenceMapper(boolean deferRebuild, @Nullable Provider provider)110     public OverlayReferenceMapper(boolean deferRebuild, @Nullable Provider provider) {
111         this.mDeferRebuild = deferRebuild;
112         this.mProvider = provider != null ? provider : new Provider() {
113             @Nullable
114             @Override
115             public String getActorPkg(String actor) {
116                 Map<String, Map<String, String>> namedActors = SystemConfig.getInstance()
117                         .getNamedActors();
118 
119                 Pair<String, OverlayActorEnforcer.ActorState> actorPair =
120                         OverlayActorEnforcer.getPackageNameForActor(actor, namedActors);
121                 return actorPair.first;
122             }
123 
124             @NonNull
125             @Override
126             public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
127                 String target = pkg.getOverlayTarget();
128                 if (TextUtils.isEmpty(target)) {
129                     return Collections.emptyMap();
130                 }
131 
132                 String overlayable = pkg.getOverlayTargetOverlayableName();
133                 Map<String, Set<String>> targetToOverlayables = new HashMap<>();
134                 Set<String> overlayables = new HashSet<>();
135                 overlayables.add(overlayable);
136                 targetToOverlayables.put(target, overlayables);
137                 return targetToOverlayables;
138             }
139         };
140     }
141 
142     /**
143      * @return mapping of actor package to a set of packages it can view
144      */
145     @NonNull
146     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
getActorPkgToPkgs()147     public Map<String, Set<String>> getActorPkgToPkgs() {
148         return mActorPkgToPkgs;
149     }
150 
isValidActor(@onNull String targetName, @NonNull String actorPackageName)151     public boolean isValidActor(@NonNull String targetName, @NonNull String actorPackageName) {
152         synchronized (mLock) {
153             ensureMapBuilt();
154             Set<String> validSet = mActorPkgToPkgs.get(actorPackageName);
155             return validSet != null && validSet.contains(targetName);
156         }
157     }
158 
159     /**
160      * Add a package to be considered for visibility. Currently supports adding as a target and/or
161      * an overlay. Adding an actor is not supported. Those are configured as part of
162      * {@link SystemConfig#getNamedActors()}.
163      *
164      * @param pkg the package to add
165      * @param otherPkgs map of other packages to consider, excluding {@param pkg}
166      * @return Set of packages that may have changed visibility
167      */
addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs)168     public ArraySet<String> addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
169         synchronized (mLock) {
170             ArraySet<String> changed = new ArraySet<>();
171 
172             if (!pkg.getOverlayables().isEmpty()) {
173                 addTarget(pkg, otherPkgs, changed);
174             }
175 
176             // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
177             if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
178                 addOverlay(pkg, otherPkgs, changed);
179             }
180 
181             if (!mDeferRebuild) {
182                 rebuild();
183             }
184 
185             return changed;
186         }
187     }
188 
189     /**
190      * Removes a package to be considered for visibility. Currently supports removing as a target
191      * and/or an overlay. Removing an actor is not supported. Those are staticly configured as part
192      * of {@link SystemConfig#getNamedActors()}.
193      *
194      * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
195      * @return Set of packages that may have changed visibility
196      */
removePkg(String pkgName)197     public ArraySet<String> removePkg(String pkgName) {
198         synchronized (mLock) {
199             ArraySet<String> changedPackages = new ArraySet<>();
200             removeTarget(pkgName, changedPackages);
201             removeOverlay(pkgName, changedPackages);
202 
203             if (!mDeferRebuild) {
204                 rebuild();
205             }
206 
207             return changedPackages;
208         }
209     }
210 
211     /**
212      * @param changedPackages Ongoing collection of packages that may have changed visibility
213      */
removeTarget(String target, @NonNull Collection<String> changedPackages)214     private void removeTarget(String target, @NonNull Collection<String> changedPackages) {
215         synchronized (mLock) {
216             int size = mActorToTargetToOverlays.size();
217             for (int index = size - 1; index >= 0; index--) {
218                 ArrayMap<String, ArraySet<String>> targetToOverlays =
219                         mActorToTargetToOverlays.valueAt(index);
220                 if (targetToOverlays.containsKey(target)) {
221                     targetToOverlays.remove(target);
222 
223                     String actor = mActorToTargetToOverlays.keyAt(index);
224                     changedPackages.add(mProvider.getActorPkg(actor));
225 
226                     if (targetToOverlays.isEmpty()) {
227                         mActorToTargetToOverlays.removeAt(index);
228                     }
229                 }
230             }
231         }
232     }
233 
234     /**
235      * Associate an actor with an association of a new target to overlays for that target.
236      *
237      * If a target overlays itself, it will not be associated with itself, as only one half of the
238      * relationship needs to exist for visibility purposes.
239      *
240      * @param changedPackages Ongoing collection of packages that may have changed visibility
241      */
addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs, @NonNull Collection<String> changedPackages)242     private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs,
243             @NonNull Collection<String> changedPackages) {
244         synchronized (mLock) {
245             String target = targetPkg.getPackageName();
246             removeTarget(target, changedPackages);
247 
248             Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
249             for (String overlayable : overlayablesToActors.keySet()) {
250                 String actor = overlayablesToActors.get(overlayable);
251                 addTargetToMap(actor, target, changedPackages);
252 
253                 for (AndroidPackage overlayPkg : otherPkgs.values()) {
254                     Map<String, Set<String>> targetToOverlayables =
255                             mProvider.getTargetToOverlayables(overlayPkg);
256                     Set<String> overlayables = targetToOverlayables.get(target);
257                     if (CollectionUtils.isEmpty(overlayables)) {
258                         continue;
259                     }
260 
261                     if (overlayables.contains(overlayable)) {
262                         String overlay = overlayPkg.getPackageName();
263                         addOverlayToMap(actor, target, overlay, changedPackages);
264                     }
265                 }
266             }
267         }
268     }
269 
270     /**
271      * @param changedPackages Ongoing collection of packages that may have changed visibility
272      */
removeOverlay(String overlay, @NonNull Collection<String> changedPackages)273     private void removeOverlay(String overlay, @NonNull Collection<String> changedPackages) {
274         synchronized (mLock) {
275             int actorsSize = mActorToTargetToOverlays.size();
276             for (int actorIndex = actorsSize - 1; actorIndex >= 0; actorIndex--) {
277                 ArrayMap<String, ArraySet<String>> targetToOverlays =
278                         mActorToTargetToOverlays.valueAt(actorIndex);
279                 int targetsSize = targetToOverlays.size();
280                 for (int targetIndex = targetsSize - 1; targetIndex >= 0; targetIndex--) {
281                     final Set<String> overlays = targetToOverlays.valueAt(targetIndex);
282 
283                     if (overlays.remove(overlay)) {
284                         String actor = mActorToTargetToOverlays.keyAt(actorIndex);
285                         changedPackages.add(mProvider.getActorPkg(actor));
286 
287                         // targetToOverlays should not be removed here even if empty as the actor
288                         // will still have visibility to the target even if no overlays exist
289                     }
290                 }
291 
292                 if (targetToOverlays.isEmpty()) {
293                     mActorToTargetToOverlays.removeAt(actorIndex);
294                 }
295             }
296         }
297     }
298 
299     /**
300      * Associate an actor with an association of targets to overlays for a new overlay.
301      *
302      * If an overlay targets itself, it will not be associated with itself, as only one half of the
303      * relationship needs to exist for visibility purposes.
304      *
305      * @param changedPackages Ongoing collection of packages that may have changed visibility
306      */
addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs, @NonNull Collection<String> changedPackages)307     private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs,
308             @NonNull Collection<String> changedPackages) {
309         synchronized (mLock) {
310             String overlay = overlayPkg.getPackageName();
311             removeOverlay(overlay, changedPackages);
312 
313             Map<String, Set<String>> targetToOverlayables =
314                     mProvider.getTargetToOverlayables(overlayPkg);
315             for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) {
316                 String target = entry.getKey();
317                 Set<String> overlayables = entry.getValue();
318                 AndroidPackage targetPkg = otherPkgs.get(target);
319                 if (targetPkg == null) {
320                     continue;
321                 }
322 
323                 String targetPkgName = targetPkg.getPackageName();
324                 Map<String, String> overlayableToActor = targetPkg.getOverlayables();
325                 for (String overlayable : overlayables) {
326                     String actor = overlayableToActor.get(overlayable);
327                     if (TextUtils.isEmpty(actor)) {
328                         continue;
329                     }
330                     addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
331                 }
332             }
333         }
334     }
335 
rebuildIfDeferred()336     public void rebuildIfDeferred() {
337         synchronized (mLock) {
338             if (mDeferRebuild) {
339                 rebuild();
340                 mDeferRebuild = false;
341             }
342         }
343     }
344 
ensureMapBuilt()345     private void ensureMapBuilt() {
346         if (mDeferRebuild) {
347             rebuildIfDeferred();
348             Slog.w(TAG, "The actor map was queried before the system was ready, which may"
349                     + "result in decreased performance.");
350         }
351     }
352 
rebuild()353     private void rebuild() {
354         synchronized (mLock) {
355             mActorPkgToPkgs.clear();
356             for (String actor : mActorToTargetToOverlays.keySet()) {
357                 String actorPkg = mProvider.getActorPkg(actor);
358                 if (TextUtils.isEmpty(actorPkg)) {
359                     continue;
360                 }
361 
362                 ArrayMap<String, ArraySet<String>> targetToOverlays =
363                         mActorToTargetToOverlays.get(actor);
364                 Set<String> pkgs = new HashSet<>();
365 
366                 for (String target : targetToOverlays.keySet()) {
367                     Set<String> overlays = targetToOverlays.get(target);
368                     pkgs.add(target);
369                     pkgs.addAll(overlays);
370                 }
371 
372                 mActorPkgToPkgs.put(actorPkg, pkgs);
373             }
374         }
375     }
376 
377     /**
378      * @param changedPackages Ongoing collection of packages that may have changed visibility
379      */
addTargetToMap(String actor, String target, @NonNull Collection<String> changedPackages)380     private void addTargetToMap(String actor, String target,
381             @NonNull Collection<String> changedPackages) {
382         ArrayMap<String, ArraySet<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
383         if (targetToOverlays == null) {
384             targetToOverlays = new ArrayMap<>();
385             mActorToTargetToOverlays.put(actor, targetToOverlays);
386         }
387 
388         ArraySet<String> overlays = targetToOverlays.get(target);
389         if (overlays == null) {
390             overlays = new ArraySet<>();
391             targetToOverlays.put(target, overlays);
392         }
393 
394         // For now, only actors themselves can gain or lose visibility through package changes
395         changedPackages.add(mProvider.getActorPkg(actor));
396     }
397 
398     /**
399      * @param changedPackages Ongoing collection of packages that may have changed visibility
400      */
addOverlayToMap(String actor, String target, String overlay, @NonNull Collection<String> changedPackages)401     private void addOverlayToMap(String actor, String target, String overlay,
402             @NonNull Collection<String> changedPackages) {
403         synchronized (mLock) {
404             ArrayMap<String, ArraySet<String>> targetToOverlays =
405                     mActorToTargetToOverlays.get(actor);
406             if (targetToOverlays == null) {
407                 targetToOverlays = new ArrayMap<>();
408                 mActorToTargetToOverlays.put(actor, targetToOverlays);
409             }
410 
411             ArraySet<String> overlays = targetToOverlays.get(target);
412             if (overlays == null) {
413                 overlays = new ArraySet<>();
414                 targetToOverlays.put(target, overlays);
415             }
416 
417             overlays.add(overlay);
418         }
419 
420         // For now, only actors themselves can gain or lose visibility through package changes
421         changedPackages.add(mProvider.getActorPkg(actor));
422     }
423 
424     public interface Provider {
425 
426         /**
427          * Given the actor string from an overlayable definition, return the actor's package name.
428          */
429         @Nullable
getActorPkg(@onNull String actor)430         String getActorPkg(@NonNull String actor);
431 
432         /**
433          * Mock response of multiple overlay tags.
434          *
435          * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests
436          */
437         @NonNull
getTargetToOverlayables(@onNull AndroidPackage pkg)438         Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg);
439     }
440 }
441