content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.print;
  
  import static android.content.pm.PackageManager.GET_SERVICES;
  import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
  import static android.content.pm.PackageManager.MATCH_INSTANT;
  import static android.os.Process.ROOT_UID;
  import static android.os.Process.SHELL_UID;
  
  import android.annotation.NonNull;
  import android.annotation.UserIdInt;
  import android.app.ActivityManager;
  import android.app.admin.DevicePolicyManagerInternal;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.PackageManager;
  import android.content.pm.ResolveInfo;
  import android.content.pm.UserInfo;
  import android.database.ContentObserver;
  import android.graphics.drawable.Icon;
  import android.net.Uri;
  import android.os.Binder;
  import android.os.Bundle;
  import android.os.Looper;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.ResultReceiver;
  import android.os.ShellCallback;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.print.IPrintDocumentAdapter;
  import android.print.IPrintJobStateChangeListener;
  import android.print.IPrintManager;
  import android.print.IPrintServicesChangeListener;
  import android.print.IPrinterDiscoveryObserver;
  import android.print.PrintAttributes;
  import android.print.PrintJobId;
  import android.print.PrintJobInfo;
  import android.print.PrintManager;
  import android.print.PrinterId;
  import android.printservice.PrintServiceInfo;
  import android.printservice.recommendation.IRecommendationsChangeListener;
  import android.printservice.recommendation.RecommendationInfo;
  import android.provider.Settings;
  import android.service.print.PrintServiceDumpProto;
  import android.util.Log;
  import android.util.SparseArray;
  import android.util.proto.ProtoOutputStream;
  import android.widget.Toast;
  
  import com.android.internal.content.PackageMonitor;
  import com.android.internal.os.BackgroundThread;
  import com.android.internal.util.DumpUtils;
  import com.android.internal.util.IndentingPrintWriter;
  import com.android.internal.util.Preconditions;
  import com.android.internal.util.dump.DualDumpOutputStream;
  import com.android.server.LocalServices;
  import com.android.server.SystemService;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  
  /**
   * SystemService wrapper for the PrintManager implementation. Publishes
   * Context.PRINT_SERVICE.
   * PrintManager implementation is contained within.
   */
  public final class PrintManagerService extends SystemService {
      private static final String LOG_TAG = "PrintManagerService";
  
      private final PrintManagerImpl mPrintManagerImpl;
  
      public PrintManagerService(Context context) {
          super(context);
          mPrintManagerImpl = new PrintManagerImpl(context);
      }
  
      @Override
      public void onStart() {
          publishBinderService(Context.PRINT_SERVICE, mPrintManagerImpl);
      }
  
      @Override
      public void onUnlockUser(int userHandle) {
          mPrintManagerImpl.handleUserUnlocked(userHandle);
      }
  
      @Override
      public void onStopUser(int userHandle) {
          mPrintManagerImpl.handleUserStopped(userHandle);
      }
  
      class PrintManagerImpl extends IPrintManager.Stub {
          private static final int BACKGROUND_USER_ID = -10;
  
          private final Object mLock = new Object();
  
          private final Context mContext;
  
          private final UserManager mUserManager;
  
          private final SparseArray<UserState> mUserStates = new SparseArray<>();
  
          PrintManagerImpl(Context context) {
              mContext = context;
              mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
              registerContentObservers();
              registerBroadcastReceivers();
          }
  
          @Override
          public void onShellCommand(FileDescriptor in, FileDescriptor out,
                  FileDescriptor err, String[] args, ShellCallback callback,
                  ResultReceiver resultReceiver) {
              new PrintShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
          }
  
          @Override
          public Bundle print(String printJobName, IPrintDocumentAdapter adapter,
                  PrintAttributes attributes, String packageName, int appId, int userId) {
              adapter = Preconditions.checkNotNull(adapter);
              if (!isPrintingEnabled()) {
                  CharSequence disabledMessage = null;
                  DevicePolicyManagerInternal dpmi =
                          LocalServices.getService(DevicePolicyManagerInternal.class);
                  final int callingUserId = UserHandle.getCallingUserId();
                  final long identity = Binder.clearCallingIdentity();
                  try {
                      disabledMessage = dpmi.getPrintingDisabledReasonForUser(callingUserId);
  
                      if (disabledMessage != null) {
                          Toast.makeText(mContext, Looper.getMainLooper(), disabledMessage,
                                  Toast.LENGTH_LONG).show();
                      }
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
                  try {
                      adapter.start();
                  } catch (RemoteException re) {
                      Log.e(LOG_TAG, "Error calling IPrintDocumentAdapter.start()");
                  }
                  try {
                      adapter.finish();
                  } catch (RemoteException re) {
                      Log.e(LOG_TAG, "Error calling IPrintDocumentAdapter.finish()");
                  }
                  return null;
              }
              printJobName = Preconditions.checkStringNotEmpty(printJobName);
              packageName = Preconditions.checkStringNotEmpty(packageName);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final int resolvedAppId;
              final UserState userState;
              final String resolvedPackageName;
              synchronized (mLock) {
                  // Only the current group members can start new print jobs.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return null;
                  }
                  resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
                  resolvedPackageName = resolveCallingPackageNameEnforcingSecurity(packageName);
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  return userState.print(printJobName, adapter, attributes,
                          resolvedPackageName, resolvedAppId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public List<PrintJobInfo> getPrintJobInfos(int appId, int userId) {
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final int resolvedAppId;
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can query for state of print jobs.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return null;
                  }
                  resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  return userState.getPrintJobInfos(resolvedAppId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId, int userId) {
              if (printJobId == null) {
                  return null;
              }
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final int resolvedAppId;
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can query for state of a print job.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return null;
                  }
                  resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  return userState.getPrintJobInfo(printJobId, resolvedAppId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public Icon getCustomPrinterIcon(PrinterId printerId, int userId) {
              printerId = Preconditions.checkNotNull(printerId);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can get the printer icons.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return null;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  return userState.getCustomPrinterIcon(printerId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) {
              if (printJobId == null) {
                  return;
              }
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final int resolvedAppId;
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can cancel a print job.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.cancelPrintJob(printJobId, resolvedAppId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void restartPrintJob(PrintJobId printJobId, int appId, int userId) {
              if (printJobId == null || !isPrintingEnabled()) {
                  // if printing is disabled the state just remains "failed".
                  return;
              }
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final int resolvedAppId;
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can restart a print job.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.restartPrintJob(printJobId, resolvedAppId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public List<PrintServiceInfo> getPrintServices(int selectionFlags, int userId) {
              Preconditions.checkFlagsArgument(selectionFlags,
                      PrintManager.DISABLED_SERVICES | PrintManager.ENABLED_SERVICES);
  
              mContext.enforceCallingOrSelfPermission(
                      android.Manifest.permission.READ_PRINT_SERVICES, null);
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can get print services.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return null;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  return userState.getPrintServices(selectionFlags);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void setPrintServiceEnabled(ComponentName service, boolean isEnabled, int userId) {
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final int appId = UserHandle.getAppId(Binder.getCallingUid());
  
              try {
                  if (appId != Process.SYSTEM_UID && appId != UserHandle.getAppId(
                          mContext.getPackageManager().getPackageUidAsUser(
                                  PrintManager.PRINT_SPOOLER_PACKAGE_NAME, resolvedUserId))) {
                      throw new SecurityException("Only system and print spooler can call this");
                  }
              } catch (PackageManager.NameNotFoundException e) {
                  Log.e(LOG_TAG, "Could not verify caller", e);
                  return;
              }
  
              service = Preconditions.checkNotNull(service);
  
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can enable / disable services.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.setPrintServiceEnabled(service, isEnabled);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public List<RecommendationInfo> getPrintServiceRecommendations(int userId) {
              mContext.enforceCallingOrSelfPermission(
                      android.Manifest.permission.READ_PRINT_SERVICE_RECOMMENDATIONS, null);
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can get print service recommendations.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return null;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  return userState.getPrintServiceRecommendations();
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer,
                  int userId) {
              observer = Preconditions.checkNotNull(observer);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can create a discovery session.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.createPrinterDiscoverySession(observer);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void destroyPrinterDiscoverySession(IPrinterDiscoveryObserver observer,
                  int userId) {
              observer = Preconditions.checkNotNull(observer);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can destroy a discovery session.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.destroyPrinterDiscoverySession(observer);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void startPrinterDiscovery(IPrinterDiscoveryObserver observer,
                  List<PrinterId> priorityList, int userId) {
              observer = Preconditions.checkNotNull(observer);
              if (priorityList != null) {
                  priorityList = Preconditions.checkCollectionElementsNotNull(priorityList,
                          "PrinterId");
              }
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can start discovery.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.startPrinterDiscovery(observer, priorityList);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void stopPrinterDiscovery(IPrinterDiscoveryObserver observer, int userId) {
              observer = Preconditions.checkNotNull(observer);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can stop discovery.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.stopPrinterDiscovery(observer);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void validatePrinters(List<PrinterId> printerIds, int userId) {
              printerIds = Preconditions.checkCollectionElementsNotNull(printerIds, "PrinterId");
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can validate printers.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.validatePrinters(printerIds);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void startPrinterStateTracking(PrinterId printerId, int userId) {
              printerId = Preconditions.checkNotNull(printerId);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can start printer tracking.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.startPrinterStateTracking(printerId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void stopPrinterStateTracking(PrinterId printerId, int userId) {
              printerId = Preconditions.checkNotNull(printerId);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can stop printer tracking.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.stopPrinterStateTracking(printerId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void addPrintJobStateChangeListener(IPrintJobStateChangeListener listener,
                  int appId, int userId) throws RemoteException {
              listener = Preconditions.checkNotNull(listener);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final int resolvedAppId;
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can add a print job listener.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.addPrintJobStateChangeListener(listener, resolvedAppId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void removePrintJobStateChangeListener(IPrintJobStateChangeListener listener,
                  int userId) {
              listener = Preconditions.checkNotNull(listener);
  
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can remove a print job listener.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.removePrintJobStateChangeListener(listener);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void addPrintServicesChangeListener(IPrintServicesChangeListener listener,
                  int userId) throws RemoteException {
              listener = Preconditions.checkNotNull(listener);
  
              mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PRINT_SERVICES,
                      null);
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can add a print services listener.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.addPrintServicesChangeListener(listener);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void removePrintServicesChangeListener(IPrintServicesChangeListener listener,
                  int userId) {
              listener = Preconditions.checkNotNull(listener);
  
              mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PRINT_SERVICES,
                      null);
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can remove a print services change listener.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.removePrintServicesChangeListener(listener);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void addPrintServiceRecommendationsChangeListener(
                  IRecommendationsChangeListener listener, int userId)
                  throws RemoteException {
              listener = Preconditions.checkNotNull(listener);
  
              mContext.enforceCallingOrSelfPermission(
                      android.Manifest.permission.READ_PRINT_SERVICE_RECOMMENDATIONS, null);
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can add a print service recommendations listener.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.addPrintServiceRecommendationsChangeListener(listener);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void removePrintServiceRecommendationsChangeListener(
                  IRecommendationsChangeListener listener, int userId) {
              listener = Preconditions.checkNotNull(listener);
  
              mContext.enforceCallingOrSelfPermission(
                      android.Manifest.permission.READ_PRINT_SERVICE_RECOMMENDATIONS, null);
              final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
              final UserState userState;
              synchronized (mLock) {
                  // Only the current group members can remove a print service recommendations
                  // listener.
                  if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
                      return;
                  }
                  userState = getOrCreateUserStateLocked(resolvedUserId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.removePrintServiceRecommendationsChangeListener(listener);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
              fd = Preconditions.checkNotNull(fd);
  
              if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
  
              int opti = 0;
              boolean dumpAsProto = false;
              while (opti < args.length) {
                  String opt = args[opti];
                  if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
                      break;
                  }
                  opti++;
                  if ("--proto".equals(opt)) {
                      dumpAsProto = true;
                  } else {
                      pw.println("Unknown argument: " + opt + "; use -h for help");
                  }
              }
  
              ArrayList<UserState> userStatesToDump = new ArrayList<>();
              synchronized (mLock) {
                  int numUserStates = mUserStates.size();
                  for (int i = 0; i < numUserStates; i++) {
                      userStatesToDump.add(mUserStates.valueAt(i));
                  }
              }
  
              final long identity = Binder.clearCallingIdentity();
              try {
                  if (dumpAsProto) {
                      dump(new DualDumpOutputStream(new ProtoOutputStream(fd)),
                              userStatesToDump);
                  } else {
                      pw.println("PRINT MANAGER STATE (dumpsys print)");
  
                      dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
                              userStatesToDump);
                  }
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public boolean getBindInstantServiceAllowed(@UserIdInt int userId) {
              int callingUid = Binder.getCallingUid();
              if (callingUid != SHELL_UID && callingUid != ROOT_UID) {
                  throw new SecurityException("Can only be called by uid " + SHELL_UID
                          + " or " + ROOT_UID);
              }
  
              final UserState userState;
              synchronized (mLock) {
                  userState = getOrCreateUserStateLocked(userId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  return userState.getBindInstantServiceAllowed();
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          @Override
          public void setBindInstantServiceAllowed(@UserIdInt int userId, boolean allowed) {
              int callingUid = Binder.getCallingUid();
              if (callingUid != SHELL_UID && callingUid != ROOT_UID) {
                  throw new SecurityException("Can only be called by uid " + SHELL_UID
                          + " or " + ROOT_UID);
              }
  
              final UserState userState;
              synchronized (mLock) {
                  userState = getOrCreateUserStateLocked(userId, false);
              }
              final long identity = Binder.clearCallingIdentity();
              try {
                  userState.setBindInstantServiceAllowed(allowed);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          private boolean isPrintingEnabled() {
              return !mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING,
                      Binder.getCallingUserHandle());
          }
  
          private void dump(@NonNull DualDumpOutputStream dumpStream,
                  @NonNull ArrayList<UserState> userStatesToDump) {
              final int userStateCount = userStatesToDump.size();
              for (int i = 0; i < userStateCount; i++) {
                  long token = dumpStream.start("user_states", PrintServiceDumpProto.USER_STATES);
                  userStatesToDump.get(i).dump(dumpStream);
                  dumpStream.end(token);
              }
  
              dumpStream.flush();
          }
  
          private void registerContentObservers() {
              final Uri enabledPrintServicesUri = Settings.Secure.getUriFor(
                      Settings.Secure.DISABLED_PRINT_SERVICES);
              ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) {
                  @Override
                  public void onChange(boolean selfChange, Uri uri, int userId) {
                      if (enabledPrintServicesUri.equals(uri)) {
                          synchronized (mLock) {
                              final int userCount = mUserStates.size();
                              for (int i = 0; i < userCount; i++) {
                                  if (userId == UserHandle.USER_ALL
                                          || userId == mUserStates.keyAt(i)) {
                                      mUserStates.valueAt(i).updateIfNeededLocked();
                                  }
                              }
                          }
                      }
                  }
              };
  
              mContext.getContentResolver().registerContentObserver(enabledPrintServicesUri,
                      false, observer, UserHandle.USER_ALL);
          }
  
          private void registerBroadcastReceivers() {
              PackageMonitor monitor = new PackageMonitor() {
                  /**
                   * Checks if the package contains a print service.
                   *
                   * @param packageName The name of the package
                   *
                   * @return true iff the package contains a print service
                   */
                  private boolean hasPrintService(String packageName) {
                      Intent intent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
                      intent.setPackage(packageName);
  
                      List<ResolveInfo> installedServices = mContext.getPackageManager()
                              .queryIntentServicesAsUser(intent,
                                      GET_SERVICES | MATCH_DEBUG_TRIAGED_MISSING | MATCH_INSTANT,
                                      getChangingUserId());
  
                      return installedServices != null && !installedServices.isEmpty();
                  }
  
                  /**
                   * Checks if there is a print service currently registered for this package.
                   *
                   * @param userState The userstate for the current user
                   * @param packageName The name of the package
                   *
                   * @return true iff the package contained (and might still contain) a print service
                   */
                  private boolean hadPrintService(@NonNull UserState userState, String packageName) {
                      List<PrintServiceInfo> installedServices = userState
                              .getPrintServices(PrintManager.ALL_SERVICES);
  
                      if (installedServices == null) {
                          return false;
                      }
  
                      final int numInstalledServices = installedServices.size();
                      for (int i = 0; i < numInstalledServices; i++) {
                          if (installedServices.get(i).getResolveInfo().serviceInfo.packageName
                                  .equals(packageName)) {
                              return true;
                          }
                      }
  
                      return false;
                  }
  
                  @Override
                  public void onPackageModified(String packageName) {
                      if (!mUserManager.isUserUnlockingOrUnlocked(getChangingUserId())) return;
                      UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false,
                              false /* enforceUserUnlockingOrUnlocked */);
  
                      boolean prunePrintServices = false;
                      synchronized (mLock) {
                          if (hadPrintService(userState, packageName)
                                  || hasPrintService(packageName)) {
                              userState.updateIfNeededLocked();
                              prunePrintServices = true;
                          }
                      }
  
                      if (prunePrintServices) {
                          userState.prunePrintServices();
                      }
                  }
  
                  @Override
                  public void onPackageRemoved(String packageName, int uid) {
                      if (!mUserManager.isUserUnlockingOrUnlocked(getChangingUserId())) return;
                      UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false,
                              false /* enforceUserUnlockingOrUnlocked */);
  
                      boolean prunePrintServices = false;
                      synchronized (mLock) {
                          if (hadPrintService(userState, packageName)) {
                              userState.updateIfNeededLocked();
                              prunePrintServices = true;
                          }
                      }
  
                      if (prunePrintServices) {
                          userState.prunePrintServices();
                      }
                  }
  
                  @Override
                  public boolean onHandleForceStop(Intent intent, String[] stoppedPackages,
                          int uid, boolean doit) {
                      if (!mUserManager.isUserUnlockingOrUnlocked(getChangingUserId())) return false;
                      synchronized (mLock) {
                          // A background user/profile's print jobs are running but there is
                          // no UI shown. Hence, if the packages of such a user change we need
                          // to handle it as the change may affect ongoing print jobs.
                          UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false,
                                  false /* enforceUserUnlockingOrUnlocked */);
                          boolean stoppedSomePackages = false;
  
                          List<PrintServiceInfo> enabledServices = userState
                                  .getPrintServices(PrintManager.ENABLED_SERVICES);
                          if (enabledServices == null) {
                              return false;
                          }
  
                          Iterator<PrintServiceInfo> iterator = enabledServices.iterator();
                          while (iterator.hasNext()) {
                              ComponentName componentName = iterator.next().getComponentName();
                              String componentPackage = componentName.getPackageName();
                              for (String stoppedPackage : stoppedPackages) {
                                  if (componentPackage.equals(stoppedPackage)) {
                                      if (!doit) {
                                          return true;
                                      }
                                      stoppedSomePackages = true;
                                      break;
                                  }
                              }
                          }
                          if (stoppedSomePackages) {
                              userState.updateIfNeededLocked();
                          }
                          return false;
                      }
                  }
  
                  @Override
                  public void onPackageAdded(String packageName, int uid) {
                      if (!mUserManager.isUserUnlockingOrUnlocked(getChangingUserId())) return;
                      synchronized (mLock) {
                          if (hasPrintService(packageName)) {
                              UserState userState = getOrCreateUserStateLocked(getChangingUserId(),
                                      false, false /* enforceUserUnlockingOrUnlocked */);
                              userState.updateIfNeededLocked();
                          }
                      }
                  }
              };
  
              // package changes
              monitor.register(mContext, BackgroundThread.getHandler().getLooper(),
                      UserHandle.ALL, true);
          }
          private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority) {
              return getOrCreateUserStateLocked(userId, lowPriority,
                      true /* enforceUserUnlockingOrUnlocked */);
          }
  
          private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority,
                  boolean enforceUserUnlockingOrUnlocked) {
              if (enforceUserUnlockingOrUnlocked && !mUserManager.isUserUnlockingOrUnlocked(userId)) {
                  throw new IllegalStateException(
                          "User " + userId + " must be unlocked for printing to be available");
              }
  
              UserState userState = mUserStates.get(userId);
              if (userState == null) {
                  userState = new UserState(mContext, userId, mLock, lowPriority);
                  mUserStates.put(userId, userState);
              }
  
              if (!lowPriority) {
                  userState.increasePriority();
              }
  
              return userState;
          }
  
          private void handleUserUnlocked(final int userId) {
              // This code will touch the remote print spooler which
              // must be called off the main thread, so post the work.
              BackgroundThread.getHandler().post(new Runnable() {
                  @Override
                  public void run() {
                      if (!mUserManager.isUserUnlockingOrUnlocked(userId)) return;
  
                      UserState userState;
                      synchronized (mLock) {
                          userState = getOrCreateUserStateLocked(userId, true,
                                  false /*enforceUserUnlockingOrUnlocked */);
                          userState.updateIfNeededLocked();
                      }
                      // This is the first time we switch to this user after boot, so
                      // now is the time to remove obsolete print jobs since they
                      // are from the last boot and no application would query them.
                      userState.removeObsoletePrintJobs();
                  }
              });
          }
  
          private void handleUserStopped(final int userId) {
              // This code will touch the remote print spooler which
              // must be called off the main thread, so post the work.
              BackgroundThread.getHandler().post(new Runnable() {
                  @Override
                  public void run() {
                      synchronized (mLock) {
                          UserState userState = mUserStates.get(userId);
                          if (userState != null) {
                              userState.destroyLocked();
                              mUserStates.remove(userId);
                          }
                      }
                  }
              });
          }
  
          private int resolveCallingProfileParentLocked(int userId) {
              if (userId != getCurrentUserId()) {
                  final long identity = Binder.clearCallingIdentity();
                  try {
                      UserInfo parent = mUserManager.getProfileParent(userId);
                      if (parent != null) {
                          return parent.getUserHandle().getIdentifier();
                      } else {
                          return BACKGROUND_USER_ID;
                      }
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
              return userId;
          }
  
          private int resolveCallingAppEnforcingPermissions(int appId) {
              final int callingUid = Binder.getCallingUid();
              if (callingUid == 0) {
                  return appId;
              }
              final int callingAppId = UserHandle.getAppId(callingUid);
              if (appId == callingAppId || callingAppId == SHELL_UID
                      || callingAppId == Process.SYSTEM_UID) {
                  return appId;
              }
              if (mContext.checkCallingPermission(
                      "com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS")
                      != PackageManager.PERMISSION_GRANTED) {
                  throw new SecurityException("Call from app " + callingAppId + " as app "
                          + appId + " without com.android.printspooler.permission"
                          + ".ACCESS_ALL_PRINT_JOBS");
              }
              return appId;
          }
  
          private int resolveCallingUserEnforcingPermissions(int userId) {
              try {
                  return ActivityManager.getService().handleIncomingUser(Binder.getCallingPid(),
                          Binder.getCallingUid(), userId, true, true, "", null);
              } catch (RemoteException re) {
                  // Shouldn't happen, local.
              }
              return userId;
          }
  
          private @NonNull String resolveCallingPackageNameEnforcingSecurity(
                  @NonNull String packageName) {
              String[] packages = mContext.getPackageManager().getPackagesForUid(
                      Binder.getCallingUid());
              final int packageCount = packages.length;
              for (int i = 0; i < packageCount; i++) {
                  if (packageName.equals(packages[i])) {
                      return packageName;
                  }
              }
              throw new IllegalArgumentException("packageName has to belong to the caller");
          }
  
          private int getCurrentUserId () {
              final long identity = Binder.clearCallingIdentity();
              try {
                  return ActivityManager.getCurrentUser();
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  }
  