1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm.verify.domain; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserIdInt; 22 import android.app.ActivityManager; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.pm.verify.domain.DomainVerificationManager; 26 import android.content.pm.verify.domain.DomainVerificationState; 27 import android.content.pm.verify.domain.DomainVerificationUserState; 28 import android.os.Binder; 29 import android.os.UserHandle; 30 import android.text.TextUtils; 31 import android.util.ArraySet; 32 import android.util.IndentingPrintWriter; 33 34 import com.android.modules.utils.BasicShellCommandHandler; 35 36 import java.io.PrintWriter; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Locale; 42 import java.util.function.Function; 43 44 public class DomainVerificationShell { 45 46 @NonNull 47 private final Callback mCallback; 48 DomainVerificationShell(@onNull Callback callback)49 public DomainVerificationShell(@NonNull Callback callback) { 50 mCallback = callback; 51 } 52 printHelp(@onNull PrintWriter pw)53 public void printHelp(@NonNull PrintWriter pw) { 54 pw.println(" get-app-links [--user <USER_ID>] [<PACKAGE>]"); 55 pw.println(" Prints the domain verification state for the given package, or for all"); 56 pw.println(" packages if none is specified. State codes are defined as follows:"); 57 pw.println(" - none: nothing has been recorded for this domain"); 58 pw.println(" - verified: the domain has been successfully verified"); 59 pw.println(" - approved: force approved, usually through shell"); 60 pw.println(" - denied: force denied, usually through shell"); 61 pw.println(" - migrated: preserved verification from a legacy response"); 62 pw.println(" - restored: preserved verification from a user data restore"); 63 pw.println(" - legacy_failure: rejected by a legacy verifier, unknown reason"); 64 pw.println(" - system_configured: automatically approved by the device config"); 65 pw.println(" - >= 1024: Custom error code which is specific to the device verifier"); 66 pw.println(" --user <USER_ID>: include user selections (includes all domains, not"); 67 pw.println(" just autoVerify ones)"); 68 pw.println(" reset-app-links [--user <USER_ID>] [<PACKAGE>]"); 69 pw.println(" Resets domain verification state for the given package, or for all"); 70 pw.println(" packages if none is specified."); 71 pw.println(" --user <USER_ID>: clear user selection state instead; note this means"); 72 pw.println(" domain verification state will NOT be cleared"); 73 pw.println(" <PACKAGE>: the package to reset, or \"all\" to reset all packages"); 74 pw.println(" verify-app-links [--re-verify] [<PACKAGE>]"); 75 pw.println(" Broadcasts a verification request for the given package, or for all"); 76 pw.println(" packages if none is specified. Only sends if the package has previously"); 77 pw.println(" not recorded a response."); 78 pw.println(" --re-verify: send even if the package has recorded a response"); 79 pw.println(" set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>..."); 80 pw.println(" Manually set the state of a domain for a package. The domain must be"); 81 pw.println(" declared by the package as autoVerify for this to work. This command"); 82 pw.println(" will not report a failure for domains that could not be applied."); 83 pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages"); 84 pw.println(" <STATE>: the code to set the domains to, valid values are:"); 85 pw.println(" STATE_NO_RESPONSE (0): reset as if no response was ever recorded."); 86 pw.println(" STATE_SUCCESS (1): treat domain as successfully verified by domain."); 87 pw.println(" verification agent. Note that the domain verification agent can"); 88 pw.println(" override this."); 89 pw.println(" STATE_APPROVED (2): treat domain as always approved, preventing the"); 90 pw.println(" domain verification agent from changing it."); 91 pw.println(" STATE_DENIED (3): treat domain as always denied, preveting the domain"); 92 pw.println(" verification agent from changing it."); 93 pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to"); 94 pw.println(" change every domain."); 95 pw.println(" set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>]"); 96 pw.println(" <ENABLED> <DOMAINS>..."); 97 pw.println(" Manually set the state of a host user selection for a package. The domain"); 98 pw.println(" must be declared by the package for this to work. This command will not"); 99 pw.println(" report a failure for domains that could not be applied."); 100 pw.println(" --user <USER_ID>: the user to change selections for"); 101 pw.println(" --package <PACKAGE>: the package to set"); 102 pw.println(" <ENABLED>: whether or not to approve the domain"); 103 pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to"); 104 pw.println(" change every domain."); 105 pw.println(" set-app-links-allowed --user <USER_ID> [--package <PACKAGE>] <ALLOWED>"); 106 pw.println(" <ENABLED> <DOMAINS>..."); 107 pw.println(" Toggle the auto verified link handling setting for a package."); 108 pw.println(" --user <USER_ID>: the user to change selections for"); 109 pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages"); 110 pw.println(" packages will be reset if no one package is specified."); 111 pw.println(" <ALLOWED>: true to allow the package to open auto verified links, false"); 112 pw.println(" to disable"); 113 pw.println(" get-app-link-owners [--user <USER_ID>] [--package <PACKAGE>] [<DOMAINS>]"); 114 pw.println(" Print the owners for a specific domain for a given user in low to high"); 115 pw.println(" priority order."); 116 pw.println(" --user <USER_ID>: the user to query for"); 117 pw.println(" --package <PACKAGE>: optionally also print for all web domains declared"); 118 pw.println(" by a package, or \"all\" to print all packages"); 119 pw.println(" --<DOMAINS>: space separated list of domains to query for"); 120 } 121 122 /** 123 * Run a shell/debugging command. 124 * 125 * @return null if the command is unhandled, true if the command succeeded, false if it failed 126 */ runCommand(@onNull BasicShellCommandHandler commandHandler, @NonNull String command)127 public Boolean runCommand(@NonNull BasicShellCommandHandler commandHandler, 128 @NonNull String command) { 129 switch (command) { 130 case "get-app-links": 131 return runGetAppLinks(commandHandler); 132 case "reset-app-links": 133 return runResetAppLinks(commandHandler); 134 case "verify-app-links": 135 return runVerifyAppLinks(commandHandler); 136 case "set-app-links": 137 return runSetAppLinks(commandHandler); 138 case "set-app-links-user-selection": 139 return runSetAppLinksUserState(commandHandler); 140 case "set-app-links-allowed": 141 return runSetAppLinksAllowed(commandHandler); 142 case "get-app-link-owners": 143 return runGetAppLinkOwners(commandHandler); 144 } 145 146 return null; 147 } 148 149 150 // pm set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>... runSetAppLinks(@onNull BasicShellCommandHandler commandHandler)151 private boolean runSetAppLinks(@NonNull BasicShellCommandHandler commandHandler) { 152 String packageName = null; 153 154 String option; 155 while ((option = commandHandler.getNextOption()) != null) { 156 if (option.equals("--package")) { 157 packageName = commandHandler.getNextArgRequired(); 158 } else { 159 commandHandler.getErrPrintWriter().println("Error: unknown option: " + option); 160 return false; 161 } 162 } 163 164 if (TextUtils.isEmpty(packageName)) { 165 commandHandler.getErrPrintWriter().println("Error: no package specified"); 166 return false; 167 } else if (packageName.equalsIgnoreCase("all")) { 168 packageName = null; 169 } 170 171 String state = commandHandler.getNextArgRequired(); 172 int stateInt; 173 switch (state) { 174 case "STATE_NO_RESPONSE": 175 case "0": 176 stateInt = DomainVerificationState.STATE_NO_RESPONSE; 177 break; 178 case "STATE_SUCCESS": 179 case "1": 180 stateInt = DomainVerificationState.STATE_SUCCESS; 181 break; 182 case "STATE_APPROVED": 183 case "2": 184 stateInt = DomainVerificationState.STATE_APPROVED; 185 break; 186 case "STATE_DENIED": 187 case "3": 188 stateInt = DomainVerificationState.STATE_DENIED; 189 break; 190 default: 191 commandHandler.getErrPrintWriter().println("Invalid state option: " + state); 192 return false; 193 } 194 195 ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler)); 196 if (domains.isEmpty()) { 197 commandHandler.getErrPrintWriter().println("No domains specified"); 198 return false; 199 } 200 201 if (domains.size() == 1 && domains.contains("all")) { 202 domains = null; 203 } 204 205 try { 206 mCallback.setDomainVerificationStatusInternal(packageName, stateInt, 207 domains); 208 } catch (NameNotFoundException e) { 209 commandHandler.getErrPrintWriter().println("Package not found: " + packageName); 210 return false; 211 } 212 return true; 213 } 214 215 // pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>... runSetAppLinksUserState(@onNull BasicShellCommandHandler commandHandler)216 private boolean runSetAppLinksUserState(@NonNull BasicShellCommandHandler commandHandler) { 217 Integer userId = null; 218 String packageName = null; 219 220 String option; 221 while ((option = commandHandler.getNextOption()) != null) { 222 switch (option) { 223 case "--user": 224 userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired()); 225 break; 226 case "--package": 227 packageName = commandHandler.getNextArgRequired(); 228 break; 229 default: 230 commandHandler.getErrPrintWriter().println("Error: unknown option: " + option); 231 return false; 232 } 233 } 234 235 if (TextUtils.isEmpty(packageName)) { 236 commandHandler.getErrPrintWriter().println("Error: no package specified"); 237 return false; 238 } 239 240 if (userId == null) { 241 commandHandler.getErrPrintWriter().println("Error: User ID not specified"); 242 return false; 243 } 244 245 userId = translateUserId(userId, "runSetAppLinksUserState"); 246 247 String enabledArg = commandHandler.getNextArg(); 248 if (TextUtils.isEmpty(enabledArg)) { 249 commandHandler.getErrPrintWriter().println("Error: enabled param not specified"); 250 return false; 251 } 252 253 boolean enabled; 254 try { 255 enabled = parseEnabled(enabledArg); 256 } catch (IllegalArgumentException e) { 257 commandHandler.getErrPrintWriter() 258 .println("Error: invalid enabled param: " + e.getMessage()); 259 return false; 260 } 261 262 ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler)); 263 if (domains.isEmpty()) { 264 commandHandler.getErrPrintWriter().println("No domains specified"); 265 return false; 266 } 267 268 if (domains.size() == 1 && domains.contains("all")) { 269 domains = null; 270 } 271 272 try { 273 mCallback.setDomainVerificationUserSelectionInternal(userId, packageName, enabled, 274 domains); 275 } catch (NameNotFoundException e) { 276 commandHandler.getErrPrintWriter().println("Package not found: " + packageName); 277 return false; 278 } 279 return true; 280 } 281 282 // pm get-app-links [--user <USER_ID>] [<PACKAGE>] runGetAppLinks(@onNull BasicShellCommandHandler commandHandler)283 private boolean runGetAppLinks(@NonNull BasicShellCommandHandler commandHandler) { 284 Integer userId = null; 285 286 String option; 287 while ((option = commandHandler.getNextOption()) != null) { 288 if (option.equals("--user")) { 289 userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired()); 290 } else { 291 commandHandler.getErrPrintWriter().println("Error: unknown option: " + option); 292 return false; 293 } 294 } 295 296 userId = userId == null ? null : translateUserId(userId, "runGetAppLinks"); 297 298 String packageName = commandHandler.getNextArg(); 299 300 try (IndentingPrintWriter writer = new IndentingPrintWriter( 301 commandHandler.getOutPrintWriter(), /* singleIndent */ " ", /* wrapLength */ 302 120)) { 303 writer.increaseIndent(); 304 try { 305 mCallback.printState(writer, packageName, userId); 306 } catch (NameNotFoundException e) { 307 commandHandler.getErrPrintWriter().println( 308 "Error: package " + packageName + " unavailable"); 309 return false; 310 } 311 writer.decreaseIndent(); 312 return true; 313 } 314 } 315 316 // pm reset-app-links [--user USER_ID] [<PACKAGE>] runResetAppLinks(@onNull BasicShellCommandHandler commandHandler)317 private boolean runResetAppLinks(@NonNull BasicShellCommandHandler commandHandler) { 318 Integer userId = null; 319 320 String option; 321 while ((option = commandHandler.getNextOption()) != null) { 322 if (option.equals("--user")) { 323 userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired()); 324 } else { 325 commandHandler.getErrPrintWriter().println("Error: unknown option: " + option); 326 return false; 327 } 328 } 329 330 userId = userId == null ? null : translateUserId(userId, "runResetAppLinks"); 331 332 List<String> packageNames; 333 String pkgNameArg = commandHandler.peekNextArg(); 334 if (TextUtils.isEmpty(pkgNameArg)) { 335 commandHandler.getErrPrintWriter().println("Error: no package specified"); 336 return false; 337 } else if (pkgNameArg.equalsIgnoreCase("all")) { 338 packageNames = null; 339 } else { 340 packageNames = Arrays.asList(commandHandler.peekRemainingArgs()); 341 } 342 343 if (userId != null) { 344 mCallback.clearUserStates(packageNames, userId); 345 } else { 346 mCallback.clearDomainVerificationState(packageNames); 347 } 348 349 return true; 350 } 351 352 // pm verify-app-links [--re-verify] [<PACKAGE>] runVerifyAppLinks(@onNull BasicShellCommandHandler commandHandler)353 private boolean runVerifyAppLinks(@NonNull BasicShellCommandHandler commandHandler) { 354 boolean reVerify = false; 355 String option; 356 while ((option = commandHandler.getNextOption()) != null) { 357 if (option.equals("--re-verify")) { 358 reVerify = true; 359 } else { 360 commandHandler.getErrPrintWriter().println("Error: unknown option: " + option); 361 return false; 362 } 363 } 364 365 List<String> packageNames = null; 366 String pkgNameArg = commandHandler.getNextArg(); 367 if (!TextUtils.isEmpty(pkgNameArg)) { 368 packageNames = Collections.singletonList(pkgNameArg); 369 } 370 371 mCallback.verifyPackages(packageNames, reVerify); 372 373 return true; 374 } 375 376 // pm set-app-links-allowed [--package <PACKAGE>] [--user <USER_ID>] <ALLOWED> runSetAppLinksAllowed(@onNull BasicShellCommandHandler commandHandler)377 private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) { 378 String packageName = null; 379 Integer userId = null; 380 String option; 381 while ((option = commandHandler.getNextOption()) != null) { 382 if (option.equals("--package")) { 383 packageName = commandHandler.getNextArg(); 384 } else if (option.equals("--user")) { 385 userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired()); 386 } else { 387 commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option); 388 return false; 389 } 390 } 391 392 if (TextUtils.isEmpty(packageName)) { 393 commandHandler.getErrPrintWriter().println("Error: no package specified"); 394 return false; 395 } else if (packageName.equalsIgnoreCase("all")) { 396 packageName = null; 397 } 398 399 if (userId == null) { 400 commandHandler.getErrPrintWriter().println("Error: user ID not specified"); 401 return false; 402 } 403 404 String allowedArg = commandHandler.getNextArg(); 405 if (TextUtils.isEmpty(allowedArg)) { 406 commandHandler.getErrPrintWriter().println("Error: allowed setting not specified"); 407 return false; 408 } 409 410 boolean allowed; 411 try { 412 allowed = parseEnabled(allowedArg); 413 } catch (IllegalArgumentException e) { 414 commandHandler.getErrPrintWriter() 415 .println("Error: invalid allowed setting: " + e.getMessage()); 416 return false; 417 } 418 419 userId = translateUserId(userId, "runSetAppLinksAllowed"); 420 421 try { 422 mCallback.setDomainVerificationLinkHandlingAllowedInternal(packageName, allowed, 423 userId); 424 } catch (NameNotFoundException e) { 425 commandHandler.getErrPrintWriter().println("Package not found: " + packageName); 426 return false; 427 } 428 429 return true; 430 } 431 432 // pm get-app-link-owners [--user <USER_ID>] [--package <PACKAGE>] [<DOMAINS>] runGetAppLinkOwners(@onNull BasicShellCommandHandler commandHandler)433 private boolean runGetAppLinkOwners(@NonNull BasicShellCommandHandler commandHandler) { 434 String packageName = null; 435 Integer userId = null; 436 String option; 437 while ((option = commandHandler.getNextOption()) != null) { 438 switch (option) { 439 case "--user": 440 userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired()); 441 break; 442 case "--package": 443 packageName = commandHandler.getNextArgRequired(); 444 if (TextUtils.isEmpty(packageName)) { 445 commandHandler.getErrPrintWriter().println("Error: no package specified"); 446 return false; 447 } 448 break; 449 default: 450 commandHandler.getErrPrintWriter().println( 451 "Error: unexpected option: " + option); 452 return false; 453 } 454 } 455 456 ArrayList<String> domains = getRemainingArgs(commandHandler); 457 if (domains.isEmpty() && TextUtils.isEmpty(packageName)) { 458 commandHandler.getErrPrintWriter() 459 .println("Error: no package name or domain specified"); 460 return false; 461 } 462 463 if (userId != null) { 464 userId = translateUserId(userId, "runSetAppLinksAllowed"); 465 } 466 467 try (IndentingPrintWriter writer = new IndentingPrintWriter( 468 commandHandler.getOutPrintWriter(), /* singleIndent */ " ", /* wrapLength */ 469 120)) { 470 writer.increaseIndent(); 471 if (packageName != null) { 472 if (packageName.equals("all")) { 473 packageName = null; 474 } 475 476 try { 477 mCallback.printOwnersForPackage(writer, packageName, userId); 478 } catch (NameNotFoundException e) { 479 commandHandler.getErrPrintWriter() 480 .println("Error: package not found: " + packageName); 481 return false; 482 } 483 } 484 if (!domains.isEmpty()) { 485 mCallback.printOwnersForDomains(writer, domains, userId); 486 } 487 writer.decreaseIndent(); 488 return true; 489 } 490 } 491 492 @NonNull getRemainingArgs(@onNull BasicShellCommandHandler commandHandler)493 private ArrayList<String> getRemainingArgs(@NonNull BasicShellCommandHandler commandHandler) { 494 ArrayList<String> args = new ArrayList<>(); 495 String arg; 496 while ((arg = commandHandler.getNextArg()) != null) { 497 args.add(arg); 498 } 499 return args; 500 } 501 translateUserId(@serIdInt int userId, @NonNull String logContext)502 private int translateUserId(@UserIdInt int userId, @NonNull String logContext) { 503 return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), 504 userId, true, true, logContext, "pm command"); 505 } 506 507 /** 508 * Manually ensure that "true" and "false" are the only options, to ensure a domain isn't 509 * accidentally parsed as a boolean. 510 */ 511 @NonNull parseEnabled(@onNull String arg)512 private boolean parseEnabled(@NonNull String arg) throws IllegalArgumentException { 513 switch (arg.toLowerCase(Locale.US)) { 514 case "true": 515 return true; 516 case "false": 517 return false; 518 default: 519 throw new IllegalArgumentException(arg + " is not a valid boolean"); 520 } 521 } 522 523 /** 524 * Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are 525 * even more internal, and so that testing is easier. 526 */ 527 public interface Callback { 528 529 /** 530 * Variant for use by PackageManagerShellCommand to allow the system/developer to override 531 * the state for a domain. 532 * 533 * @param packageName the package whose state to change, or all packages if none is 534 * specified 535 * @param state the new state code, valid values are 536 * {@link DomainVerificationState#STATE_NO_RESPONSE}, 537 * {@link DomainVerificationState#STATE_SUCCESS}, {@link 538 * DomainVerificationState#STATE_APPROVED}, and {@link 539 * DomainVerificationState#STATE_DENIED} 540 * @param domains the set of domains to change, or null to change all of them 541 */ setDomainVerificationStatusInternal(@ullable String packageName, int state, @Nullable ArraySet<String> domains)542 void setDomainVerificationStatusInternal(@Nullable String packageName, int state, 543 @Nullable ArraySet<String> domains) throws PackageManager.NameNotFoundException; 544 545 /** 546 * Variant for use by PackageManagerShellCommand to allow the system/developer to override 547 * the state for a domain. 548 * 549 * If an approval fails because of a higher level owner, this method will silently skip the 550 * domain. 551 * 552 * @param packageName the package whose state to change 553 * @param enabled whether the domain is now approved by the user 554 * @param domains the set of domains to change, or null to affect all domains 555 */ setDomainVerificationUserSelectionInternal(@serIdInt int userId, @NonNull String packageName, boolean enabled, @Nullable ArraySet<String> domains)556 void setDomainVerificationUserSelectionInternal(@UserIdInt int userId, 557 @NonNull String packageName, boolean enabled, @Nullable ArraySet<String> domains) 558 throws PackageManager.NameNotFoundException; 559 560 /** 561 * @see DomainVerificationManager#getDomainVerificationUserState(String) 562 */ 563 @Nullable getDomainVerificationUserState( @onNull String packageName, @UserIdInt int userId)564 DomainVerificationUserState getDomainVerificationUserState( 565 @NonNull String packageName, @UserIdInt int userId) 566 throws PackageManager.NameNotFoundException; 567 568 /** 569 * Variant for use by PackageManagerShellCommand to allow the system/developer to override 570 * the setting for a package. 571 * 572 * @param packageName the package whose state to change, or all packages if non is 573 * specified 574 * @param allowed whether the package is allowed to automatically open links through 575 * domain verification 576 */ setDomainVerificationLinkHandlingAllowedInternal(@ullable String packageName, boolean allowed, @UserIdInt int userId)577 void setDomainVerificationLinkHandlingAllowedInternal(@Nullable String packageName, 578 boolean allowed, @UserIdInt int userId) throws NameNotFoundException; 579 580 /** 581 * Reset all the domain verification states for all domains for the given package names, or 582 * all package names if null is provided. 583 */ clearDomainVerificationState(@ullable List<String> packageNames)584 void clearDomainVerificationState(@Nullable List<String> packageNames); 585 586 /** 587 * Reset all the user selections for the given package names, or all package names if null 588 * is provided. 589 */ clearUserStates(@ullable List<String> packageNames, @UserIdInt int userId)590 void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId); 591 592 /** 593 * Broadcast a verification request for the given package names, or all package names if 594 * null is provided. By default only re-broadcasts if a package has not recorded a 595 * response. 596 * 597 * @param reVerify send even if the package has previously recorded a response 598 */ verifyPackages(@ullable List<String> packageNames, boolean reVerify)599 void verifyPackages(@Nullable List<String> packageNames, boolean reVerify); 600 601 /** 602 * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer, 603 * Function) 604 */ printState(@onNull IndentingPrintWriter writer, @Nullable String packageName, @Nullable @UserIdInt Integer userId)605 void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, 606 @Nullable @UserIdInt Integer userId) throws NameNotFoundException; 607 608 /** 609 * Print the owners for all domains in a given package. 610 */ printOwnersForPackage(@onNull IndentingPrintWriter writer, @Nullable String packageName, @Nullable @UserIdInt Integer userId)611 void printOwnersForPackage(@NonNull IndentingPrintWriter writer, 612 @Nullable String packageName, @Nullable @UserIdInt Integer userId) 613 throws NameNotFoundException; 614 615 /** 616 * Print the owners for the given domains. 617 */ printOwnersForDomains(@onNull IndentingPrintWriter writer, @NonNull List<String> domains, @Nullable @UserIdInt Integer userId)618 void printOwnersForDomains(@NonNull IndentingPrintWriter writer, 619 @NonNull List<String> domains, @Nullable @UserIdInt Integer userId); 620 } 621 } 622