Revert "Revert "Overlay, actor, and target app visibility handling""
This reverts commit 637138dd94.
Exempt-From-Owner-Approval: Revert of revert, all necessary review will be in follow up
Reason for revert: Revert of revert for re-merge, follow up will fix test conflict
Change-Id: I6c5c3209d1fdb62cb6f733961ee8737dea4ca89b
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -772,6 +777,13 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
|
||||
return this;
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -2124,6 +2136,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
|
||||
return overlayTargetName;
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -307,6 +307,17 @@ public class CollectionUtils {
|
||||
return cur;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1073,8 +1073,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) {
|
||||
@@ -1097,18 +1095,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();
|
||||
}
|
||||
|
||||
@@ -1136,57 +1128,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();
|
||||
PackageInfo packageInfo = getPackageInfo(packageName, userId);
|
||||
if (packageInfo == null) {
|
||||
throw new IOException("Unable to get target package");
|
||||
}
|
||||
|
||||
String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
|
||||
|
||||
ApkAssets apkAssets = null;
|
||||
try {
|
||||
PackageInfo packageInfo = getPackageInfo(packageName, userId);
|
||||
if (packageInfo == null) {
|
||||
throw new IOException("Unable to get target package");
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1229,16 +1209,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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}" }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: [
|
||||
|
||||
21
tests/utils/testutils/java/test/package-info.java
Normal file
21
tests/utils/testutils/java/test/package-info.java
Normal file
@@ -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;
|
||||
70
tests/utils/testutils/java/test/util/MockitoUtils.kt
Normal file
70
tests/utils/testutils/java/test/util/MockitoUtils.kt
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user