Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b415525a authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Print job files and print job records not always cleaned up." into klp-dev

parents 16e6e203 dd68da27
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import android.print.PrintJobInfo;
 */
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,
+1 −1
Original line number Diff line number Diff line
@@ -29,5 +29,5 @@ oneway interface IPrintSpoolerClient {
    void onPrintJobQueued(in PrintJobInfo printJob);
    void onAllPrintJobsForServiceHandled(in ComponentName printService);
    void onAllPrintJobsHandled();
    void onPrintJobStateChanged(in PrintJobId printJobId, int appId);
    void onPrintJobStateChanged(in PrintJobInfo printJob);
}
+83 −44
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.print.PrintManager;
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.XmlPullParserException;
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 @@ public final class PrintSpoolerService extends Service {

    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 @@ public final class PrintSpoolerService extends Service {
                        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 @@ public final class PrintSpoolerService extends Service {
                mHandlerCaller.executeOrSendMessage(message);

                printJob.setCreationTime(System.currentTimeMillis());
                synchronized (mLock) {
                    mPersistanceManager.writeStateLocked();
                }
            }

            @Override
@@ -225,12 +227,40 @@ public final class PrintSpoolerService extends Service {
            }

            @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 @@ public final class PrintSpoolerService extends Service {

                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 final class PrintSpoolerService extends Service {
    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 @@ public final class PrintSpoolerService extends Service {
                } 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 final class PrintSpoolerService extends Service {
    }

    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 @@ public final class PrintSpoolerService extends Service {
        }
    }

    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 @@ public final class PrintSpoolerService extends Service {
        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 @@ public final class PrintSpoolerService extends Service {
                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 @@ public final class PrintSpoolerService extends Service {
                    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);
            }
        }
+14 −42
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.print.IPrintSpoolerCallbacks;
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 @@ final class RemotePrintSpooler {
    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 @@ final class RemotePrintSpooler {
        }
    }

    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 @@ final class RemotePrintSpooler {
                    .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 @@ final class RemotePrintSpooler {
        }
    }

    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 @@ final class RemotePrintSpooler {
        }

        @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);
                }
+139 −41
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.os.Looper;
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.ArrayMap;
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 com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks;
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 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
    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 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
    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 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
    }

    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 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
    }

    @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 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
            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 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
        }
    }

    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 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
                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();
                    }
                }
            }
        }
    }
}