Merge "Print job files and print job records not always cleaned up." into klp-dev
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 291e81f..96b168d 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -36,7 +36,6 @@
  */
 oneway interface IPrintSpooler {
     void removeObsoletePrintJobs();
-    void forgetPrintJobs(in List<PrintJobId> printJob);
     void getPrintJobInfos(IPrintSpoolerCallbacks callback, in ComponentName componentName,
             int state, int appId, int sequence);
     void getPrintJobInfo(in PrintJobId printJobId, IPrintSpoolerCallbacks callback,
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 0cf00cc..8270812 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -29,5 +29,5 @@
     void onPrintJobQueued(in PrintJobInfo printJob);
     void onAllPrintJobsForServiceHandled(in ComponentName printService);
     void onAllPrintJobsHandled();
-    void onPrintJobStateChanged(in PrintJobId printJobId, int appId);
+    void onPrintJobStateChanged(in PrintJobInfo printJob);
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 62b35fe..87181f7 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -43,6 +43,7 @@
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
@@ -59,10 +60,12 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -82,6 +85,8 @@
 
     private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
 
+    private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
+
     private static final String PRINT_FILE_EXTENSION = "pdf";
 
     private static final Object sLock = new Object();
@@ -168,9 +173,9 @@
                         PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
                         | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
 
-                Message message = mHandlerCaller.obtainMessageIIO(
+                Message message = mHandlerCaller.obtainMessageO(
                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                        printJob.getAppId(), 0, printJob.getId());
+                        printJob);
                 mHandlerCaller.executeOrSendMessage(message);
 
                 message = mHandlerCaller.obtainMessageOO(
@@ -179,9 +184,6 @@
                 mHandlerCaller.executeOrSendMessage(message);
 
                 printJob.setCreationTime(System.currentTimeMillis());
-                synchronized (mLock) {
-                    mPersistanceManager.writeStateLocked();
-                }
             }
 
             @Override
@@ -225,12 +227,40 @@
             }
 
             @Override
-            public void forgetPrintJobs(List<PrintJobId> printJobIds) {
-                PrintSpoolerService.this.forgetPrintJobs(printJobIds);
+            protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+                PrintSpoolerService.this.dump(fd, writer, args);
             }
         };
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            String prefix = args[0];
+            String tab = "  ";
+
+            pw.append(prefix).append("print jobs:").println();
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                pw.append(prefix).append(tab).append(printJob.toString());
+                pw.println();
+            }
+
+            pw.append(prefix).append("print job files:").println();
+            File[] files = getFilesDir().listFiles();
+            if (files != null) {
+                final int fileCount = files.length;
+                for (int i = 0; i < fileCount; i++) {
+                    File file = files[i];
+                    if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
+                        pw.append(prefix).append(tab).append(file.getName()).println();
+                    }
+                }
+            }
+        }
+    }
+
     private void sendOnPrintJobQueued(PrintJobInfo printJob) {
         Message message = mHandlerCaller.obtainMessageO(
                 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
@@ -324,10 +354,9 @@
 
                 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
                     if (mClient != null) {
-                        PrintJobId printJobId = (PrintJobId) message.obj;
-                        final int appId = message.arg1;
+                        PrintJobInfo printJob = (PrintJobInfo) message.obj;
                         try {
-                            mClient.onPrintJobStateChanged(printJobId, appId);
+                            mClient.onPrintJobStateChanged(printJob);
                         } catch (RemoteException re) {
                             Slog.e(LOG_TAG, "Error notify for print job state change.", re);
                         }
@@ -391,17 +420,46 @@
     public void createPrintJob(PrintJobInfo printJob) {
         synchronized (mLock) {
             addPrintJobLocked(printJob);
+            setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
         }
     }
 
     private void handleReadPrintJobsLocked() {
+        // Make a map with the files for a print job since we may have
+        // to delete some. One example of getting orphan files if the
+        // spooler crashes while constructing a print job. We do not
+        // persist partially populated print jobs under construction to
+        // avoid special handling for various attributes missing.
+        ArrayMap<PrintJobId, File> fileForJobMap = null;
+        File[] files = getFilesDir().listFiles();
+        if (files != null) {
+            final int fileCount = files.length;
+            for (int i = 0; i < fileCount; i++) {
+                File file = files[i];
+                if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
+                    if (fileForJobMap == null) {
+                        fileForJobMap = new ArrayMap<PrintJobId, File>();
+                    }
+                    String printJobIdString = file.getName().substring(0,
+                            PRINT_JOB_FILE_PREFIX.length());
+                    PrintJobId printJobId = PrintJobId.unflattenFromString(
+                            printJobIdString);
+                    fileForJobMap.put(printJobId, file);
+                }
+            }
+        }
+
         final int printJobCount = mPrintJobs.size();
         for (int i = 0; i < printJobCount; i++) {
             PrintJobInfo printJob = mPrintJobs.get(i);
 
+            // We want to have only the orphan files at the end.
+            if (fileForJobMap != null) {
+                fileForJobMap.remove(printJob.getId());
+            }
+
             // Update the notification.
             mNotificationController.onPrintJobStateChanged(printJob);
-
             switch (printJob.getState()) {
                 case PrintJobInfo.STATE_QUEUED:
                 case PrintJobInfo.STATE_STARTED:
@@ -415,6 +473,15 @@
                 } break;
             }
         }
+
+        // Delete the orphan files.
+        if (fileForJobMap != null) {
+            final int orphanFileCount = fileForJobMap.size();
+            for (int i = 0; i < orphanFileCount; i++) {
+                File file = fileForJobMap.valueAt(i);
+                file.delete();
+            }
+        }
     }
 
     public void checkAllPrintJobsHandled() {
@@ -465,7 +532,7 @@
     }
 
     public File generateFileForPrintJob(PrintJobId printJobId) {
-        return new File(getFilesDir(), "print_job_"
+        return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
                 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
     }
 
@@ -476,31 +543,6 @@
         }
     }
 
