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