Merge "Add pm command for getOwnersForDomain API" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-04-24 00:13:52 +00:00
committed by Android (Google) Code Review
5 changed files with 465 additions and 69 deletions

View File

@@ -17,6 +17,7 @@
package com.android.server.pm.verify.domain;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Intent;
@@ -33,6 +34,8 @@ import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -44,6 +47,12 @@ public class DomainVerificationCollector {
private static final int MAX_DOMAINS_BYTE_SIZE = 1024 * 1024;
private static final BiFunction<ArraySet<String>, String, Boolean> ARRAY_SET_COLLECTOR =
(set, domain) -> {
set.add(domain);
return null;
};
@NonNull
private final PlatformCompat mPlatformCompat;
@@ -105,27 +114,62 @@ public class DomainVerificationCollector {
return collectDomains(pkg, true /* checkAutoVerify */, false /* valid */);
}
public boolean containsWebDomain(@NonNull AndroidPackage pkg, @NonNull String targetDomain) {
return collectDomains(pkg, false /* checkAutoVerify */, true /* valid */, null,
(BiFunction<Void, String, Boolean>) (unused, domain) -> {
if (Objects.equals(targetDomain, domain)) {
return true;
}
return null;
}) != null;
}
public boolean containsAutoVerifyDomain(@NonNull AndroidPackage pkg,
@NonNull String targetDomain) {
return collectDomains(pkg, true /* checkAutoVerify */, true /* valid */, null,
(BiFunction<Void, String, Boolean>) (unused, domain) -> {
if (Objects.equals(targetDomain, domain)) {
return true;
}
return null;
}) != null;
}
@NonNull
private ArraySet<String> collectDomains(@NonNull AndroidPackage pkg,
boolean checkAutoVerify, boolean valid) {
ArraySet<String> domains = new ArraySet<>();
collectDomains(pkg, checkAutoVerify, valid, domains, ARRAY_SET_COLLECTOR);
return domains;
}
@NonNull
private <InitialValue, ReturnValue> ReturnValue collectDomains(@NonNull AndroidPackage pkg,
boolean checkAutoVerify, boolean valid, @Nullable InitialValue initialValue,
@NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
boolean restrictDomains =
DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS);
if (restrictDomains) {
return collectDomainsInternal(pkg, checkAutoVerify, valid);
return collectDomainsInternal(pkg, checkAutoVerify, valid, initialValue,
domainCollector);
} else {
return collectDomainsLegacy(pkg, checkAutoVerify, valid);
return collectDomainsLegacy(pkg, checkAutoVerify, valid, initialValue, domainCollector);
}
}
/**
* @see #RESTRICT_DOMAINS
*/
private ArraySet<String> collectDomainsLegacy(@NonNull AndroidPackage pkg,
boolean checkAutoVerify, boolean valid) {
@Nullable
private <InitialValue, ReturnValue> ReturnValue collectDomainsLegacy(
@NonNull AndroidPackage pkg, boolean checkAutoVerify, boolean valid,
@Nullable InitialValue initialValue,
@NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
if (!checkAutoVerify) {
// Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2
return collectDomainsInternal(pkg, false /* checkAutoVerify */, true /* valid */);
return collectDomainsInternal(pkg, false /* checkAutoVerify */, true /* valid */,
initialValue, domainCollector);
}
List<ParsedActivity> activities = pkg.getActivities();
@@ -148,11 +192,10 @@ public class DomainVerificationCollector {
}
if (!needsAutoVerify) {
return new ArraySet<>();
return null;
}
}
ArraySet<String> domains = new ArraySet<>();
int totalSize = 0;
boolean underMaxSize = true;
for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize;
@@ -169,22 +212,30 @@ public class DomainVerificationCollector {
if (isValidHost(host) == valid) {
totalSize += byteSizeOf(host);
underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
domains.add(host);
ReturnValue returnValue = domainCollector.apply(initialValue, host);
if (returnValue != null) {
return returnValue;
}
}
}
}
}
}
return domains;
return null;
}
/**
* @see #RESTRICT_DOMAINS
* @param domainCollector Function to call with initialValue and a valid host. Should return
* a non-null value if the function should return immediately
* after the currently processed host.
*/
private ArraySet<String> collectDomainsInternal(@NonNull AndroidPackage pkg,
boolean checkAutoVerify, boolean valid) {
ArraySet<String> domains = new ArraySet<>();
@Nullable
private <InitialValue, ReturnValue> ReturnValue collectDomainsInternal(
@NonNull AndroidPackage pkg, boolean checkAutoVerify, boolean valid,
@Nullable InitialValue initialValue,
@NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
int totalSize = 0;
boolean underMaxSize = true;
@@ -226,13 +277,16 @@ public class DomainVerificationCollector {
if (isValidHost(host) == valid) {
totalSize += byteSizeOf(host);
underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
domains.add(host);
ReturnValue returnValue = domainCollector.apply(initialValue, host);
if (returnValue != null) {
return returnValue;
}
}
}
}
}
return domains;
return null;
}
/**

View File

@@ -37,6 +37,7 @@ import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@SuppressWarnings("PointlessBooleanExpression")
@@ -100,6 +101,70 @@ public class DomainVerificationDebug {
}
}
/**
* @param userIdToApprovalLevelToOwners Mapping of user ID to approval level to domain owners.
*/
public void printOwners(@NonNull IndentingPrintWriter writer, @NonNull String domain,
SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners) {
writer.println(domain + ":");
writer.increaseIndent();
if (userIdToApprovalLevelToOwners.size() == 0) {
writer.println("none");
writer.decreaseIndent();
return;
}
int usersSize = userIdToApprovalLevelToOwners.size();
for (int userIndex = 0; userIndex < usersSize; userIndex++) {
int userId = userIdToApprovalLevelToOwners.keyAt(userIndex);
SparseArray<List<String>> approvalLevelToOwners =
userIdToApprovalLevelToOwners.valueAt(userIndex);
if (approvalLevelToOwners.size() == 0) {
continue;
}
boolean printedUserHeader = false;
int approvalsSize = approvalLevelToOwners.size();
for (int approvalIndex = 0; approvalIndex < approvalsSize; approvalIndex++) {
int approvalLevel = approvalLevelToOwners.keyAt(approvalIndex);
if (approvalLevel < DomainVerificationManagerInternal.APPROVAL_LEVEL_UNVERIFIED) {
continue;
}
if (!printedUserHeader) {
writer.println("User " + userId + ":");
writer.increaseIndent();
printedUserHeader = true;
}
String approvalString =
DomainVerificationManagerInternal.approvalLevelToDebugString(approvalLevel);
List<String> owners = approvalLevelToOwners.valueAt(approvalIndex);
writer.println(approvalString + "[" + approvalLevel + "]" + ":");
writer.increaseIndent();
if (owners.size() == 0) {
writer.println("none");
writer.decreaseIndent();
continue;
}
int ownersSize = owners.size();
for (int ownersIndex = 0; ownersIndex < ownersSize; ownersIndex++) {
writer.println(owners.get(ownersIndex));
}
writer.decreaseIndent();
}
if (printedUserHeader) {
writer.decreaseIndent();
}
}
writer.decreaseIndent();
}
boolean printState(@NonNull IndentingPrintWriter writer,
@NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
@NonNull ArrayMap<String, Integer> reusedMap, boolean wasHeaderPrinted) {

View File

@@ -56,6 +56,28 @@ public interface DomainVerificationManagerInternal {
UUID DISABLED_ID = new UUID(0, 0);
/**
* The app was not installed for the user.
*/
int APPROVAL_LEVEL_NOT_INSTALLED = -4;
/**
* The app was not enabled for the user.
*/
int APPROVAL_LEVEL_DISABLED = -3;
/**
* The app has not declared this domain in a valid web intent-filter in their manifest, and so
* would never be able to be approved for this domain.
*/
int APPROVAL_LEVEL_UNDECLARED = -2;
/**
* The app has declared this domain as a valid autoVerify domain, but it failed or has not
* succeeded verification.
*/
int APPROVAL_LEVEL_UNVERIFIED = -1;
/**
* The app has not been approved for this domain and should never be able to open it through
* an implicit web intent.
@@ -117,10 +139,14 @@ public interface DomainVerificationManagerInternal {
* by approval priority. A higher numerical value means the package should override all lower
* values. This means that comparison using less/greater than IS valid.
*
* Negative values are possible, although not implemented, reserved if explicit disable of a
* package for a domain needs to be tracked.
* Negative values are possible, used for tracking specific reasons for why an app doesn't have
* approval.
*/
@IntDef({
APPROVAL_LEVEL_NOT_INSTALLED,
APPROVAL_LEVEL_DISABLED,
APPROVAL_LEVEL_UNDECLARED,
APPROVAL_LEVEL_UNVERIFIED,
APPROVAL_LEVEL_NONE,
APPROVAL_LEVEL_LEGACY_ASK,
APPROVAL_LEVEL_LEGACY_ALWAYS,
@@ -131,6 +157,33 @@ public interface DomainVerificationManagerInternal {
@interface ApprovalLevel {
}
static String approvalLevelToDebugString(@ApprovalLevel int level) {
switch (level) {
case APPROVAL_LEVEL_NOT_INSTALLED:
return "NOT_INSTALLED";
case APPROVAL_LEVEL_DISABLED:
return "DISABLED";
case APPROVAL_LEVEL_UNDECLARED:
return "UNDECLARED";
case APPROVAL_LEVEL_UNVERIFIED:
return "UNVERIFIED";
case APPROVAL_LEVEL_NONE:
return "NONE";
case APPROVAL_LEVEL_LEGACY_ASK:
return "LEGACY_ASK";
case APPROVAL_LEVEL_LEGACY_ALWAYS:
return "LEGACY_ALWAYS";
case APPROVAL_LEVEL_SELECTION:
return "USER_SELECTION";
case APPROVAL_LEVEL_VERIFIED:
return "VERIFIED";
case APPROVAL_LEVEL_INSTANT_APP:
return "INSTANT_APP";
default:
return "UNKNOWN";
}
}
/** @see DomainVerificationManager#getDomainVerificationInfo(String) */
@Nullable
@RequiresPermission(anyOf = {

View File

@@ -741,62 +741,21 @@ public class DomainVerificationService extends SystemService
});
}
@NonNull
public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
Objects.requireNonNull(domain);
mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
userId);
SparseArray<List<String>> levelToPackages = new SparseArray<>();
return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
// First, collect the raw approval level values
synchronized (mLock) {
final int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String packageName = pkgState.getPackageName();
PackageSetting pkgSetting = pkgSettings.apply(packageName);
if (pkgSetting == null) {
continue;
}
int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
if (level <= APPROVAL_LEVEL_NONE) {
continue;
}
List<String> list = levelToPackages.get(level);
if (list == null) {
list = new ArrayList<>();
levelToPackages.put(level, list);
}
list.add(packageName);
}
}
final int size = levelToPackages.size();
if (size == 0) {
SparseArray<List<String>> levelToPackages = getOwnersForDomainInternal(domain, false,
userId, pkgSettings);
if (levelToPackages.size() == 0) {
return emptyList();
}
// Then sort them ascending by first installed time, with package name as tie breaker
for (int index = 0; index < size; index++) {
levelToPackages.valueAt(index).sort((first, second) -> {
PackageSetting firstPkgSetting = pkgSettings.apply(first);
PackageSetting secondPkgSetting = pkgSettings.apply(second);
long firstInstallTime =
firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
long secondInstallTime =
secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
if (firstInstallTime != secondInstallTime) {
return (int) (firstInstallTime - secondInstallTime);
}
return first.compareToIgnoreCase(second);
});
}
List<DomainOwner> owners = new ArrayList<>();
int size = levelToPackages.size();
for (int index = 0; index < size; index++) {
int level = levelToPackages.keyAt(index);
boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
@@ -811,6 +770,69 @@ public class DomainVerificationService extends SystemService
});
}
/**
* @param includeNegative See {@link #approvalLevelForDomain(PackageSetting, String, boolean,
* int, Object)}.
* @return Mapping of approval level to packages; packages are sorted by firstInstallTime. Null
* if no owners were found.
*/
@NonNull
private SparseArray<List<String>> getOwnersForDomainInternal(@NonNull String domain,
boolean includeNegative, @UserIdInt int userId,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
SparseArray<List<String>> levelToPackages = new SparseArray<>();
// First, collect the raw approval level values
synchronized (mLock) {
final int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String packageName = pkgState.getPackageName();
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
if (pkgSetting == null) {
continue;
}
int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
domain);
if (!includeNegative && level <= APPROVAL_LEVEL_NONE) {
continue;
}
List<String> list = levelToPackages.get(level);
if (list == null) {
list = new ArrayList<>();
levelToPackages.put(level, list);
}
list.add(packageName);
}
}
final int size = levelToPackages.size();
if (size == 0) {
return levelToPackages;
}
// Then sort them ascending by first installed time, with package name as tie breaker
for (int index = 0; index < size; index++) {
levelToPackages.valueAt(index).sort((first, second) -> {
PackageSetting firstPkgSetting = pkgSettingFunction.apply(first);
PackageSetting secondPkgSetting = pkgSettingFunction.apply(second);
long firstInstallTime =
firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
long secondInstallTime =
secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
if (firstInstallTime != secondInstallTime) {
return (int) (firstInstallTime - secondInstallTime);
}
return first.compareToIgnoreCase(second);
});
}
return levelToPackages;
}
@NonNull
@Override
public UUID generateNewId() {
@@ -1153,6 +1175,88 @@ public class DomainVerificationService extends SystemService
}
}
@Override
public void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
@Nullable String packageName, @Nullable @UserIdInt Integer userId)
throws NameNotFoundException {
mConnection.withPackageSettingsThrowing(pkgSettings -> {
synchronized (mLock) {
if (packageName == null) {
int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
try {
printOwnersForPackage(writer,
mAttachedPkgStates.valueAt(index).getPackageName(), userId,
pkgSettings);
} catch (NameNotFoundException ignored) {
// When iterating packages, if one doesn't exist somehow, ignore
}
}
} else {
printOwnersForPackage(writer, packageName, userId, pkgSettings);
}
}
});
}
private void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
@NonNull String packageName, @Nullable @UserIdInt Integer userId,
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException {
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
int size = domains.size();
if (size == 0) {
return;
}
writer.println(packageName + ":");
writer.increaseIndent();
for (int index = 0; index < size; index++) {
printOwnersForDomain(writer, domains.valueAt(index), userId, pkgSettingFunction);
}
writer.decreaseIndent();
}
@Override
public void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
@NonNull List<String> domains, @Nullable @UserIdInt Integer userId) {
mConnection.withPackageSettings(pkgSettings -> {
synchronized (mLock) {
int size = domains.size();
for (int index = 0; index < size; index++) {
printOwnersForDomain(writer, domains.get(index), userId, pkgSettings);
}
}
});
}
private void printOwnersForDomain(@NonNull IndentingPrintWriter writer, @NonNull String domain,
@Nullable @UserIdInt Integer userId,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners =
new SparseArray<>();
if (userId == null || userId == UserHandle.USER_ALL) {
for (int aUserId : mConnection.getAllUserIds()) {
userIdToApprovalLevelToOwners.put(aUserId,
getOwnersForDomainInternal(domain, true, aUserId, pkgSettingFunction));
}
} else {
userIdToApprovalLevelToOwners.put(userId,
getOwnersForDomainInternal(domain, true, userId, pkgSettingFunction));
}
mDebug.printOwners(writer, domain, userIdToApprovalLevelToOwners);
}
@NonNull
@Override
public DomainVerificationShell getShell() {
@@ -1427,7 +1531,7 @@ public class DomainVerificationService extends SystemService
// Find all approval levels
int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId,
pkgSettingFunction);
if (highestApproval == APPROVAL_LEVEL_NONE) {
if (highestApproval <= APPROVAL_LEVEL_NONE) {
return Pair.create(emptyList(), highestApproval);
}
@@ -1484,7 +1588,7 @@ public class DomainVerificationService extends SystemService
fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE);
continue;
}
int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
int approval = approvalLevelForDomain(pkgSetting, domain, false, userId, domain);
highestApproval = Math.max(highestApproval, approval);
fillInfoMapForSamePackage(inputMap, packageName, approval);
}
@@ -1600,18 +1704,53 @@ public class DomainVerificationService extends SystemService
return APPROVAL_LEVEL_NONE;
}
return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), false, userId,
intent);
}
/**
* @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
* otherwise.
* @param includeNegative Whether to include negative values, which requires an expensive
* domain comparison operation.
* @param debugObject Should be an {@link Intent} if checking for resolution or a
* {@link String} otherwise.
*/
private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
@UserIdInt int userId, @NonNull Object debugObject) {
boolean includeNegative, @UserIdInt int userId, @NonNull Object debugObject) {
int approvalLevel = approvalLevelForDomainInternal(pkgSetting, host, includeNegative,
userId, debugObject);
if (includeNegative && approvalLevel == APPROVAL_LEVEL_NONE) {
PackageUserState pkgUserState = pkgSetting.readUserState(userId);
if (!pkgUserState.installed) {
return APPROVAL_LEVEL_NOT_INSTALLED;
}
AndroidPackage pkg = pkgSetting.getPkg();
if (pkg != null) {
if (!pkgUserState.isPackageEnabled(pkg)) {
return APPROVAL_LEVEL_DISABLED;
} else if (mCollector.containsAutoVerifyDomain(pkgSetting.getPkg(), host)) {
return APPROVAL_LEVEL_UNVERIFIED;
}
}
}
return approvalLevel;
}
private int approvalLevelForDomainInternal(@NonNull PackageSetting pkgSetting,
@NonNull String host, boolean includeNegative, @UserIdInt int userId,
@NonNull Object debugObject) {
String packageName = pkgSetting.getName();
final AndroidPackage pkg = pkgSetting.getPkg();
if (pkg != null && includeNegative && !mCollector.containsWebDomain(pkg, host)) {
if (DEBUG_APPROVAL) {
debugApproval(packageName, debugObject, userId, false,
"domain not declared");
}
return APPROVAL_LEVEL_UNDECLARED;
}
final PackageUserState pkgUserState = pkgSetting.readUserState(userId);
if (pkgUserState == null) {
if (DEBUG_APPROVAL) {
@@ -1749,6 +1888,7 @@ public class DomainVerificationService extends SystemService
private Pair<List<String>, Integer> getApprovedPackagesLocked(@NonNull String domain,
@UserIdInt int userId, int minimumApproval,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
boolean includeNegative = minimumApproval < APPROVAL_LEVEL_NONE;
int highestApproval = minimumApproval;
List<String> approvedPackages = emptyList();
@@ -1762,7 +1902,8 @@ public class DomainVerificationService extends SystemService
continue;
}
int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
domain);
if (level < minimumApproval) {
continue;
}

View File

@@ -110,6 +110,13 @@ public class DomainVerificationShell {
pw.println(" packages will be reset if no one package is specified.");
pw.println(" <ALLOWED>: true to allow the package to open auto verified links, false");
pw.println(" to disable");
pw.println(" get-app-link-owners [--user <USER_ID>] [--package <PACKAGE>] [<DOMAINS>]");
pw.println(" Print the owners for a specific domain for a given user in low to high");
pw.println(" priority order.");
pw.println(" --user <USER_ID>: the user to query for");
pw.println(" --package <PACKAGE>: optionally also print for all web domains declared");
pw.println(" by a package, or \"all\" to print all packages");
pw.println(" --<DOMAINS>: space separated list of domains to query for");
}
/**
@@ -132,6 +139,8 @@ public class DomainVerificationShell {
return runSetAppLinksUserState(commandHandler);
case "set-app-links-allowed":
return runSetAppLinksAllowed(commandHandler);
case "get-app-link-owners":
return runGetAppLinkOwners(commandHandler);
}
return null;
@@ -420,6 +429,67 @@ public class DomainVerificationShell {
return true;
}
// pm get-app-link-owners [--user <USER_ID>] [--package <PACKAGE>] [<DOMAINS>]
private boolean runGetAppLinkOwners(@NonNull BasicShellCommandHandler commandHandler) {
String packageName = null;
Integer userId = null;
String option;
while ((option = commandHandler.getNextOption()) != null) {
switch (option) {
case "--user":
userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
break;
case "--package":
packageName = commandHandler.getNextArgRequired();
if (TextUtils.isEmpty(packageName)) {
commandHandler.getErrPrintWriter().println("Error: no package specified");
return false;
}
break;
default:
commandHandler.getErrPrintWriter().println(
"Error: unexpected option: " + option);
return false;
}
}
ArrayList<String> domains = getRemainingArgs(commandHandler);
if (domains.isEmpty() && TextUtils.isEmpty(packageName)) {
commandHandler.getErrPrintWriter()
.println("Error: no package name or domain specified");
return false;
}
if (userId != null) {
userId = translateUserId(userId, "runSetAppLinksAllowed");
}
try (IndentingPrintWriter writer = new IndentingPrintWriter(
commandHandler.getOutPrintWriter(), /* singleIndent */ " ", /* wrapLength */
120)) {
writer.increaseIndent();
if (packageName != null) {
if (packageName.equals("all")) {
packageName = null;
}
try {
mCallback.printOwnersForPackage(writer, packageName, userId);
} catch (NameNotFoundException e) {
commandHandler.getErrPrintWriter()
.println("Error: package not found: " + packageName);
return false;
}
}
if (!domains.isEmpty()) {
mCallback.printOwnersForDomains(writer, domains, userId);
}
writer.decreaseIndent();
return true;
}
}
@NonNull
private ArrayList<String> getRemainingArgs(@NonNull BasicShellCommandHandler commandHandler) {
ArrayList<String> args = new ArrayList<>();
String arg;
@@ -534,5 +604,18 @@ public class DomainVerificationShell {
*/
void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable @UserIdInt Integer userId) throws NameNotFoundException;
/**
* Print the owners for all domains in a given package.
*/
void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
@Nullable String packageName, @Nullable @UserIdInt Integer userId)
throws NameNotFoundException;
/**
* Print the owners for the given domains.
*/
void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
@NonNull List<String> domains, @Nullable @UserIdInt Integer userId);
}
}