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