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