-    private void forgetPrintJobs(List<PrintJobId> printJobIds) {
-        synchronized (mLock) {
-            boolean printJobsRemoved = false;
-            final int removedPrintJobCount = printJobIds.size();
-            for (int i = 0; i < removedPrintJobCount; i++) {
-                PrintJobId removedPrintJobId = printJobIds.get(i);
-                final int printJobCount = mPrintJobs.size();
-                for (int j = printJobCount - 1; j >= 0; j--) {
-                    PrintJobInfo printJob = mPrintJobs.get(j);
-                    if (removedPrintJobId.equals(printJob.getId())) {
-                        mPrintJobs.remove(j);
-                        printJobsRemoved = true;
-                        if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                            Slog.i(LOG_TAG, "[FORGOT] " + printJob.getId().flattenToString());
-                        }
-                        removePrintJobFileLocked(printJob.getId());
-                    }
-                }
-            }
-            if (printJobsRemoved) {
-                mPersistanceManager.writeStateLocked();
-            }
-        }
-    }
-
     private void removeObsoletePrintJobs() {
         synchronized (mLock) {
             final int printJobCount = mPrintJobs.size();
@@ -523,7 +565,7 @@
         if (file.exists()) {
             file.delete();
             if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId.flattenToString());
+                Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
             }
         }
     }
@@ -552,10 +594,7 @@
                 switch (state) {
                     case PrintJobInfo.STATE_COMPLETED:
                     case PrintJobInfo.STATE_CANCELED:
-                        // Just remove the file but keep the print job info since
-                        // the app that created it may be holding onto the PrintJob
-                        // instance and query it for its most recent state. We will
-                        // remove the info for this job when told so by the system.
+                        mPrintJobs.remove(printJob);
                         removePrintJobFileLocked(printJob.getId());
                         // $fall-through$
 
@@ -582,9 +621,9 @@
                     notifyOnAllPrintJobsHandled();
                 }
 
-                Message message = mHandlerCaller.obtainMessageIIO(
+                Message message = mHandlerCaller.obtainMessageO(
                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                        printJob.getAppId(), 0, printJob.getId());
+                        printJob);
                 mHandlerCaller.executeOrSendMessage(message);
             }
         }
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index f98a805..798cea3 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -33,7 +33,6 @@
 import android.print.IPrintSpoolerClient;
 import android.print.PrintJobId;
 import android.print.PrintJobInfo;
