diff options
author | Winson <chiuwinson@google.com> | 2019-10-24 16:32:20 -0700 |
---|---|---|
committer | Winson <chiuwinson@google.com> | 2019-12-09 10:46:59 -0800 |
commit | 3f46dbd7a091b5e594be6055ab11d0e0cfe25a18 (patch) | |
tree | d303f72bf4a8f6cd2106ef7851e716c707a622bb | |
parent | 6571c8a46154b800d16a2fe850c4b2129b209a2a (diff) |
Overlay, actor, and target app visibility handling
Hooks AppsFilter to support exposing a target and any overlays
targeting it to the actor specified in its overlayable block.
Sacrifices some install-time performance in favor of less memory
usage and easier to follow code by doing a full search/rebuild
on each change.
Benchmarks TBD
Bug: 143096091
Test: atest OverlayReferenceMapperTests
Change-Id: Ic832818b9aa383f1167ca3e69a11b8459fa9db97
16 files changed, 1024 insertions, 117 deletions
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java index 515185eaaf57..35df47431a91 100644 --- a/core/java/android/content/pm/parsing/AndroidPackage.java +++ b/core/java/android/content/pm/parsing/AndroidPackage.java @@ -229,6 +229,11 @@ public interface AndroidPackage extends Parcelable { String getOverlayTargetName(); + /** + * Map of overlayable name to actor name. + */ + Map<String, String> getOverlayables(); + // TODO(b/135203078): Does this and getAppInfoPackageName have to be separate methods? // The refactor makes them the same value with no known consequences, so should be redundant. String getPackageName(); diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index edbf73a0c0da..ffddbb6c2c47 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -52,6 +52,7 @@ import android.content.pm.permission.SplitPermissionInfoParcelable; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -92,6 +93,7 @@ import java.security.PublicKey; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Set; /** @hide */ @@ -287,8 +289,23 @@ public class ApkParseUtils { + result.getErrorMessage()); } - return result.getResultAndNull() - .setVolumeUuid(volumeUuid) + ParsingPackage pkg = result.getResultAndNull(); + ApkAssets apkAssets = assets.getApkAssets()[0]; + if (apkAssets.definesOverlayable()) { + SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers(); + int size = packageNames.size(); + for (int index = 0; index < size; index++) { + String packageName = packageNames.get(index); + Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName); + if (overlayableToActor != null && !overlayableToActor.isEmpty()) { + for (String overlayable : overlayableToActor.keySet()) { + pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable)); + } + } + } + } + + return pkg.setVolumeUuid(volumeUuid) .setApplicationVolumeUuid(volumeUuid) .setSigningDetails(SigningDetails.UNKNOWN); } catch (PackageParserException e) { diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index 377279e750c6..0e736d522c10 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -18,6 +18,8 @@ package android.content.pm.parsing; import static android.os.Build.VERSION_CODES.DONUT; +import static java.util.Collections.emptyMap; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; @@ -55,11 +57,13 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import com.android.server.SystemConfig; import java.security.PublicKey; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -126,6 +130,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android private String overlayCategory; private int overlayPriority; private boolean overlayIsStatic; + private Map<String, String> overlayables = emptyMap(); private String staticSharedLibName; private long staticSharedLibVersion; @@ -475,7 +480,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android @Override public Map<String, ArraySet<PublicKey>> getKeySetMapping() { - return keySetMapping == null ? Collections.emptyMap() : keySetMapping; + return keySetMapping == null ? emptyMap() : keySetMapping; } @Override @@ -773,6 +778,13 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public ParsingPackage addOverlayable(String overlayableName, String actorName) { + this.overlayables = CollectionUtils.add(this.overlayables, + TextUtils.safeIntern(overlayableName), TextUtils.safeIntern(actorName)); + return this; + } + + @Override public PackageImpl addAdoptPermission(String adoptPermission) { this.adoptPermissions = ArrayUtils.add(this.adoptPermissions, adoptPermission); return this; @@ -2125,6 +2137,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public Map<String, String> getOverlayables() { + return overlayables; + } + + @Override public boolean isOverlayIsStatic() { return overlayIsStatic; } @@ -2291,7 +2308,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android appInfo.metaData = appMetaData; appInfo.minAspectRatio = minAspectRatio; appInfo.minSdkVersion = minSdkVersion; - appInfo.name = name; + appInfo.name = className; if (appInfo.name != null) { appInfo.name = appInfo.name.trim(); } @@ -2957,6 +2974,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android dest.writeString(this.overlayCategory); dest.writeInt(this.overlayPriority); dest.writeBoolean(this.overlayIsStatic); + dest.writeMap(this.overlayables); dest.writeString(this.staticSharedLibName); dest.writeLong(this.staticSharedLibVersion); dest.writeStringList(this.libraryNames); @@ -3100,6 +3118,8 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android this.overlayCategory = in.readString(); this.overlayPriority = in.readInt(); this.overlayIsStatic = in.readBoolean(); + this.overlayables = new HashMap<>(); + in.readMap(overlayables, boot); this.staticSharedLibName = TextUtils.safeIntern(in.readString()); this.staticSharedLibVersion = in.readLong(); this.libraryNames = in.createStringArrayList(); diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 43c1f6e335b0..aff1b2e05eaf 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -62,6 +62,8 @@ public interface ParsingPackage extends AndroidPackage { ParsingPackage addOriginalPackage(String originalPackage); + ParsingPackage addOverlayable(String overlayableName, String actorName); + ParsingPackage addPermission(ParsedPermission permission); ParsingPackage addPermissionGroup(ParsedPermissionGroup permissionGroup); diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 4165f202998c..4dac5427095b 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -308,6 +308,17 @@ public class CollectionUtils { } /** + * @see #add(List, Object) + */ + public static @NonNull <K, V> Map<K, V> add(@Nullable Map<K, V> map, K key, V value) { + if (map == null || map == Collections.emptyMap()) { + map = new ArrayMap<>(); + } + map.put(key, value); + return map; + } + + /** * Similar to {@link List#remove}, but with support for list values of {@code null} and * {@link Collections#emptyList} */ diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 49a73ee7790f..e6232e851253 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -226,7 +226,7 @@ public class SystemConfig { * Map of system pre-defined, uniquely named actors; keys are namespace, * value maps actor name to package name. */ - private ArrayMap<String, ArrayMap<String, String>> mNamedActors = null; + private Map<String, Map<String, String>> mNamedActors = null; public static SystemConfig getInstance() { if (!isSystemProcess()) { @@ -406,7 +406,7 @@ public class SystemConfig { } @NonNull - public Map<String, ? extends Map<String, String>> getNamedActors() { + public Map<String, Map<String, String>> getNamedActors() { return mNamedActors != null ? mNamedActors : Collections.emptyMap(); } @@ -1063,7 +1063,7 @@ public class SystemConfig { mNamedActors = new ArrayMap<>(); } - ArrayMap<String, String> nameToPkgMap = mNamedActors.get(namespace); + Map<String, String> nameToPkgMap = mNamedActors.get(namespace); if (nameToPkgMap == null) { nameToPkgMap = new ArrayMap<>(); mNamedActors.put(namespace, nameToPkgMap); diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index e05511681ba8..ac3bf9ad5d8a 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.Process; import android.os.RemoteException; import android.text.TextUtils; +import android.util.Pair; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -44,6 +45,38 @@ public class OverlayActorEnforcer { private final VerifyCallback mVerifyCallback; + /** + * @return nullable actor result with {@link ActorState} failure status + */ + static Pair<String, ActorState> getPackageNameForActor(String actorUriString, + Map<String, Map<String, String>> namedActors) { + if (namedActors.isEmpty()) { + return Pair.create(null, ActorState.NO_NAMED_ACTORS); + } + + Uri actorUri = Uri.parse(actorUriString); + + String actorScheme = actorUri.getScheme(); + List<String> actorPathSegments = actorUri.getPathSegments(); + if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) { + return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME); + } + + String actorNamespace = actorUri.getAuthority(); + Map<String, String> namespace = namedActors.get(actorNamespace); + if (namespace == null) { + return Pair.create(null, ActorState.MISSING_NAMESPACE); + } + + String actorName = actorPathSegments.get(0); + String packageName = namespace.get(actorName); + if (TextUtils.isEmpty(packageName)) { + return Pair.create(null, ActorState.MISSING_ACTOR_NAME); + } + + return Pair.create(packageName, ActorState.ALLOWED); + } + public OverlayActorEnforcer(@NonNull VerifyCallback verifyCallback) { mVerifyCallback = verifyCallback; } @@ -141,31 +174,14 @@ public class OverlayActorEnforcer { } } - Map<String, ? extends Map<String, String>> namedActors = mVerifyCallback.getNamedActors(); - if (namedActors.isEmpty()) { - return ActorState.NO_NAMED_ACTORS; - } - - Uri actorUri = Uri.parse(actor); - - String actorScheme = actorUri.getScheme(); - List<String> actorPathSegments = actorUri.getPathSegments(); - if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) { - return ActorState.INVALID_OVERLAYABLE_ACTOR_NAME; - } - - String actorNamespace = actorUri.getAuthority(); - Map<String, String> namespace = namedActors.get(actorNamespace); - if (namespace == null) { - return ActorState.MISSING_NAMESPACE; - } - - String actorName = actorPathSegments.get(0); - String packageName = namespace.get(actorName); - if (TextUtils.isEmpty(packageName)) { - return ActorState.MISSING_ACTOR_NAME; + Map<String, Map<String, String>> namedActors = mVerifyCallback.getNamedActors(); + Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors); + ActorState actorUriState = actorUriPair.second; + if (actorUriState != ActorState.ALLOWED) { + return actorUriState; } + String packageName = actorUriPair.first; PackageInfo packageInfo = mVerifyCallback.getPackageInfo(packageName, userId); if (packageInfo == null) { return ActorState.MISSING_APP_INFO; @@ -192,7 +208,7 @@ public class OverlayActorEnforcer { * For easier logging/debugging, a set of all possible failure/success states when running * enforcement. */ - private enum ActorState { + enum ActorState { ALLOWED, INVALID_ACTOR, MISSING_NAMESPACE, @@ -244,7 +260,7 @@ public class OverlayActorEnforcer { * value maps actor name to package name */ @NonNull - Map<String, ? extends Map<String, String>> getNamedActors(); + Map<String, Map<String, String>> getNamedActors(); /** * @return true if the target package has declared an overlayable diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 63de61c9782f..f5fcb77710ca 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -1062,8 +1062,6 @@ public final class OverlayManagerService extends SystemService { mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); } - // TODO(b/143096091): Remove PackageInfo cache so that PackageManager is always queried - // to enforce visibility/other permission checks public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId, final boolean useCache) { if (useCache) { @@ -1086,18 +1084,12 @@ public final class OverlayManagerService extends SystemService { @Override public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) { - // TODO(b/143096091): Remove clearing calling ID - long callingIdentity = Binder.clearCallingIdentity(); - try { - return getPackageInfo(packageName, userId, true); - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } + return getPackageInfo(packageName, userId, true); } @NonNull @Override - public Map<String, ? extends Map<String, String>> getNamedActors() { + public Map<String, Map<String, String>> getNamedActors() { return SystemConfig.getInstance().getNamedActors(); } @@ -1125,57 +1117,45 @@ public final class OverlayManagerService extends SystemService { public OverlayableInfo getOverlayableForTarget(@NonNull String packageName, @Nullable String targetOverlayableName, int userId) throws IOException { - // TODO(b/143096091): Remove clearing calling ID - long callingIdentity = Binder.clearCallingIdentity(); - try { - PackageInfo packageInfo = getPackageInfo(packageName, userId); - if (packageInfo == null) { - throw new IOException("Unable to get target package"); - } + PackageInfo packageInfo = getPackageInfo(packageName, userId); + if (packageInfo == null) { + throw new IOException("Unable to get target package"); + } - String baseCodePath = packageInfo.applicationInfo.getBaseCodePath(); + String baseCodePath = packageInfo.applicationInfo.getBaseCodePath(); - ApkAssets apkAssets = null; - try { - apkAssets = ApkAssets.loadFromPath(baseCodePath); - return apkAssets.getOverlayableInfo(targetOverlayableName); - } finally { - if (apkAssets != null) { - try { - apkAssets.close(); - } catch (Throwable ignored) { - } + ApkAssets apkAssets = null; + try { + apkAssets = ApkAssets.loadFromPath(baseCodePath); + return apkAssets.getOverlayableInfo(targetOverlayableName); + } finally { + if (apkAssets != null) { + try { + apkAssets.close(); + } catch (Throwable ignored) { } } - } finally { - Binder.restoreCallingIdentity(callingIdentity); } } @Override public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) throws RemoteException, IOException { - // TODO(b/143096091): Remove clearing calling ID - long callingIdentity = Binder.clearCallingIdentity(); - try { - PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0, - userId); - String baseCodePath = packageInfo.applicationInfo.getBaseCodePath(); + PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0, + userId); + String baseCodePath = packageInfo.applicationInfo.getBaseCodePath(); - ApkAssets apkAssets = null; - try { - apkAssets = ApkAssets.loadFromPath(baseCodePath); - return apkAssets.definesOverlayable(); - } finally { - if (apkAssets != null) { - try { - apkAssets.close(); - } catch (Throwable ignored) { - } + ApkAssets apkAssets = null; + try { + apkAssets = ApkAssets.loadFromPath(baseCodePath); + return apkAssets.definesOverlayable(); + } finally { + if (apkAssets != null) { + try { + apkAssets.close(); + } catch (Throwable ignored) { } } - } finally { - Binder.restoreCallingIdentity(callingIdentity); } } @@ -1218,16 +1198,10 @@ public final class OverlayManagerService extends SystemService { @Nullable @Override public String[] getPackagesForUid(int uid) { - // TODO(b/143096091): Remove clearing calling ID - long callingIdentity = Binder.clearCallingIdentity(); try { - try { - return mPackageManager.getPackagesForUid(uid); - } catch (RemoteException ignored) { - return null; - } - } finally { - Binder.restoreCallingIdentity(callingIdentity); + return mPackageManager.getPackagesForUid(uid); + } catch (RemoteException ignored) { + return null; } } diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java new file mode 100644 index 000000000000..8bea119f3490 --- /dev/null +++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.om; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.parsing.AndroidPackage; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Pair; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.CollectionUtils; +import com.android.server.SystemConfig; +import com.android.server.pm.PackageSetting; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Track visibility of a targets and overlays to actors. + * + * 4 cases to handle: + * <ol> + * <li>Target adds/changes an overlayable to add a reference to an actor + * <ul> + * <li>Must expose target to actor</li> + * <li>Must expose any overlays that pointed to that overlayable name to the actor</li> + * </ul> + * </li> + * <li>Target removes/changes an overlayable to remove a reference to an actor + * <ul> + * <li>If this target has no other overlayables referencing the actor, hide the + * target</li> + * <li>For all overlays targeting this overlayable, if the overlay is only visible to + * the actor through this overlayable, hide the overlay</li> + * </ul> + * </li> + * <li>Overlay adds/changes an overlay tag to add a reference to an overlayable name + * <ul> + * <li>Expose this overlay to the actor defined by the target overlayable</li> + * </ul> + * </li> + * <li>Overlay removes/changes an overlay tag to remove a reference to an overlayable name + * <ul> + * <li>If this overlay is only visible to an actor through this overlayable name's + * target's actor</li> + * </ul> + * </li> + * </ol> + * + * In this class, the names "actor", "target", and "overlay" all refer to the ID representations. + * All other use cases are named appropriate. "actor" is actor name, "target" is target package + * name, and "overlay" is overlay package name. + */ +public class OverlayReferenceMapper { + + private final Object mLock = new Object(); + + /** + * Keys are actors, values are maps which map target to a set of overlays targeting it. + * The presence of a target in the value map means the actor and targets are connected, even + * if the corresponding target's set is empty. + * See class comment for specific types. + */ + @GuardedBy("mLock") + private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>(); + + /** + * Keys are actor package names, values are generic package names the actor should be able + * to see. + */ + @GuardedBy("mLock") + private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>(); + + @GuardedBy("mLock") + private boolean mDeferRebuild; + + @NonNull + private final Provider mProvider; + + /** + * @param deferRebuild whether or not to defer rebuild calls on add/remove until first get call; + * useful during boot when multiple packages are added in rapid succession + * and queries in-between are not expected + */ + public OverlayReferenceMapper(boolean deferRebuild, @Nullable Provider provider) { + this.mDeferRebuild = deferRebuild; + this.mProvider = provider != null ? provider : new Provider() { + @Nullable + @Override + public String getActorPkg(String actor) { + Map<String, Map<String, String>> namedActors = SystemConfig.getInstance() + .getNamedActors(); + + Pair<String, OverlayActorEnforcer.ActorState> actorPair = + OverlayActorEnforcer.getPackageNameForActor(actor, namedActors); + return actorPair.first; + } + + @NonNull + @Override + public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) { + String target = pkg.getOverlayTarget(); + if (TextUtils.isEmpty(target)) { + return Collections.emptyMap(); + } + + String overlayable = pkg.getOverlayTargetName(); + Map<String, Set<String>> targetToOverlayables = new HashMap<>(); + Set<String> overlayables = new HashSet<>(); + overlayables.add(overlayable); + targetToOverlayables.put(target, overlayables); + return targetToOverlayables; + } + }; + } + + /** + * @return mapping of actor package to a set of packages it can view + */ + @NonNull + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public Map<String, Set<String>> getActorPkgToPkgs() { + return mActorPkgToPkgs; + } + + public boolean isValidActor(@NonNull String targetName, @NonNull String actorPackageName) { + synchronized (mLock) { + assertMapBuilt(); + Set<String> validSet = mActorPkgToPkgs.get(actorPackageName); + return validSet != null && validSet.contains(targetName); + } + } + + /** + * Add a package to be considered for visibility. Currently supports adding as a target and/or + * an overlay. Adding an actor is not supported. Those are configured as part of + * {@link SystemConfig#getNamedActors()}. + * + * @param pkg the package to add + * @param otherPkgs map of other packages to consider, excluding {@param pkg} + */ + public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) { + synchronized (mLock) { + if (!pkg.getOverlayables().isEmpty()) { + addTarget(pkg, otherPkgs); + } + + // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks + if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) { + addOverlay(pkg, otherPkgs); + } + + if (!mDeferRebuild) { + rebuild(); + } + } + } + + /** + * Removes a package to be considered for visibility. Currently supports removing as a target + * and/or an overlay. Removing an actor is not supported. Those are staticly configured as part + * of {@link SystemConfig#getNamedActors()}. + * + * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)} + */ + public void removePkg(String pkgName) { + synchronized (mLock) { + removeTarget(pkgName); + removeOverlay(pkgName); + + if (!mDeferRebuild) { + rebuild(); + } + } + } + + private void removeTarget(String target) { + synchronized (mLock) { + Iterator<Map<String, Set<String>>> iterator = + mActorToTargetToOverlays.values().iterator(); + while (iterator.hasNext()) { + Map<String, Set<String>> next = iterator.next(); + next.remove(target); + if (next.isEmpty()) { + iterator.remove(); + } + } + } + } + + /** + * Associate an actor with an association of a new target to overlays for that target. + * + * If a target overlays itself, it will not be associated with itself, as only one half of the + * relationship needs to exist for visibility purposes. + */ + private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) { + synchronized (mLock) { + String target = targetPkg.getPackageName(); + removeTarget(target); + + Map<String, String> overlayablesToActors = targetPkg.getOverlayables(); + for (String overlayable : overlayablesToActors.keySet()) { + String actor = overlayablesToActors.get(overlayable); + addTargetToMap(actor, target); + + for (AndroidPackage overlayPkg : otherPkgs.values()) { + Map<String, Set<String>> targetToOverlayables = + mProvider.getTargetToOverlayables(overlayPkg); + Set<String> overlayables = targetToOverlayables.get(target); + if (CollectionUtils.isEmpty(overlayables)) { + continue; + } + + if (overlayables.contains(overlayable)) { + addOverlayToMap(actor, target, overlayPkg.getPackageName()); + } + } + } + } + } + + private void removeOverlay(String overlay) { + synchronized (mLock) { + for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) { + for (Set<String> overlays : targetToOverlays.values()) { + overlays.remove(overlay); + } + } + } + } + + /** + * Associate an actor with an association of targets to overlays for a new overlay. + * + * If an overlay targets itself, it will not be associated with itself, as only one half of the + * relationship needs to exist for visibility purposes. + */ + private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) { + synchronized (mLock) { + String overlay = overlayPkg.getPackageName(); + removeOverlay(overlay); + + Map<String, Set<String>> targetToOverlayables = + mProvider.getTargetToOverlayables(overlayPkg); + for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) { + String target = entry.getKey(); + Set<String> overlayables = entry.getValue(); + AndroidPackage targetPkg = otherPkgs.get(target); + if (targetPkg == null) { + continue; + } + + String targetPkgName = targetPkg.getPackageName(); + Map<String, String> overlayableToActor = targetPkg.getOverlayables(); + for (String overlayable : overlayables) { + String actor = overlayableToActor.get(overlayable); + if (TextUtils.isEmpty(actor)) { + continue; + } + addOverlayToMap(actor, targetPkgName, overlay); + } + } + } + } + + public void rebuildIfDeferred() { + synchronized (mLock) { + if (mDeferRebuild) { + rebuild(); + mDeferRebuild = false; + } + } + } + + private void assertMapBuilt() { + if (mDeferRebuild) { + throw new IllegalStateException("The actor map must be built by calling " + + "rebuildIfDeferred before it is queried"); + } + } + + private void rebuild() { + synchronized (mLock) { + mActorPkgToPkgs.clear(); + for (String actor : mActorToTargetToOverlays.keySet()) { + String actorPkg = mProvider.getActorPkg(actor); + if (TextUtils.isEmpty(actorPkg)) { + continue; + } + + Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor); + Set<String> pkgs = new HashSet<>(); + + for (String target : targetToOverlays.keySet()) { + Set<String> overlays = targetToOverlays.get(target); + pkgs.add(target); + pkgs.addAll(overlays); + } + + mActorPkgToPkgs.put(actorPkg, pkgs); + } + } + } + + private void addTargetToMap(String actor, String target) { + Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor); + if (targetToOverlays == null) { + targetToOverlays = new HashMap<>(); + mActorToTargetToOverlays.put(actor, targetToOverlays); + } + + Set<String> overlays = targetToOverlays.get(target); + if (overlays == null) { + overlays = new HashSet<>(); + targetToOverlays.put(target, overlays); + } + } + + private void addOverlayToMap(String actor, String target, String overlay) { + synchronized (mLock) { + Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor); + if (targetToOverlays == null) { + targetToOverlays = new HashMap<>(); + mActorToTargetToOverlays.put(actor, targetToOverlays); + } + + Set<String> overlays = targetToOverlays.get(target); + if (overlays == null) { + overlays = new HashSet<>(); + targetToOverlays.put(target, overlays); + } + + overlays.add(overlay); + } + } + + public interface Provider { + + /** + * Given the actor string from an overlayable definition, return the actor's package name. + */ + @Nullable + String getActorPkg(String actor); + + /** + * Mock response of multiple overlay tags. + * + * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests + */ + @NonNull + Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg); + } +} diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 8374ee63e07e..c4bcf809a67d 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -16,8 +16,6 @@ package com.android.server.pm; -import static android.content.pm.PackageParser.Component; -import static android.content.pm.PackageParser.IntentInfo; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; @@ -26,7 +24,6 @@ import android.annotation.Nullable; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.parsing.AndroidPackage; import android.content.pm.parsing.ComponentParseUtils; import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; @@ -50,9 +47,9 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.FgThread; +import com.android.server.om.OverlayReferenceMapper; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; @@ -109,11 +106,16 @@ public class AppsFilter { private final FeatureConfig mFeatureConfig; + private final OverlayReferenceMapper mOverlayReferenceMapper; + AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist, - boolean systemAppsQueryable) { + boolean systemAppsQueryable, + @Nullable OverlayReferenceMapper.Provider overlayProvider) { mFeatureConfig = featureConfig; mForceQueryableByDevicePackageNames = forceQueryableWhitelist; mSystemAppsQueryable = systemAppsQueryable; + mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/, + overlayProvider); } public interface FeatureConfig { @@ -193,7 +195,7 @@ public class AppsFilter { } } return new AppsFilter(featureConfig, forcedQueryablePackageNames, - forceSystemAppsQueryable); + forceSystemAppsQueryable, null); } /** Returns true if the querying package may query for the potential target package */ @@ -282,6 +284,7 @@ public class AppsFilter { public void onSystemReady() { mFeatureConfig.onSystemReady(); + mOverlayReferenceMapper.rebuildIfDeferred(); } /** @@ -338,6 +341,16 @@ public class AppsFilter { } } } + + int existingSize = existingSettings.size(); + ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize); + for (int index = 0; index < existingSize; index++) { + PackageSetting pkgSetting = existingSettings.valueAt(index); + if (pkgSetting.pkg != null) { + existingPkgs.put(pkgSetting.name, pkgSetting.pkg); + } + } + mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -381,6 +394,8 @@ public class AppsFilter { addPackage(setting.sharedUser.packages.valueAt(i), existingSettings); } } + + mOverlayReferenceMapper.removePkg(setting.name); } /** @@ -397,8 +412,7 @@ public class AppsFilter { PackageSetting targetPkgSetting, int userId) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication"); try { - if (!shouldFilterApplicationInternal(callingUid, callingSetting, - targetPkgSetting, + if (!shouldFilterApplicationInternal(callingUid, callingSetting, targetPkgSetting, userId)) { return false; } @@ -412,8 +426,8 @@ public class AppsFilter { } } - private boolean shouldFilterApplicationInternal(int callingUid, - SettingBase callingSetting, PackageSetting targetPkgSetting, int userId) { + private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting, + PackageSetting targetPkgSetting, int userId) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal"); try { final boolean featureEnabled = mFeatureConfig.isGloballyEnabled(); @@ -530,6 +544,29 @@ public class AppsFilter { } } } + + if (callingSharedPkgSettings != null) { + int size = callingSharedPkgSettings.size(); + for (int index = 0; index < size; index++) { + PackageSetting pkgSetting = callingSharedPkgSettings.valueAt(index); + if (mOverlayReferenceMapper.isValidActor(targetName, pkgSetting.name)) { + if (DEBUG_LOGGING) { + log(callingPkgSetting, targetPkgSetting, + "matches shared user of package that acts on target of " + + "overlay"); + } + return false; + } + } + } else { + if (mOverlayReferenceMapper.isValidActor(targetName, callingPkgSetting.name)) { + if (DEBUG_LOGGING) { + log(callingPkgSetting, targetPkgSetting, "acts on target of overlay"); + } + return false; + } + } + return true; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt new file mode 100644 index 000000000000..f45316fc74cd --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.om + +import android.content.pm.parsing.AndroidPackage +import android.net.Uri +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.testng.Assert.assertThrows +import test.util.mockThrowOnUnmocked +import test.util.whenever + +@RunWith(Parameterized::class) +class OverlayReferenceMapperTests { + + companion object { + private const val TARGET_PACKAGE_NAME = "com.test.target" + private const val OVERLAY_PACKAGE_NAME = "com.test.overlay" + private const val ACTOR_PACKAGE_NAME = "com.test.actor" + private const val ACTOR_NAME = "overlay://test/actorName" + + @JvmStatic + @Parameterized.Parameters(name = "deferRebuild {0}") + fun parameters() = arrayOf(true, false) + } + + private lateinit var mapper: OverlayReferenceMapper + + @JvmField + @Parameterized.Parameter(0) + var deferRebuild = false + + @Before + fun initMapper() { + mapper = mapper() + } + + @Test + fun targetWithOverlay() { + val target = mockTarget() + val overlay = mockOverlay() + val existing = mapper.addInOrder(overlay) + assertEmpty() + mapper.addInOrder(target, existing = existing) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay)) + mapper.remove(target) + assertEmpty() + } + + @Test + fun targetWithMultipleOverlays() { + val target = mockTarget() + val overlay0 = mockOverlay(0) + val overlay1 = mockOverlay(1) + mapper = mapper( + overlayToTargetToOverlayables = mapOf( + overlay0.packageName to mapOf( + target.packageName to target.overlayables.keys + ), + overlay1.packageName to mapOf( + target.packageName to target.overlayables.keys + ) + ) + ) + val existing = mapper.addInOrder(overlay0, overlay1) + assertEmpty() + mapper.addInOrder(target, existing = existing) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1)) + mapper.remove(overlay0) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1)) + mapper.remove(target) + assertEmpty() + } + + @Test + fun targetWithoutOverlay() { + val target = mockTarget() + mapper.addInOrder(target) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) + mapper.remove(target) + assertEmpty() + } + + @Test + fun overlayWithTarget() { + val target = mockTarget() + val overlay = mockOverlay() + val existing = mapper.addInOrder(target) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) + mapper.addInOrder(overlay, existing = existing) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay)) + mapper.remove(overlay) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) + } + + @Test + fun overlayWithMultipleTargets() { + val target0 = mockTarget(0) + val target1 = mockTarget(1) + val overlay = mockOverlay() + mapper = mapper( + overlayToTargetToOverlayables = mapOf( + overlay.packageName to mapOf( + target0.packageName to target0.overlayables.keys, + target1.packageName to target1.overlayables.keys + ) + ) + ) + mapper.addInOrder(target0, target1, overlay) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay)) + mapper.remove(target0) + assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay)) + mapper.remove(target1) + assertEmpty() + } + + @Test + fun overlayWithoutTarget() { + val overlay = mockOverlay() + mapper.addInOrder(overlay) + // An overlay can only have visibility exposed through its target + assertEmpty() + mapper.remove(overlay) + assertEmpty() + } + + private fun OverlayReferenceMapper.addInOrder( + vararg pkgs: AndroidPackage, + existing: MutableMap<String, AndroidPackage> = mutableMapOf() + ) = pkgs.fold(existing) { map, pkg -> + addPkg(pkg, map) + map[pkg.packageName] = pkg + return@fold map + } + + private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName) + + private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) { + val expected = pairs.associate { it } + .mapValues { pair -> pair.value.map { it.packageName }.toSet() } + + // This validates the API exposed for querying the relationships + expected.forEach { (actorPkgName, expectedPkgNames) -> + expectedPkgNames.forEach { expectedPkgName -> + if (deferRebuild) { + assertThrows(IllegalStateException::class.java) { + mapper.isValidActor(expectedPkgName, actorPkgName) + } + mapper.rebuildIfDeferred() + deferRebuild = false + } + + assertThat(mapper.isValidActor(expectedPkgName, actorPkgName)).isTrue() + } + } + + // This asserts no other relationships are defined besides those tested above + assertThat(mapper.actorPkgToPkgs).containsExactlyEntriesIn(expected) + } + + private fun assertEmpty() = assertMapping() + + private fun mapper( + namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run { + mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME)) + }, + overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf( + mockOverlay().packageName to mapOf( + mockTarget().run { packageName to overlayables.keys } + ) + ) + ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider { + override fun getActorPkg(actor: String?) = + OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first + + override fun getTargetToOverlayables(pkg: AndroidPackage) = + overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap() + }) + + private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { "$TARGET_PACKAGE_NAME$increment" } + whenever(overlayables) { mapOf("overlayableName$increment" to ACTOR_NAME) } + whenever(toString()) { "Package{$packageName}" } + } + + private fun mockOverlay(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { "$OVERLAY_PACKAGE_NAME$increment" } + whenever(overlayables) { emptyMap<String, String>() } + whenever(toString()) { "Package{$packageName}" } + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 4fc625a1e1fb..82bbdcba5bc1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -35,6 +35,11 @@ import android.content.pm.parsing.ParsingPackage; import android.os.Build; import android.os.Process; import android.util.ArrayMap; +import android.util.ArraySet; + +import androidx.annotation.NonNull; + +import com.android.server.om.OverlayReferenceMapper; import org.junit.Before; import org.junit.Test; @@ -43,11 +48,18 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + @RunWith(JUnit4.class) public class AppsFilterTest { private static final int DUMMY_CALLING_UID = 10345; private static final int DUMMY_TARGET_UID = 10556; + private static final int DUMMY_ACTOR_UID = 10656; + private static final int DUMMY_OVERLAY_UID = 10756; + private static final int DUMMY_ACTOR_TWO_UID = 10856; @Mock AppsFilter.FeatureConfig mFeatureConfigMock; @@ -117,7 +129,7 @@ public class AppsFilterTest { @Test public void testSystemReadyPropogates() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); appsFilter.onSystemReady(); verify(mFeatureConfigMock).onSystemReady(); } @@ -125,7 +137,8 @@ public class AppsFilterTest { @Test public void testQueriesAction_FilterMatches() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_UID); @@ -138,7 +151,8 @@ public class AppsFilterTest { @Test public void testQueriesAction_NoMatchingAction_Filters() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); @@ -151,7 +165,8 @@ public class AppsFilterTest { @Test public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); @@ -169,7 +184,8 @@ public class AppsFilterTest { @Test public void testNoQueries_Filters() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); @@ -182,7 +198,8 @@ public class AppsFilterTest { @Test public void testForceQueryable_DoesntFilter() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); @@ -195,7 +212,8 @@ public class AppsFilterTest { @Test public void testForceQueryableByDevice_SystemCaller_DoesntFilter() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false); + new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID, @@ -209,7 +227,8 @@ public class AppsFilterTest { @Test public void testForceQueryableByDevice_NonSystemCaller_Filters() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false); + new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); @@ -224,7 +243,8 @@ public class AppsFilterTest { public void testSystemQueryable_DoesntFilter() { final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, - true /* system force queryable */); + true /* system force queryable */, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID, @@ -238,7 +258,8 @@ public class AppsFilterTest { @Test public void testQueriesPackage_DoesntFilter() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); @@ -253,7 +274,8 @@ public class AppsFilterTest { when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))) .thenReturn(false); final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage( appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); @@ -266,20 +288,22 @@ public class AppsFilterTest { @Test public void testSystemUid_DoesntFilter() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0)); - assertFalse(appsFilter.shouldFilterApplication( - Process.FIRST_APPLICATION_UID - 1, null, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1, + null, target, 0)); } @Test public void testNonSystemUid_NoCallingSetting_Filters() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); @@ -290,7 +314,8 @@ public class AppsFilterTest { @Test public void testNoTargetPackage_filters() { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); PackageSetting target = new PackageSettingBuilder() .setName("com.some.package") @@ -304,6 +329,127 @@ public class AppsFilterTest { assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } + @Test + public void testActsOnTargetOfOverlay() { + final String actorName = "overlay://test/actorName"; + + ParsingPackage target = pkg("com.some.package.target") + .addOverlayable("overlayableName", actorName); + ParsingPackage overlay = pkg("com.some.package.overlay") + .setIsOverlay(true) + .setOverlayTarget(target.getPackageName()) + .setOverlayTargetName("overlayableName"); + ParsingPackage actor = pkg("com.some.package.actor"); + + final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false, + new OverlayReferenceMapper.Provider() { + @Nullable + @Override + public String getActorPkg(String actorString) { + if (actorName.equals(actorString)) { + return actor.getPackageName(); + } + return null; + } + + @NonNull + @Override + public Map<String, Set<String>> getTargetToOverlayables( + @NonNull AndroidPackage pkg) { + if (overlay.getPackageName().equals(pkg.getPackageName())) { + Map<String, Set<String>> map = new ArrayMap<>(); + Set<String> set = new ArraySet<>(); + set.add(overlay.getOverlayTargetName()); + map.put(overlay.getOverlayTarget(), set); + return map; + } + return Collections.emptyMap(); + } + }); + appsFilter.onSystemReady(); + + PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID); + PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID); + PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_UID); + + // Actor can see both target and overlay + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting, + targetSetting, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting, + overlaySetting, 0)); + + // But target/overlay can't see each other + assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting, + overlaySetting, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting, + targetSetting, 0)); + + // And can't see the actor + assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting, + actorSetting, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting, + actorSetting, 0)); + } + + @Test + public void testActsOnTargetOfOverlayThroughSharedUser() { + final String actorName = "overlay://test/actorName"; + + ParsingPackage target = pkg("com.some.package.target") + .addOverlayable("overlayableName", actorName); + ParsingPackage overlay = pkg("com.some.package.overlay") + .setIsOverlay(true) + .setOverlayTarget(target.getPackageName()) + .setOverlayTargetName("overlayableName"); + ParsingPackage actorOne = pkg("com.some.package.actor.one"); + ParsingPackage actorTwo = pkg("com.some.package.actor.two"); + + final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false, + new OverlayReferenceMapper.Provider() { + @Nullable + @Override + public String getActorPkg(String actorString) { + // Only actorOne is mapped as a valid actor + if (actorName.equals(actorString)) { + return actorOne.getPackageName(); + } + return null; + } + + @NonNull + @Override + public Map<String, Set<String>> getTargetToOverlayables( + @NonNull AndroidPackage pkg) { + if (overlay.getPackageName().equals(pkg.getPackageName())) { + Map<String, Set<String>> map = new ArrayMap<>(); + Set<String> set = new ArraySet<>(); + set.add(overlay.getOverlayTargetName()); + map.put(overlay.getOverlayTarget(), set); + return map; + } + return Collections.emptyMap(); + } + }); + appsFilter.onSystemReady(); + + PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID); + PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID); + PackageSetting actorOneSetting = simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_UID); + PackageSetting actorTwoSetting = simulateAddPackage(appsFilter, actorTwo, + DUMMY_ACTOR_TWO_UID); + + SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser", + actorOneSetting.pkgFlags, actorOneSetting.pkgPrivateFlags); + actorSharedSetting.addPackage(actorOneSetting); + actorSharedSetting.addPackage(actorTwoSetting); + + // actorTwo can see both target and overlay + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting, + targetSetting, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting, + overlaySetting, 0)); + } + private interface WithSettingBuilder { PackageSettingBuilder withBuilder(PackageSettingBuilder builder); } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java index 5baeedefef05..2473997a61c9 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java @@ -22,7 +22,7 @@ import android.util.SparseArray; import java.io.File; -class PackageSettingBuilder { +public class PackageSettingBuilder { private String mName; private String mRealName; private String mCodePath; diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp index f71be7b0b7d3..027b1d6799fd 100644 --- a/tests/utils/testutils/Android.bp +++ b/tests/utils/testutils/Android.bp @@ -17,11 +17,16 @@ java_library { name: "frameworks-base-testutils", - srcs: ["java/**/*.java"], + srcs: [ + "java/**/*.java", + "java/**/*.kt", + ], static_libs: [ "junit", "hamcrest-library", + "truth-prebuilt", + "mockito-target-minus-junit4", ], libs: [ diff --git a/tests/utils/testutils/java/test/package-info.java b/tests/utils/testutils/java/test/package-info.java new file mode 100644 index 000000000000..c34d7b21ab84 --- /dev/null +++ b/tests/utils/testutils/java/test/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package separated from android. because placing classes under android.'s .test/.util + * may be confused with tests for that actual android subpackage. + **/ +package test; diff --git a/tests/utils/testutils/java/test/util/MockitoUtils.kt b/tests/utils/testutils/java/test/util/MockitoUtils.kt new file mode 100644 index 000000000000..5151abe54108 --- /dev/null +++ b/tests/utils/testutils/java/test/util/MockitoUtils.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test.util + +import org.mockito.Answers +import org.mockito.Mockito +import org.mockito.invocation.InvocationOnMock +import org.mockito.stubbing.Answer +import org.mockito.stubbing.Stubber + +object MockitoUtils { + val ANSWER_THROWS = Answer<Any?> { + when (val name = it.method.name) { + "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it) + else -> { + val arguments = it.arguments + ?.takeUnless { it.isEmpty() } + ?.joinToString() + ?.let { + "with $it" + } + .orEmpty() + + throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " + + "$arguments should not be called") + } + } + } +} + +inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block) + +fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock) +fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock) + +@Suppress("UNCHECKED_CAST") +fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) = + Mockito.`when`(mock).thenAnswer { block(it) } + +fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { } + +inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T { + val swappingAnswer = object : Answer<Any?> { + var delegate: Answer<*> = Answers.RETURNS_DEFAULTS + + override fun answer(invocation: InvocationOnMock?): Any? { + return delegate.answer(invocation) + } + } + + return Mockito.mock(T::class.java, swappingAnswer).apply(block) + .also { + // To allow when() usage inside block, only swap to throwing afterwards + swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS + } +} |