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