-import android.print.PrintManager;
 import android.util.Slog;
 import android.util.TimedRemoteCaller;
 
@@ -91,7 +90,7 @@
     public static interface PrintSpoolerCallbacks {
         public void onPrintJobQueued(PrintJobInfo printJob);
         public void onAllPrintJobsForServiceHandled(ComponentName printService);
-        public void onPrintJobStateChanged(PrintJobId printJobId, int appId);
+        public void onPrintJobStateChanged(PrintJobInfo printJob);
     }
 
     public RemotePrintSpooler(Context context, int userId,
@@ -280,30 +279,6 @@
         }
     }
 
-    public final void forgetPrintJobs(List<PrintJobId> printJobIds) {
-        throwIfCalledOnMainThread();
-        synchronized (mLock) {
-            throwIfDestroyedLocked();
-            mCanUnbind = false;
-        }
-        try {
-            getRemoteInstanceLazy().forgetPrintJobs(printJobIds);
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error forgeting print jobs", re);
-        } catch (TimeoutException te) {
-            Slog.e(LOG_TAG, "Error forgeting print jobs", te);
-        } finally {
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
-                        + "] forgetPrintJobs()");
-            }
-            synchronized (mLock) {
-                mCanUnbind = true;
-                mLock.notifyAll();
-            }
-        }
-    }
-
     public final void destroy() {
         throwIfCalledOnMainThread();
         if (DEBUG) {
@@ -323,18 +298,15 @@
                     .append(String.valueOf(mDestroyed)).println();
             pw.append(prefix).append("bound=")
                     .append((mRemoteInstance != null) ? "true" : "false").println();
-            pw.append(prefix).append("print jobs:").println();
-            if (mRemoteInstance != null) {
-                List<PrintJobInfo> printJobs = getPrintJobInfos(null,
-                        PrintJobInfo.STATE_ANY, PrintManager.APP_ID_ANY);
-                if (printJobs != null) {
-                    final int printJobCount = printJobs.size();
-                    for (int i = 0; i < printJobCount; i++) {
-                        PrintJobInfo printJob = printJobs.get(i);
-                        pw.append(prefix).append(prefix).append(printJob.toString());
-                        pw.println();
-                    }
-                }
+
+            pw.flush();
+
+            try {
+                getRemoteInstanceLazy().asBinder().dump(fd, new String[]{prefix});
+            } catch (TimeoutException te) {
+                /* ignore */
+            } catch (RemoteException re) {
+                /* ignore */
             }
         }
     }
@@ -346,8 +318,8 @@
         }
     }
 
-    private void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
-        mCallbacks.onPrintJobStateChanged(printJobId, appId);
+    private void onPrintJobStateChanged(PrintJobInfo printJob) {
+        mCallbacks.onPrintJobStateChanged(printJob);
     }
 
     private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
@@ -625,12 +597,12 @@
         }
 
         @Override
