Merge "Fix flicker when releasing drag of divider"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index eb7c712..031bdbd 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -28,13 +28,24 @@
 import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.content.pm.VerificationParams;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -45,13 +56,26 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 
+import libcore.io.IoUtils;
+
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.SizedInputStream;
 
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
 
 public final class Pm {
     private static final String TAG = "Pm";
@@ -82,7 +106,7 @@
         System.exit(exitCode);
     }
 
-    public int run(String[] args) throws RemoteException {
+    public int run(String[] args) throws IOException, RemoteException {
         boolean validCommand = false;
         if (args.length < 1) {
             return showUsage();
@@ -118,19 +142,19 @@
         }
 
         if ("install-create".equals(op)) {
-            return runInstall();
+            return runInstallCreate();
         }
 
         if ("install-write".equals(op)) {
-            return runInstall();
+            return runInstallWrite();
         }
 
         if ("install-commit".equals(op)) {
-            return runInstall();
+            return runInstallCommit();
         }
 
         if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
-            return runInstall();
+            return runInstallAbandon();
         }
 
         if ("set-installer".equals(op)) {
@@ -275,10 +299,6 @@
         return -1;
     }
 
-    private int runInstall() {
-        return runShellCommand("package", mArgs);
-    }
-
     /**
      * Execute the list sub-command.
      *
@@ -297,10 +317,6 @@
         return runShellCommand("package", mArgs);
     }
 
-    private int runUninstall() {
-        return runShellCommand("package", mArgs);
-    }
-
     private int runPath() {
         int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
@@ -354,6 +370,49 @@
         }
     }
 
+    /**
+     * Converts a failure code into a string by using reflection to find a matching constant
+     * in PackageManager.
+     */
+    private String installFailureToString(LocalPackageInstallObserver obs) {
+        final int result = obs.result;
+        Field[] fields = PackageManager.class.getFields();
+        for (Field f: fields) {
+            if (f.getType() == int.class) {
+                int modifiers = f.getModifiers();
+                // only look at public final static fields.
+                if (((modifiers & Modifier.FINAL) != 0) &&
+                        ((modifiers & Modifier.PUBLIC) != 0) &&
+                        ((modifiers & Modifier.STATIC) != 0)) {
+                    String fieldName = f.getName();
+                    if (fieldName.startsWith("INSTALL_FAILED_") ||
+                            fieldName.startsWith("INSTALL_PARSE_FAILED_")) {
+                        // get the int value and compare it to result.
+                        try {
+                            if (result == f.getInt(null)) {
+                                StringBuilder sb = new StringBuilder(64);
+                                sb.append(fieldName);
+                                if (obs.extraPermission != null) {
+                                    sb.append(" perm=");
+                                    sb.append(obs.extraPermission);
+                                }
+                                if (obs.extraPackage != null) {
+                                    sb.append(" pkg=" + obs.extraPackage);
+                                }
+                                return sb.toString();
+                            }
+                        } catch (IllegalAccessException e) {
+                            // this shouldn't happen since we only look for public static fields.
+                        }
+                    }
+                }
+            }
+        }
+
+        // couldn't find a matching constant? return the value
+        return Integer.toString(result);
+    }
+
     // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
     private int runSetAppLink() {
         int userId = UserHandle.USER_SYSTEM;
@@ -543,6 +602,316 @@
         }
     }
 
