diff options
5 files changed, 465 insertions, 69 deletions
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index a8a6a723ce74..a5ba82fe162a 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -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; } /** diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java index 1d0447842cac..adf8f0d3e486 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java @@ -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) { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index 0f99e1963f28..5aed3670e5c7 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -57,6 +57,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 = { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index a3e1a9cdc524..3a4b849bac3c 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -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; } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java index ea71b28c6acd..da2d162d46f9 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java @@ -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); } } |