-        public void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
+        public void onPrintJobStateChanged(PrintJobInfo printJob) {
             RemotePrintSpooler spooler = mWeakSpooler.get();
             if (spooler != null) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    spooler.onPrintJobStateChanged(printJobId, appId);
+                    spooler.onPrintJobStateChanged(printJob);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index e5f5842..72acc53 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -33,7 +33,6 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.os.UserManager;
 import android.print.IPrintClient;
 import android.print.IPrintDocumentAdapter;
 import android.print.IPrintJobStateChangeListener;
@@ -52,6 +51,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.R;
 import com.android.internal.os.BackgroundThread;
@@ -62,6 +62,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -93,8 +94,8 @@
     private final Set<ComponentName> mEnabledServices =
             new ArraySet<ComponentName>();
 
-    private final CreatedPrintJobTracker mCreatedPrintJobTracker =
-            new CreatedPrintJobTracker();
+    private final PrintJobForAppCache mPrintJobForAppCache =
+            new PrintJobForAppCache();
 
     private final Object mLock;
 
@@ -155,23 +156,22 @@
     public PrintJobInfo print(String printJobName, final IPrintClient client,
             final IPrintDocumentAdapter documentAdapter, PrintAttributes attributes,
             int appId) {
-        PrintJobId printJobId = new PrintJobId();
-
-        // Track this job so we can forget it when the creator dies.
-        if (!mCreatedPrintJobTracker.onPrintJobCreatedLocked(client.asBinder(), printJobId)) {
-            // Not adding a print job means the client is dead - done.
-            return null;
-        }
-
         // Create print job place holder.
         final PrintJobInfo printJob = new PrintJobInfo();
-        printJob.setId(printJobId);
+        printJob.setId(new PrintJobId());
         printJob.setAppId(appId);
         printJob.setLabel(printJobName);
         printJob.setAttributes(attributes);
         printJob.setState(PrintJobInfo.STATE_CREATED);
         printJob.setCopies(1);
 
+        // Track this job so we can forget it when the creator dies.
+        if (!mPrintJobForAppCache.onPrintJobCreated(client.asBinder(), appId,
+                printJob)) {
+            // Not adding a print job means the client is dead - done.
+            return null;
+        }
+
         // Spin the spooler to add the job and show the config UI.
         new AsyncTask<Void, Void, Void>() {
             @Override
@@ -185,10 +185,40 @@
     }
 
     public List<PrintJobInfo> getPrintJobInfos(int appId) {
-        return mSpooler.getPrintJobInfos(null, PrintJobInfo.STATE_ANY, appId);
+        List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId);
+        // Note that the print spooler is not storing print jobs that
+        // are in a terminal state as it is non-trivial to properly update
+        // the spooler state for when to forget print jobs in terminal state.
+        // Therefore, we fuse the cached print jobs for running apps (some
+        // jobs are in a terminal state) with the ones that the print
+        // spooler knows about (some jobs are being processed).
+        ArrayMap<PrintJobId, PrintJobInfo> result =
+                new ArrayMap<PrintJobId, PrintJobInfo>();
+
+        // Add the cached print jobs for running apps.
+        final int cachedPrintJobCount = cachedPrintJobs.size();
+        for (int i = 0; i < cachedPrintJobCount; i++) {
+            PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i);
+            result.put(cachedPrintJob.getId(), cachedPrintJob);
+        }
+
+        // Add everything else the spooler knows about.
+        List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(null,
+                PrintJobInfo.STATE_ANY, appId);
+        final int printJobCount = printJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = printJobs.get(i);
+            result.put(printJob.getId(), printJob);
+        }
+
+        return new ArrayList<PrintJobInfo>(result.values());
     }
 
     public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
+        PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId);
+        if (printJob != null) {
+            return printJob;
+        }
         return mSpooler.getPrintJobInfo(printJobId, appId);
     }
 
@@ -398,9 +428,10 @@
     }
 
     @Override
-    public void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
+    public void onPrintJobStateChanged(PrintJobInfo printJob) {
+        mPrintJobForAppCache.onPrintJobStateChanged(printJob);
         mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_JOB_STATE_CHANGED,
-                appId, 0, printJobId).sendToTarget();
+                printJob.getAppId(), 0, printJob.getId()).sendToTarget();
     }
 
     @Override
@@ -525,6 +556,9 @@
             pw.println();
         }
 
+        pw.append(prefix).append(tab).append("cached print jobs:").println();
+        mPrintJobForAppCache.dump(pw, prefix + tab + tab);
+
         pw.append(prefix).append(tab).append("discovery mediator:").println();
         if (mPrinterDiscoverySession != null) {
             mPrinterDiscoverySession.dump(pw, prefix + tab + tab);
@@ -1424,34 +1458,19 @@
         }
     }
 
-    private final class CreatedPrintJobTracker {
-        private final ArrayMap<IBinder, List<PrintJobId>> mCreatedPrintJobs =
-                new ArrayMap<IBinder, List<PrintJobId>>();
+    private final class PrintJobForAppCache {
+        private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp =
+                new SparseArray<List<PrintJobInfo>>();
 
-        public boolean onPrintJobCreatedLocked(final IBinder creator, PrintJobId printJobId) {
+        public boolean onPrintJobCreated(final IBinder creator, final int appId,
+                PrintJobInfo printJob) {
             try {
                 creator.linkToDeath(new DeathRecipient() {
                     @Override
                     public void binderDied() {
                         creator.unlinkToDeath(this, 0);
-                        UserManager userManager = (UserManager) mContext.getSystemService(
-                                Context.USER_SERVICE);
-                        // If the death is a result of the user being removed, then
-                        // do nothing since the spooler data for this user will be
-                        // wiped and we cannot bind to the spooler at this point.
-                        if (userManager.getUserInfo(mUserId) == null) {
-                            return;
-                        }
-                        List<PrintJobId> printJobIds = null;
                         synchronized (mLock) {
-                            printJobIds = mCreatedPrintJobs.remove(creator);
-                            if (printJobIds == null) {
-                                return;
-                            }
-                            printJobIds = new ArrayList<PrintJobId>(printJobIds);
-                        }
-                        if (printJobIds != null) {
-                            mSpooler.forgetPrintJobs(printJobIds);
+                            mPrintJobsForRunningApp.remove(appId);
                         }
                     }
                 }, 0);
@@ -1460,14 +1479,93 @@
                 return false;
             }
             synchronized (mLock) {
-                List<PrintJobId> printJobIds = mCreatedPrintJobs.get(creator);
-                if (printJobIds == null) {
-                    printJobIds = new ArrayList<PrintJobId>();
-                    mCreatedPrintJobs.put(creator, printJobIds);
+                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
+                if (printJobsForApp == null) {
+                    printJobsForApp = new ArrayList<PrintJobInfo>();
+                    mPrintJobsForRunningApp.put(appId, printJobsForApp);
                 }
-                printJobIds.add(printJobId);
+                printJobsForApp.add(printJob);
             }
             return true;
         }
+
+        public void onPrintJobStateChanged(PrintJobInfo printJob) {
+            synchronized (mLock) {
+                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(
+                        printJob.getAppId());
+                if (printJobsForApp == null) {
+                    return;
+                }
+                final int printJobCount = printJobsForApp.size();
+                for (int i = 0; i < printJobCount; i++) {
+                    PrintJobInfo oldPrintJob = printJobsForApp.get(i);
+                    if (oldPrintJob.getId().equals(printJob.getId())) {
+                        printJobsForApp.set(i, printJob);
+                    }
+                }
+            }
+        }
+
+        public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) {
+            synchronized (mLock) {
+                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
+                if (printJobsForApp == null) {
+                    return null;
+                }
+                final int printJobCount = printJobsForApp.size();
+                for (int i = 0; i < printJobCount; i++) {
+                    PrintJobInfo printJob = printJobsForApp.get(i);
+                    if (printJob.getId().equals(printJobId)) {
+                        return printJob;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public List<PrintJobInfo> getPrintJobs(int appId) {
+            synchronized (mLock) {
+                List<PrintJobInfo> printJobs = null;
+                if (appId == PrintManager.APP_ID_ANY) {
+                    final int bucketCount = mPrintJobsForRunningApp.size();
+                    for (int i = 0; i < bucketCount; i++) {
+                        List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
+                        if (printJobs == null) {
+                            printJobs = new ArrayList<PrintJobInfo>();
+                        }
+                        printJobs.addAll(bucket);
+                    }
+                } else {
+                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId);
+                    if (bucket != null) {
+                        if (printJobs == null) {
+                            printJobs = new ArrayList<PrintJobInfo>();
+                        }
+                        printJobs.addAll(bucket);
+                    }
+                }
+                if (printJobs != null) {
+                    return printJobs;
+                }
+                return Collections.emptyList();
+            }
+        }
+
+        public void dump(PrintWriter pw, String prefix) {
+            synchronized (mLock) {
+                String tab = "  ";
+                final int bucketCount = mPrintJobsForRunningApp.size();
+                for (int i = 0; i < bucketCount; i++) {
+                    final int appId = mPrintJobsForRunningApp.keyAt(i);
+                    pw.append(prefix).append("appId=" + appId).append(':').println();
+                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
+                    final int printJobCount = bucket.size();
+                    for (int j = 0; j < printJobCount; j++) {
+                        PrintJobInfo printJob = bucket.get(j);
+                        pw.append(prefix).append(tab).append(printJob.toString()).println();
+                    }
+                }
+            }
+        }
     }
 }