+    private int runInstall() {
+        int installFlags = 0;
+        int userId = UserHandle.USER_ALL;
+        String installerPackageName = null;
+
+        String opt;
+
+        String originatingUriString = null;
+        String referrer = null;
+        String abi = null;
+
+        while ((opt=nextOption()) != null) {
+            if (opt.equals("-l")) {
+                installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
+            } else if (opt.equals("-r")) {
+                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+            } else if (opt.equals("-i")) {
+                installerPackageName = nextOptionData();
+                if (installerPackageName == null) {
+                    System.err.println("Error: no value specified for -i");
+                    return 1;
+                }
+            } else if (opt.equals("-t")) {
+                installFlags |= PackageManager.INSTALL_ALLOW_TEST;
+            } else if (opt.equals("-s")) {
+                // Override if -s option is specified.
+                installFlags |= PackageManager.INSTALL_EXTERNAL;
+            } else if (opt.equals("-f")) {
+                // Override if -s option is specified.
+                installFlags |= PackageManager.INSTALL_INTERNAL;
+            } else if (opt.equals("-d")) {
+                installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
+            } else if (opt.equals("-g")) {
+                installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
+            } else if (opt.equals("--originating-uri")) {
+                originatingUriString = nextOptionData();
+                if (originatingUriString == null) {
+                    System.err.println("Error: must supply argument for --originating-uri");
+                    return 1;
+                }
+            } else if (opt.equals("--referrer")) {
+                referrer = nextOptionData();
+                if (referrer == null) {
+                    System.err.println("Error: must supply argument for --referrer");
+                    return 1;
+                }
+            } else if (opt.equals("--abi")) {
+                abi = checkAbiArgument(nextOptionData());
+            } else if (opt.equals("--user")) {
+                userId = Integer.parseInt(nextOptionData());
+            } else {
+                System.err.println("Error: Unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        userId = translateUserId(userId, "runInstall");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            installFlags |= PackageManager.INSTALL_ALL_USERS;
+        }
+
+        final Uri verificationURI;
+        final Uri originatingURI;
+        final Uri referrerURI;
+
+        if (originatingUriString != null) {
+            originatingURI = Uri.parse(originatingUriString);
+        } else {
+            originatingURI = null;
+        }
+
+        if (referrer != null) {
+            referrerURI = Uri.parse(referrer);
+        } else {
+            referrerURI = null;
+        }
+
+        // Populate apkURI, must be present
+        final String apkFilePath = nextArg();
+        System.err.println("\tpkg: " + apkFilePath);
+        if (apkFilePath == null) {
+            System.err.println("Error: no package specified");
+            return 1;
+        }
+
+        // Populate verificationURI, optionally present
+        final String verificationFilePath = nextArg();
+        if (verificationFilePath != null) {
+            System.err.println("\tver: " + verificationFilePath);
+            verificationURI = Uri.fromFile(new File(verificationFilePath));
+        } else {
+            verificationURI = null;
+        }
+
+        LocalPackageInstallObserver obs = new LocalPackageInstallObserver();
+        try {
+            VerificationParams verificationParams = new VerificationParams(verificationURI,
+                    originatingURI, referrerURI, VerificationParams.NO_UID, null);
+
+            mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
+                    installerPackageName, verificationParams, abi, userId);
+
+            synchronized (obs) {
+                while (!obs.finished) {
+                    try {
+                        obs.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+                if (obs.result == PackageManager.INSTALL_SUCCEEDED) {
+                    System.out.println("Success");
+                    return 0;
+                } else {
+                    System.err.println("Failure ["
+                            + installFailureToString(obs)
+                            + "]");
+                    return 1;
+                }
+            }
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(PM_NOT_RUNNING_ERR);
+            return 1;
+        }
+    }
+
+    /**
+     * @param userId The user id to be translated.
+     * @param logContext Optional human readable text to provide some context in error log.
+     * @return Translated concrete user id.  This will include USER_ALL.
+     */
+    private int translateUserId(int userId, String logContext) {
+        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, logContext, "pm command");
+    }
+
+    private int runInstallCreate() throws RemoteException {
+        int userId = UserHandle.USER_ALL;
+        String installerPackageName = null;
+
+        final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+        String opt;
+        while ((opt = nextOption()) != null) {
+            if (opt.equals("-l")) {
+                params.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
+            } else if (opt.equals("-r")) {
+                params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+            } else if (opt.equals("-i")) {
+                installerPackageName = nextArg();
+                if (installerPackageName == null) {
+                    throw new IllegalArgumentException("Missing installer package");
+                }
+            } else if (opt.equals("-t")) {
+                params.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
+            } else if (opt.equals("-s")) {
+                params.installFlags |= PackageManager.INSTALL_EXTERNAL;
+            } else if (opt.equals("-f")) {
+                params.installFlags |= PackageManager.INSTALL_INTERNAL;
+            } else if (opt.equals("-d")) {
+                params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
+            } else if (opt.equals("-g")) {
+                params.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
+            } else if (opt.equals("--originating-uri")) {
+                params.originatingUri = Uri.parse(nextOptionData());
+            } else if (opt.equals("--referrer")) {
+                params.referrerUri = Uri.parse(nextOptionData());
+            } else if (opt.equals("-p")) {
+                params.mode = SessionParams.MODE_INHERIT_EXISTING;
+                params.appPackageName = nextOptionData();
+                if (params.appPackageName == null) {
+                    throw new IllegalArgumentException("Missing inherit package name");
+                }
+            } else if (opt.equals("-S")) {
+                params.setSize(Long.parseLong(nextOptionData()));
+            } else if (opt.equals("--abi")) {
+                params.abiOverride = checkAbiArgument(nextOptionData());
+            } else if (opt.equals("--user")) {
+                userId = Integer.parseInt(nextOptionData());
+            } else if (opt.equals("--install-location")) {
+                params.installLocation = Integer.parseInt(nextOptionData());
+            } else if (opt.equals("--force-uuid")) {
+                params.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
+                params.volumeUuid = nextOptionData();
+                if ("internal".equals(params.volumeUuid)) {
+                    params.volumeUuid = null;
+                }
+            } else {
+                throw new IllegalArgumentException("Unknown option " + opt);
+            }
+        }
+
+        userId = translateUserId(userId, "runInstallCreate");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
+        }
+
+        final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
+
+        // NOTE: adb depends on parsing this string
+        System.out.println("Success: created install session [" + sessionId + "]");
+        return 0;
+    }
+
+    private int runInstallWrite() throws IOException, RemoteException {
+        long sizeBytes = -1;
+
+        String opt;
+        while ((opt = nextOption()) != null) {
+            if (opt.equals("-S")) {
+                sizeBytes = Long.parseLong(nextOptionData());
+            } else {
+                throw new IllegalArgumentException("Unknown option: " + opt);
+            }
+        }
+
+        final int sessionId = Integer.parseInt(nextArg());
+        final String splitName = nextArg();
+
+        String path = nextArg();
+        if ("-".equals(path)) {
+            path = null;
+        } else if (path != null) {
+            final File file = new File(path);
+            if (file.isFile()) {
+                sizeBytes = file.length();
+            }
+        }
+
+        final SessionInfo info = mInstaller.getSessionInfo(sessionId);
+
+        PackageInstaller.Session session = null;
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
+
+            if (path != null) {
+                in = new FileInputStream(path);
+            } else {
+                in = new SizedInputStream(System.in, sizeBytes);
+            }
+            out = session.openWrite(splitName, 0, sizeBytes);
+
+            int total = 0;
+            byte[] buffer = new byte[65536];
+            int c;
+            while ((c = in.read(buffer)) != -1) {
+                total += c;
+                out.write(buffer, 0, c);
+
+                if (info.sizeBytes > 0) {
+                    final float fraction = ((float) c / (float) info.sizeBytes);
+                    session.addProgress(fraction);
+                }
+            }
+            session.fsync(out);
+
+            System.out.println("Success: streamed " + total + " bytes");
+            return 0;
+        } finally {
+            IoUtils.closeQuietly(out);
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    private int runInstallCommit() throws RemoteException {
+        final int sessionId = Integer.parseInt(nextArg());
+
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
+
+            final LocalIntentReceiver receiver = new LocalIntentReceiver();
+            session.commit(receiver.getIntentSender());
+
+            final Intent result = receiver.getResult();
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                System.out.println("Success");
+                return 0;
+            } else {
+                Log.e(TAG, "Failure details: " + result.getExtras());
+                System.err.println("Failure ["
+                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+                return 1;
+            }
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    private int runInstallAbandon() throws RemoteException {
+        final int sessionId = Integer.parseInt(nextArg());
+
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
+            session.abandon();
+            System.out.println("Success");
+            return 0;
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
     private int runSetInstaller() throws RemoteException {
         final String targetPackage = nextArg();
         final String installerPackageName = nextArg();
@@ -711,6 +1080,80 @@
         }
     }
 
+    private int runUninstall() throws RemoteException {
+        int flags = 0;
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt=nextOption()) != null) {
+            if (opt.equals("-k")) {
+                flags |= PackageManager.DELETE_KEEP_DATA;
+            } else if (opt.equals("--user")) {
+                String param = nextArg();
+                if (isNumber(param)) {
+                    userId = Integer.parseInt(param);
+                } else {
+                    showUsage();
+                    System.err.println("Error: Invalid user: " + param);
+                    return 1;
+                }
+            } else {
+                System.err.println("Error: Unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        String pkg = nextArg();
+        if (pkg == null) {
+            System.err.println("Error: no package specified");
+            return showUsage();
+        }
+
+        userId = translateUserId(userId, "runUninstall");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            flags |= PackageManager.DELETE_ALL_USERS;
+        } else {
+            PackageInfo info;
+            try {
+                info = mPm.getPackageInfo(pkg, 0, userId);
+            } catch (RemoteException e) {
+                System.err.println(e.toString());
+                System.err.println(PM_NOT_RUNNING_ERR);
+                return 1;
+            }
+            if (info == null) {
+                System.err.println("Failure - not installed for " + userId);
+                return 1;
+            }
+            final boolean isSystem =
+                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            // If we are being asked to delete a system app for just one
+            // user set flag so it disables rather than reverting to system
+            // version of the app.
+            if (isSystem) {
+                flags |= PackageManager.DELETE_SYSTEM_APP;
+            }
+        }
+
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
+        mInstaller.uninstall(pkg, null /* callerPackageName */, flags,
+                receiver.getIntentSender(), userId);
+
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            System.out.println("Success");
+            return 0;
+        } else {
+            Log.e(TAG, "Failure details: " + result.getExtras());
+            System.err.println("Failure ["
+                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+            return 1;
+        }
+    }
+
     static class ClearDataObserver extends IPackageDataObserver.Stub {
         boolean finished;
         boolean result;
@@ -1056,6 +1499,54 @@
         return 1;
     }
 
+    private static String checkAbiArgument(String abi) {
+        if (TextUtils.isEmpty(abi)) {
+            throw new IllegalArgumentException("Missing ABI argument");
+        }
+
+        if ("-".equals(abi)) {
+            return abi;
+        }
+
+        final String[] supportedAbis = Build.SUPPORTED_ABIS;
+        for (String supportedAbi : supportedAbis) {
+            if (supportedAbi.equals(abi)) {
+                return abi;
+            }
+        }
+
+        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
+    }
+
+    private static class LocalIntentReceiver {
+        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public int send(int code, Intent intent, String resolvedType,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                return 0;
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
     private String nextOption() {
         if (mNextArg >= mArgs.length) {
             return null;
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index cad482b..73c2c80 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -19,11 +19,8 @@
 import android.util.Slog;
 import com.android.internal.util.FastPrintWriter;
 
-import java.io.BufferedInputStream;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.InputStream;
 import java.io.PrintWriter;
 
 /**
@@ -46,7 +43,6 @@
 
     private FastPrintWriter mOutPrintWriter;
     private FastPrintWriter mErrPrintWriter;
-    private InputStream mInputStream;
 
     public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ResultReceiver resultReceiver) {
@@ -115,13 +111,6 @@
         return mErrPrintWriter;
     }
 
-    public InputStream getInputStream() {
-        if (mInputStream == null) {
-            mInputStream = new BufferedInputStream(new FileInputStream(mIn));
-        }
-        return mInputStream;
-    }
-
     /**
      * Return the next option on the command line -- that is an argument that
      * starts with '-'.  If the next argument is not an option, null is returned.
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 7b2cf25..db2bf1a 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -46,4 +46,5 @@
     void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
     void setClient(IPrintSpoolerClient client);
     void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling);
+    void removeApprovedPrintService(in ComponentName serviceToRemove);
 }
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index e92f74c..50237832 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -187,6 +187,18 @@
     <!-- Label for a printer that is not available. [CHAR LIMIT=25] -->
     <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> &#8211; unavailable</string>
 
+    <!-- Title for a warning message about security implications of using a print service,
+         displayed as a dialog message when the user prints using a print service that has not been
+         used before. [CHAR LIMIT=NONE] -->
+    <string name="print_service_security_warning_title">Use
+         <xliff:g id="service" example="My Print Service">%1$s</xliff:g>?</string>
+
+    <!-- Summary for a warning message about security implications of using a print service,
+         displayed as a dialog message when the user prints using a print service that has not been
+         used before. [CHAR LIMIT=NONE] -->
+    <string name="print_service_security_warning_summary">Your document may pass through one or
+         more servers on its way to the printer.</string>
+
     <!-- Arrays -->
 
     <!-- Color mode labels. -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index bafccae..7adcfec 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -50,6 +50,7 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.printspooler.R;
+import com.android.printspooler.util.ApprovedPrintServices;
 
 import libcore.io.IoUtils;
 
@@ -67,6 +68,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Service for exposing some of the {@link PrintSpooler} functionality to
@@ -136,10 +138,10 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        synchronized (mLock) {
-            String prefix = (args.length > 0) ? args[0] : "";
-            String tab = "  ";
+        String prefix = (args.length > 0) ? args[0] : "";
+        String tab = "  ";
 
+        synchronized (mLock) {
             pw.append(prefix).append("print jobs:").println();
             final int printJobCount = mPrintJobs.size();
             for (int i = 0; i < printJobCount; i++) {
@@ -160,6 +162,14 @@
                 }
             }
         }
+
+        pw.append(prefix).append("approved print services:").println();
+        Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices();
+        if (approvedPrintServices != null) {
+            for (String approvedService : approvedPrintServices) {
+                pw.append(prefix).append(tab).append(approvedService).println();
+            }
+        }
     }
 
     private void sendOnPrintJobQueued(PrintJobInfo printJob) {
@@ -1307,6 +1317,12 @@
             PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
         }
 
+        @Override
+        public void removeApprovedPrintService(ComponentName serviceToRemove) {
+            (new ApprovedPrintServices(PrintSpoolerService.this))
+                    .removeApprovedService(serviceToRemove);
+        }
+
         public PrintSpoolerService getService() {
             return PrintSpoolerService.this;
         }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index e8a5e43..4f80d91 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -17,14 +17,21 @@
 package com.android.printspooler.ui;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentTransaction;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -81,6 +88,7 @@
 import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo;
 import com.android.printspooler.renderer.IPdfEditor;
 import com.android.printspooler.renderer.PdfManipulationService;
+import com.android.printspooler.util.ApprovedPrintServices;
 import com.android.printspooler.util.MediaSizeUtils;
 import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
 import com.android.printspooler.util.PageRangeUtils;
@@ -88,6 +96,7 @@
 import com.android.printspooler.widget.PrintContentView;
 import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener;
 import com.android.printspooler.widget.PrintContentView.OptionsStateController;
+
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
@@ -1184,12 +1193,125 @@
         mPrintButton.setOnClickListener(clickListener);
     }
 
+    /**
+     * A dialog that asks the user to approve a {@link PrintService}. This dialog is automatically
+     * dismissed if the same {@link PrintService} gets approved by another
+     * {@link PrintServiceApprovalDialog}.
+     */
+    private static final class PrintServiceApprovalDialog extends DialogFragment
+            implements OnSharedPreferenceChangeListener {
+        private static final String PRINTSERVICE_KEY = "PRINTSERVICE";
+        private ApprovedPrintServices mApprovedServices;
+
+        /**
+         * Create a new {@link PrintServiceApprovalDialog} that ask the user to approve a
+         * {@link PrintService}.
+         *
+         * @param printService The {@link ComponentName} of the service to approve
+         * @return A new {@link PrintServiceApprovalDialog} that might approve the service
+         */
+        static PrintServiceApprovalDialog newInstance(ComponentName printService) {
+            PrintServiceApprovalDialog dialog = new PrintServiceApprovalDialog();
+
+            Bundle args = new Bundle();
+            args.putParcelable(PRINTSERVICE_KEY, printService);
+            dialog.setArguments(args);
+
+            return dialog;
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+
+            mApprovedServices.unregisterChangeListener(this);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+
+            ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
+            synchronized (ApprovedPrintServices.sLock) {
+                if (mApprovedServices.isApprovedService(printService)) {
+                    dismiss();
+                } else {
+                    mApprovedServices.registerChangeListenerLocked(this);
+                }
+            }
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            super.onCreateDialog(savedInstanceState);
+
+            mApprovedServices = new ApprovedPrintServices(getActivity());
+
+            PackageManager packageManager = getActivity().getPackageManager();
+            CharSequence serviceLabel;
+            try {
+                ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
+
+                serviceLabel = packageManager.getApplicationInfo(printService.getPackageName(), 0)
+                        .loadLabel(packageManager);
+            } catch (NameNotFoundException e) {
+                serviceLabel = null;
+            }
+
+            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+            builder.setTitle(getString(R.string.print_service_security_warning_title,
+                    serviceLabel))
+                    .setMessage(getString(R.string.print_service_security_warning_summary,
+                            serviceLabel))
+                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int id) {
+                            ComponentName printService =
+                                    getArguments().getParcelable(PRINTSERVICE_KEY);
+                            // Prevent onSharedPreferenceChanged from getting triggered
+                            mApprovedServices
+                                    .unregisterChangeListener(PrintServiceApprovalDialog.this);
+
+                            mApprovedServices.addApprovedService(printService);
+                            ((PrintActivity) getActivity()).confirmPrint();
+                        }
+                    })
+                    .setNegativeButton(android.R.string.cancel, null);
+
+            return builder.create();
+        }
+
+        @Override
+        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+            ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY);
+
+            synchronized (ApprovedPrintServices.sLock) {
+                if (mApprovedServices.isApprovedService(printService)) {
+                    dismiss();
+                }
+            }
+        }
+    }
+
     private final class MyClickListener implements OnClickListener {
         @Override
         public void onClick(View view) {
             if (view == mPrintButton) {
                 if (mCurrentPrinter != null) {
-                    confirmPrint();
+                    if (mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter) {
+                        confirmPrint();
+                    } else {
+                        ApprovedPrintServices approvedServices =
+                                new ApprovedPrintServices(PrintActivity.this);
+
+                        ComponentName printService = mCurrentPrinter.getId().getServiceName();
+                        if (approvedServices.isApprovedService(printService)) {
+                            confirmPrint();
+                        } else {
+                            PrintServiceApprovalDialog.newInstance(printService)
+                                    .show(getFragmentManager(), "approve");
+                        }
+                    }
                 } else {
                     cancelPrint();
                 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
new file mode 100644
index 0000000..dd10567
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2015 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.printspooler.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.printservice.PrintService;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+/**
+ * Manage approved print services. These services are stored in the shared preferences.
+ */
+public class ApprovedPrintServices {
+    /**
+     * Used for locking accesses to the approved services.
+     */
+    static final public Object sLock = new Object();
+
+    private static final String APPROVED_SERVICES_PREFERENCE = "PRINT_SPOOLER_APPROVED_SERVICES";
+    private final SharedPreferences mPreferences;
+
+    /**
+     * Create a new {@link ApprovedPrintServices}
+     *
+     * @param owner The {@link Context} using this object.
+     */
+    public ApprovedPrintServices(Context owner) {
+        mPreferences = owner.getSharedPreferences(APPROVED_SERVICES_PREFERENCE,
+                Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Get {@link Set} of approved services.
+     *
+     * @return A {@link Set} containing all currently approved services.
+     */
+    public Set<String> getApprovedServices() {
+        return mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null);
+    }
+
+    /**
+     * Check if a {@link PrintService} is approved.
+     *
+     * This function does not acquire the {@link #sLock}.
+     *
+     * @param service The {@link ComponentName} of the {@link PrintService} that might be approved
+     * @return true iff the service is currently approved
+     */
+    public boolean isApprovedService(ComponentName service) {
+        final Set<String> approvedServices = getApprovedServices();
+
+        if (approvedServices != null) {
+            final String flattenedString = service.flattenToShortString();
+
+            for (String approvedService : approvedServices) {
+                if (approvedService.equals(flattenedString)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Add a {@link PrintService} to the list of approved print services.
+     *
+     * @param serviceToAdd The {@link ComponentName} of the {@link PrintService} to be approved.
+     */
+    public void addApprovedService(ComponentName serviceToAdd) {
+        synchronized (sLock) {
+            Set<String> oldApprovedServices =
+                    mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null);
+
+            Set<String> newApprovedServices;
+            if (oldApprovedServices == null) {
+                newApprovedServices = new ArraySet<String>(1);
+            } else {
+                // Copy approved services.
+                newApprovedServices = new ArraySet<String>(oldApprovedServices);
+            }
+            newApprovedServices.add(serviceToAdd.flattenToShortString());
+
+            SharedPreferences.Editor editor = mPreferences.edit();
+            editor.putStringSet(APPROVED_SERVICES_PREFERENCE, newApprovedServices);
+            editor.apply();
+        }
+    }
+
+    /**
+     * Add a {@link OnSharedPreferenceChangeListener} that listens for changes to the approved
+     * services. Should only be called while holding {@link #sLock} to synchronize against
+     * {@link #addApprovedService}.
+     *
+     * @param listener {@link OnSharedPreferenceChangeListener} to register
+     */
+    public void registerChangeListenerLocked(OnSharedPreferenceChangeListener listener) {
+        mPreferences.registerOnSharedPreferenceChangeListener(listener);
+    }
+
+    /**
+     * Unregister a listener registered in {@link #registerChangeListenerLocked}.
+     *
+     * @param listener {@link OnSharedPreferenceChangeListener} to unregister
+     */
+    public void unregisterChangeListener(OnSharedPreferenceChangeListener listener) {
+        mPreferences.unregisterOnSharedPreferenceChangeListener(listener);
+    }
+
+    /**
+     * If a {@link PrintService} is approved, remove it from the list of approved services.
+     *
+     * @param serviceToRemove The {@link ComponentName} of the {@link PrintService} to be removed
+     */
+    public void removeApprovedService(ComponentName serviceToRemove) {
+        synchronized (sLock) {
+            if (isApprovedService(serviceToRemove)) {
+                // Copy approved services.
+                ArraySet<String> approvedServices = new ArraySet<String>(
+                        mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null));
+
+                SharedPreferences.Editor editor = mPreferences.edit();
+
+                final int numApprovedServices = approvedServices.size();
+                for (int i = 0; i < numApprovedServices; i++) {
+                    if (approvedServices.valueAt(i)
+                            .equals(serviceToRemove.flattenToShortString())) {
+                        approvedServices.removeAt(i);
+                        break;
+                    }
+                }
+
+                editor.putStringSet(APPROVED_SERVICES_PREFERENCE, approvedServices);
+                editor.apply();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d7176fd..c259ac2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1,68 +1,28 @@
-/*
- * Copyright (C) 2015 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.pm;
 
-import android.app.ActivityManager;
 import android.content.ComponentName;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageInstaller.SessionParams;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
-import android.text.TextUtils;
 
-import com.android.internal.util.SizedInputStream;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.WeakHashMap;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
 
 class PackageManagerShellCommand extends ShellCommand {
     final IPackageManager mInterface;
@@ -82,21 +42,8 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch(cmd) {
-                case "install":
-                    return runInstall();
-                case "install-abandon":
-                case "install-destroy":
-                    return runInstallAbandon();
-                case "install-commit":
-                    return runInstallCommit();
-                case "install-create":
-                    return runInstallCreate();
-                case "install-write":
-                    return runInstallWrite();
                 case "list":
                     return runList();
-                case "uninstall":
-                    return runUninstall();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -106,65 +53,6 @@
         return -1;
     }
 
-    private int runInstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams params = makeInstallParams();
-        final int sessionId = doCreateSession(params.sessionParams,
-                params.installerPackageName, params.userId);
-
-        final String inPath = getNextArg();
-        if (inPath == null && params.sessionParams.sizeBytes == 0) {
-            pw.println("Error: must either specify a package size or an APK file");
-            return 1;
-        }
-        if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk") != 0) {
-            return 1;
-        }
-        if (doCommitSession(sessionId) != 0) {
-            return 1;
-        }
-        return 0;
-    }
-
-    private int runInstallAbandon() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doAbandonSession(sessionId);
-    }
-
-    private int runInstallCommit() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doCommitSession(sessionId);
-    }
-
-    private int runInstallCreate() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams installParams = makeInstallParams();
-        final int sessionId = doCreateSession(installParams.sessionParams,
-                installParams.installerPackageName, installParams.userId);
-
-        // NOTE: adb depends on parsing this string
-        pw.println("Success: created install session [" + sessionId + "]");
-        return 0;
-    }
-
-    private int runInstallWrite() throws RemoteException {
-        long sizeBytes = -1;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            if (opt.equals("-S")) {
-                sizeBytes = Long.parseLong(getNextArg());
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
-            }
-        }
-
-        final int sessionId = Integer.parseInt(getNextArg());
-        final String splitName = getNextArg();
-        final String path = getNextArg();
-        return doWriteSession(sessionId, path, sizeBytes, splitName);
-    }
-
     private int runList() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         final String type = getNextArg();
@@ -475,279 +363,6 @@
         return 0;
     }
 
-    private int runUninstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        int flags = 0;
-        int userId = UserHandle.USER_ALL;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-k":
-                    flags |= PackageManager.DELETE_KEEP_DATA;
-                    break;
-                case "--user":
-                    userId = Integer.parseInt(getNextArg());
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
-            return 1;
-        }
-
-        userId = translateUserId(userId, "runUninstall");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            flags |= PackageManager.DELETE_ALL_USERS;
-        } else {
-            final PackageInfo info = mInterface.getPackageInfo(packageName, 0, userId);
-            if (info == null) {
-                pw.println("Failure - not installed for " + userId);
-                return 1;
-            }
-            final boolean isSystem =
-                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            // If we are being asked to delete a system app for just one
-            // user set flag so it disables rather than reverting to system
-            // version of the app.
-            if (isSystem) {
-                flags |= PackageManager.DELETE_SYSTEM_APP;
-            }
-        }
-
-        final LocalIntentReceiver receiver = new LocalIntentReceiver();
-        mInterface.getPackageInstaller().uninstall(packageName, null /*callerPackageName*/, flags,
-                receiver.getIntentSender(), userId);
-
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == PackageInstaller.STATUS_SUCCESS) {
-            pw.println("Success");
-            return 0;
-        } else {
-            pw.println("Failure ["
-                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-            return 1;
-        }
-    }
-
-    private static class InstallParams {
-        SessionParams sessionParams;
-        String installerPackageName;
-        int userId = UserHandle.USER_ALL;
-    }
-
-    private InstallParams makeInstallParams() {
-        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
-        final InstallParams params = new InstallParams();
-        params.sessionParams = sessionParams;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-l":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
-                    break;
-                case "-r":
-                    sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-                    break;
-                case "-i":
-                    params.installerPackageName = getNextArg();
-                    if (params.installerPackageName == null) {
-                        throw new IllegalArgumentException("Missing installer package");
-                    }
-                    break;
-                case "-t":
-                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
-                    break;
-                case "-s":
-                    sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
-                    break;
-                case "-f":
-                    sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
-                    break;
-                case "-d":
-                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
-                    break;
-                case "-g":
-                    sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
-                    break;
-                case "--originating-uri":
-                    sessionParams.originatingUri = Uri.parse(getNextArg());
-                    break;
-                case "--referrer":
-                    sessionParams.referrerUri = Uri.parse(getNextArg());
-                    break;
-                case "-p":
-                    sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
-                    sessionParams.appPackageName = getNextArg();
-                    if (sessionParams.appPackageName == null) {
-                        throw new IllegalArgumentException("Missing inherit package name");
-                    }
-                    break;
-                case "-S":
-                    sessionParams.setSize(Long.parseLong(getNextArg()));
-                    break;
-                case "--abi":
-                    sessionParams.abiOverride = checkAbiArgument(getNextArg());
-                    break;
-                case "--user":
-                    params.userId = Integer.parseInt(getNextArg());
-                    break;
-                case "--install-location":
-                    sessionParams.installLocation = Integer.parseInt(getNextArg());
-                    break;
-                case "--force-uuid":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
-                    sessionParams.volumeUuid = getNextArg();
-                    if ("internal".equals(sessionParams.volumeUuid)) {
-                        sessionParams.volumeUuid = null;
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown option " + opt);
-            }
-        }
-        return params;
-    }
-
-    private static String checkAbiArgument(String abi) {
-        if (TextUtils.isEmpty(abi)) {
-            throw new IllegalArgumentException("Missing ABI argument");
-        }
-
-        if ("-".equals(abi)) {
-            return abi;
-        }
-
-        final String[] supportedAbis = Build.SUPPORTED_ABIS;
-        for (String supportedAbi : supportedAbis) {
-            if (supportedAbi.equals(abi)) {
-                return abi;
-            }
-        }
-
-        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
-    }
-
-    private int translateUserId(int userId, String logContext) {
-        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, true, logContext, "pm command");
-    }
-
-    private int doCreateSession(SessionParams params, String installerPackageName, int userId)
-            throws RemoteException {
-        userId = translateUserId(userId, "runInstallCreate");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
-        }
-
-        final int sessionId = mInterface.getPackageInstaller()
-                .createSession(params, installerPackageName, userId);
-        return sessionId;
-    }
-
-    private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName)
-            throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        if ("-".equals(inPath)) {
-            inPath = null;
-        } else if (inPath != null) {
-            final File file = new File(inPath);
-            if (file.isFile()) {
-                sizeBytes = file.length();
-            }
-        }
-
-        final SessionInfo info = mInterface.getPackageInstaller().getSessionInfo(sessionId);
-
-        PackageInstaller.Session session = null;
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            session = new PackageInstaller.Session(
-                    mInterface.getPackageInstaller().openSession(sessionId));
-
-            if (inPath != null) {
-                in = new FileInputStream(inPath);
-            } else {
-                in = new SizedInputStream(getInputStream(), sizeBytes);
-            }
-            out = session.openWrite(splitName, 0, sizeBytes);
-
-            int total = 0;
-            byte[] buffer = new byte[65536];
-            int c;
-            while ((c = in.read(buffer)) != -1) {
-                total += c;
-                out.write(buffer, 0, c);
-
-                if (info.sizeBytes > 0) {
-                    final float fraction = ((float) c / (float) info.sizeBytes);
-                    session.addProgress(fraction);
-                }
-            }
-            session.fsync(out);
-
-            pw.println("Success: streamed " + total + " bytes");
-            return 0;
-        } catch (IOException e) {
-            pw.println("Error: failed to write; " + e.getMessage());
-            return 1;
-        } finally {
-            IoUtils.closeQuietly(out);
-            IoUtils.closeQuietly(in);
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private int doCommitSession(int sessionId) throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(
-                    mInterface.getPackageInstaller().openSession(sessionId));
-
-            final LocalIntentReceiver receiver = new LocalIntentReceiver();
-            session.commit(receiver.getIntentSender());
-
-            final Intent result = receiver.getResult();
-            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status == PackageInstaller.STATUS_SUCCESS) {
-                pw.println("Success");
-            } else {
-                pw.println("Failure ["
-                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-                pw.println("Failure details: " + result.getExtras());
-            }
-            return status;
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private int doAbandonSession(int sessionId) throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(
-                    mInterface.getPackageInstaller().openSession(sessionId));
-            session.abandon();
-            pw.println("Success");
-            return 0;
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
     private void doListPermissions(ArrayList<String> groupList, boolean groups, boolean labels,
             boolean summary, int startProtectionLevel, int endProtectionLevel)
                     throws RemoteException {
@@ -910,34 +525,5 @@
         pw.println("      -u: list only the permissions users will see");
         pw.println("");
     }
-
-    private static class LocalIntentReceiver {
-        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public int send(int code, Intent intent, String resolvedType,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return 0;
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-
-        public Intent getResult() {
-            try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
 }
 
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 34347cf..92bd81f 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -19,14 +19,10 @@
 import android.Manifest;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
@@ -51,7 +47,6 @@
 import android.text.TextUtils;
 import android.util.SparseArray;
 
-import com.android.internal.R;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.SystemService;
@@ -527,6 +522,7 @@
                         while (iterator.hasNext()) {
                             ComponentName componentName = iterator.next();
                             if (packageName.equals(componentName.getPackageName())) {
+                                userState.removeApprovedPrintService(componentName);
                                 iterator.remove();
                                 servicesRemoved = true;
                             }
@@ -587,15 +583,29 @@
                         return;
                     }
 
-                    final int installedServiceCount = installedServices.size();
-                    for (int i = 0; i < installedServiceCount; i++) {
-                        ServiceInfo serviceInfo = installedServices.get(i).serviceInfo;
-                        ComponentName component = new ComponentName(serviceInfo.packageName,
-                                serviceInfo.name);
-                        String label = serviceInfo.loadLabel(mContext.getPackageManager())
-                                .toString();
-                        showEnableInstalledPrintServiceNotification(component, label,
-                                getChangingUserId());
+                    // Enable all added services by default
+                    synchronized (mLock) {
+                        UserState userState = getOrCreateUserStateLocked(getChangingUserId());
+
+                        Set<ComponentName> enabledServices = userState.getEnabledServices();
+                        boolean servicesAdded = false;
+
+                        final int installedServiceCount = installedServices.size();
+                        for (int i = 0; i < installedServiceCount; i++) {
+                            ServiceInfo serviceInfo = installedServices.get(i).serviceInfo;
+                            ComponentName component = new ComponentName(serviceInfo.packageName,
+                                    serviceInfo.name);
+
+                            enabledServices.add(component);
+                            servicesAdded = true;
+                        }
+
+                        if (servicesAdded) {
+                            persistComponentNamesToSettingLocked(
+                                    Settings.Secure.ENABLED_PRINT_SERVICES, enabledServices,
+                                    getChangingUserId());
+                            userState.updateIfNeededLocked();
+                        }
                     }
                 }
 
@@ -733,43 +743,5 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
-
-        private void showEnableInstalledPrintServiceNotification(ComponentName component,
-                String label, int userId) {
-            UserHandle userHandle = new UserHandle(userId);
-
-            Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
-            intent.putExtra(EXTRA_PRINT_SERVICE_COMPONENT_NAME, component.flattenToString());
-
-            PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, intent,
-                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null,
-                    userHandle);
-
-            Context builderContext = mContext;
-            try {
-                builderContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
-                        userHandle);
-            } catch (NameNotFoundException e) {
-                // Ignore can't find the package the system is running as.
-            }
-            Notification.Builder builder = new Notification.Builder(builderContext)
-                    .setSmallIcon(R.drawable.ic_print)
-                    .setContentTitle(mContext.getString(R.string.print_service_installed_title,
-                            label))
-                    .setContentText(mContext.getString(R.string.print_service_installed_message))
-                    .setContentIntent(pendingIntent)
-                    .setWhen(System.currentTimeMillis())
-                    .setAutoCancel(true)
-                    .setShowWhen(true)
-                    .setColor(mContext.getColor(
-                            com.android.internal.R.color.system_notification_accent_color));
-
-            NotificationManager notificationManager = (NotificationManager) mContext
-                    .getSystemService(Context.NOTIFICATION_SERVICE);
-
-            String notificationTag = getClass().getName() + ":" + component.flattenToString();
-            notificationManager.notifyAsUser(notificationTag, 0, builder.build(),
-                    userHandle);
-        }
     }
 }
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index 85c876a..e5370f4 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -279,6 +279,35 @@
         }
     }
 
+    /**
+     * Connect to the print spooler service and remove an approved print service.
+     *
+     * @param serviceToRemove The {@link ComponentName} of the service to be removed.
+     */
+    public final void removeApprovedPrintService(ComponentName serviceToRemove) {
+        throwIfCalledOnMainThread();
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            mCanUnbind = false;
+        }
+        try {
+            getRemoteInstanceLazy().removeApprovedPrintService(serviceToRemove);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Error removing approved print service.", re);
+        } catch (TimeoutException te) {
+            Slog.e(LOG_TAG, "Error removing approved print service.", te);
+        } finally {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+                        + "] removing approved print service()");
+            }
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
+        }
+    }
+
     public final void removeObsoletePrintJobs() {
         throwIfCalledOnMainThread();
         synchronized (mLock) {
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index ae19dac..f37bb9eb 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -297,6 +297,15 @@
         }
     }
 
+    /**
+     * Remove an approved print service.
+     *
+     * @param serviceToRemove The {@link ComponentName} of the service to be removed.
+     */
+    public void removeApprovedPrintService(ComponentName serviceToRemove) {
+        mSpooler.removeApprovedPrintService(serviceToRemove);
+    }
+
     public void restartPrintJob(PrintJobId printJobId, int appId) {
         PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId);
         if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {