UserState.java revision e8ae15482eef7617494cb3187899983e27cb8a35
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.print;
18
19import static android.content.pm.PackageManager.GET_META_DATA;
20import static android.content.pm.PackageManager.GET_SERVICES;
21import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
22
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.app.PendingIntent;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentSender;
30import android.content.pm.ParceledListSlice;
31import android.content.pm.ResolveInfo;
32import android.graphics.drawable.Icon;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Binder;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.IBinder;
39import android.os.IBinder.DeathRecipient;
40import android.os.Looper;
41import android.os.Message;
42import android.os.RemoteCallbackList;
43import android.os.RemoteException;
44import android.os.UserHandle;
45import android.print.IPrintDocumentAdapter;
46import android.print.IPrintJobStateChangeListener;
47import android.print.IPrintServicesChangeListener;
48import android.print.IPrinterDiscoveryObserver;
49import android.print.PrintAttributes;
50import android.print.PrintJobId;
51import android.print.PrintJobInfo;
52import android.print.PrintManager;
53import android.print.PrinterId;
54import android.print.PrinterInfo;
55import android.printservice.PrintServiceInfo;
56import android.provider.DocumentsContract;
57import android.provider.Settings;
58import android.text.TextUtils;
59import android.text.TextUtils.SimpleStringSplitter;
60import android.util.ArrayMap;
61import android.util.ArraySet;
62import android.util.Log;
63import android.util.Slog;
64import android.util.SparseArray;
65
66import com.android.internal.R;
67import com.android.internal.os.BackgroundThread;
68import com.android.internal.os.SomeArgs;
69import com.android.server.print.RemotePrintService.PrintServiceCallbacks;
70import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks;
71
72import java.io.FileDescriptor;
73import java.io.PrintWriter;
74import java.util.ArrayList;
75import java.util.Collections;
76import java.util.HashSet;
77import java.util.Iterator;
78import java.util.List;
79import java.util.Map;
80import java.util.Set;
81
82/**
83 * Represents the print state for a user.
84 */
85final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
86
87    private static final String LOG_TAG = "UserState";
88
89    private static final boolean DEBUG = false;
90
91    private static final char COMPONENT_NAME_SEPARATOR = ':';
92
93    private final SimpleStringSplitter mStringColonSplitter =
94            new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
95
96    private final Intent mQueryIntent =
97            new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
98
99    private final ArrayMap<ComponentName, RemotePrintService> mActiveServices =
100            new ArrayMap<ComponentName, RemotePrintService>();
101
102    private final List<PrintServiceInfo> mInstalledServices =
103            new ArrayList<PrintServiceInfo>();
104
105    private final Set<ComponentName> mDisabledServices =
106            new ArraySet<ComponentName>();
107
108    private final PrintJobForAppCache mPrintJobForAppCache =
109            new PrintJobForAppCache();
110
111    private final Object mLock;
112
113    private final Context mContext;
114
115    private final int mUserId;
116
117    private final RemotePrintSpooler mSpooler;
118
119    private final Handler mHandler;
120
121    private PrinterDiscoverySessionMediator mPrinterDiscoverySession;
122
123    private List<PrintJobStateChangeListenerRecord> mPrintJobStateChangeListenerRecords;
124
125    private List<PrintServicesChangeListenerRecord> mPrintServicesChangeListenerRecords;
126
127    private boolean mDestroyed;
128
129    public UserState(Context context, int userId, Object lock, boolean lowPriority) {
130        mContext = context;
131        mUserId = userId;
132        mLock = lock;
133        mSpooler = new RemotePrintSpooler(context, userId, lowPriority, this);
134        mHandler = new UserStateHandler(context.getMainLooper());
135
136        synchronized (mLock) {
137            readInstalledPrintServicesLocked();
138            upgradePersistentStateIfNeeded();
139            readDisabledPrintServicesLocked();
140
141            // Some print services might have gotten installed before the User State came up
142            prunePrintServices();
143
144            onConfigurationChangedLocked();
145        }
146    }
147
148    public void increasePriority() {
149        mSpooler.increasePriority();
150    }
151
152    @Override
153    public void onPrintJobQueued(PrintJobInfo printJob) {
154        final RemotePrintService service;
155        synchronized (mLock) {
156            throwIfDestroyedLocked();
157            ComponentName printServiceName = printJob.getPrinterId().getServiceName();
158            service = mActiveServices.get(printServiceName);
159        }
160        if (service != null) {
161            service.onPrintJobQueued(printJob);
162        } else {
163            // The service for the job is no longer enabled, so just
164            // fail the job with the appropriate message.
165            mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
166                    mContext.getString(R.string.reason_service_unavailable));
167        }
168    }
169
170    @Override
171    public void onAllPrintJobsForServiceHandled(ComponentName printService) {
172        final RemotePrintService service;
173        synchronized (mLock) {
174            throwIfDestroyedLocked();
175            service = mActiveServices.get(printService);
176        }
177        if (service != null) {
178            service.onAllPrintJobsHandled();
179        }
180    }
181
182    public void removeObsoletePrintJobs() {
183        mSpooler.removeObsoletePrintJobs();
184    }
185
186    @SuppressWarnings("deprecation")
187    public Bundle print(@NonNull String printJobName, @NonNull IPrintDocumentAdapter adapter,
188            @Nullable PrintAttributes attributes, @NonNull String packageName, int appId) {
189        // Create print job place holder.
190        final PrintJobInfo printJob = new PrintJobInfo();
191        printJob.setId(new PrintJobId());
192        printJob.setAppId(appId);
193        printJob.setLabel(printJobName);
194        printJob.setAttributes(attributes);
195        printJob.setState(PrintJobInfo.STATE_CREATED);
196        printJob.setCopies(1);
197        printJob.setCreationTime(System.currentTimeMillis());
198
199        // Track this job so we can forget it when the creator dies.
200        if (!mPrintJobForAppCache.onPrintJobCreated(adapter.asBinder(), appId,
201                printJob)) {
202            // Not adding a print job means the client is dead - done.
203            return null;
204        }
205
206        // Spin the spooler to add the job and show the config UI.
207        new AsyncTask<Void, Void, Void>() {
208            @Override
209            protected Void doInBackground(Void... params) {
210                mSpooler.createPrintJob(printJob);
211                return null;
212            }
213        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
214
215        final long identity = Binder.clearCallingIdentity();
216        try {
217            Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG);
218            intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
219            intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
220            intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
221            intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName);
222
223            IntentSender intentSender = PendingIntent.getActivityAsUser(
224                    mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
225                    | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
226                    null, new UserHandle(mUserId)) .getIntentSender();
227
228            Bundle result = new Bundle();
229            result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
230            result.putParcelable(PrintManager.EXTRA_PRINT_DIALOG_INTENT, intentSender);
231
232            return result;
233        } finally {
234            Binder.restoreCallingIdentity(identity);
235        }
236    }
237
238    public List<PrintJobInfo> getPrintJobInfos(int appId) {
239        List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId);
240        // Note that the print spooler is not storing print jobs that
241        // are in a terminal state as it is non-trivial to properly update
242        // the spooler state for when to forget print jobs in terminal state.
243        // Therefore, we fuse the cached print jobs for running apps (some
244        // jobs are in a terminal state) with the ones that the print
245        // spooler knows about (some jobs are being processed).
246        ArrayMap<PrintJobId, PrintJobInfo> result =
247                new ArrayMap<PrintJobId, PrintJobInfo>();
248
249        // Add the cached print jobs for running apps.
250        final int cachedPrintJobCount = cachedPrintJobs.size();
251        for (int i = 0; i < cachedPrintJobCount; i++) {
252            PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i);
253            result.put(cachedPrintJob.getId(), cachedPrintJob);
254            // Strip out the tag and the advanced print options.
255            // They are visible only to print services.
256            cachedPrintJob.setTag(null);
257            cachedPrintJob.setAdvancedOptions(null);
258        }
259
260        // Add everything else the spooler knows about.
261        List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(null,
262                PrintJobInfo.STATE_ANY, appId);
263        if (printJobs != null) {
264            final int printJobCount = printJobs.size();
265            for (int i = 0; i < printJobCount; i++) {
266                PrintJobInfo printJob = printJobs.get(i);
267                result.put(printJob.getId(), printJob);
268                // Strip out the tag and the advanced print options.
269                // They are visible only to print services.
270                printJob.setTag(null);
271                printJob.setAdvancedOptions(null);
272            }
273        }
274
275        return new ArrayList<PrintJobInfo>(result.values());
276    }
277
278    public PrintJobInfo getPrintJobInfo(@NonNull PrintJobId printJobId, int appId) {
279        PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId);
280        if (printJob == null) {
281            printJob = mSpooler.getPrintJobInfo(printJobId, appId);
282        }
283        if (printJob != null) {
284            // Strip out the tag and the advanced print options.
285            // They are visible only to print services.
286            printJob.setTag(null);
287            printJob.setAdvancedOptions(null);
288        }
289        return printJob;
290    }
291
292    /**
293     * Get the custom icon for a printer. If the icon is not cached, the icon is
294     * requested asynchronously. Once it is available the printer is updated.
295     *
296     * @param printerId the id of the printer the icon should be loaded for
297     * @return the custom icon to be used for the printer or null if the icon is
298     *         not yet available
299     * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
300     */
301    public @Nullable Icon getCustomPrinterIcon(@NonNull PrinterId printerId) {
302        Icon icon = mSpooler.getCustomPrinterIcon(printerId);
303
304        if (icon == null) {
305            RemotePrintService service = mActiveServices.get(printerId.getServiceName());
306            if (service != null) {
307                service.requestCustomPrinterIcon(printerId);
308            }
309        }
310
311        return icon;
312    }
313
314    public void cancelPrintJob(@NonNull PrintJobId printJobId, int appId) {
315        PrintJobInfo printJobInfo = mSpooler.getPrintJobInfo(printJobId, appId);
316        if (printJobInfo == null) {
317            return;
318        }
319
320        // Take a note that we are trying to cancel the job.
321        mSpooler.setPrintJobCancelling(printJobId, true);
322
323        if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
324            PrinterId printerId = printJobInfo.getPrinterId();
325
326            if (printerId != null) {
327                ComponentName printServiceName = printerId.getServiceName();
328                RemotePrintService printService = null;
329                synchronized (mLock) {
330                    printService = mActiveServices.get(printServiceName);
331                }
332                if (printService == null) {
333                    return;
334                }
335                printService.onRequestCancelPrintJob(printJobInfo);
336            }
337        } else {
338            // If the print job is failed we do not need cooperation
339            // from the print service.
340            mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED, null);
341        }
342    }
343
344    public void restartPrintJob(@NonNull PrintJobId printJobId, int appId) {
345        PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId);
346        if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
347            return;
348        }
349        mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null);
350    }
351
352    public @Nullable List<PrintServiceInfo> getPrintServices(int selectionFlags) {
353        synchronized (mLock) {
354            List<PrintServiceInfo> selectedServices = null;
355            final int installedServiceCount = mInstalledServices.size();
356            for (int i = 0; i < installedServiceCount; i++) {
357                PrintServiceInfo installedService = mInstalledServices.get(i);
358
359                ComponentName componentName = new ComponentName(
360                        installedService.getResolveInfo().serviceInfo.packageName,
361                        installedService.getResolveInfo().serviceInfo.name);
362
363                // Update isEnabled under the same lock the final returned list is created
364                installedService.setIsEnabled(mActiveServices.containsKey(componentName));
365
366                if (installedService.isEnabled()) {
367                    if ((selectionFlags & PrintManager.ENABLED_SERVICES) == 0) {
368                        continue;
369                    }
370                } else {
371                    if ((selectionFlags & PrintManager.DISABLED_SERVICES) == 0) {
372                        continue;
373                    }
374                }
375
376                if (selectedServices == null) {
377                    selectedServices = new ArrayList<>();
378                }
379                selectedServices.add(installedService);
380            }
381            return selectedServices;
382        }
383    }
384
385    public void setPrintServiceEnabled(@NonNull ComponentName serviceName, boolean isEnabled) {
386        synchronized (mLock) {
387            boolean isChanged = false;
388            if (isEnabled) {
389                isChanged = mDisabledServices.remove(serviceName);
390            } else {
391                // Make sure to only disable services that are currently installed
392                final int numServices = mInstalledServices.size();
393                for (int i = 0; i < numServices; i++) {
394                    PrintServiceInfo service = mInstalledServices.get(i);
395
396                    if (service.getComponentName().equals(serviceName)) {
397                        mDisabledServices.add(serviceName);
398                        isChanged = true;
399                        break;
400                    }
401                }
402            }
403
404            if (isChanged) {
405                writeDisabledPrintServicesLocked(mDisabledServices);
406
407                onConfigurationChangedLocked();
408            }
409        }
410    }
411
412    public void createPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) {
413        synchronized (mLock) {
414            throwIfDestroyedLocked();
415
416            if (mPrinterDiscoverySession == null) {
417                mSpooler.clearCustomPrinterIconCache();
418
419                // If we do not have a session, tell all service to create one.
420                mPrinterDiscoverySession = new PrinterDiscoverySessionMediator(mContext) {
421                    @Override
422                    public void onDestroyed() {
423                        mPrinterDiscoverySession = null;
424                    }
425                };
426                // Add the observer to the brand new session.
427                mPrinterDiscoverySession.addObserverLocked(observer);
428            } else {
429                // If services have created session, just add the observer.
430                mPrinterDiscoverySession.addObserverLocked(observer);
431            }
432        }
433    }
434
435    public void destroyPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) {
436        synchronized (mLock) {
437            // Already destroyed - nothing to do.
438            if (mPrinterDiscoverySession == null) {
439                return;
440            }
441            // Remove this observer.
442            mPrinterDiscoverySession.removeObserverLocked(observer);
443        }
444    }
445
446    public void startPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer,
447            @Nullable List<PrinterId> printerIds) {
448        synchronized (mLock) {
449            throwIfDestroyedLocked();
450
451            // No session - nothing to do.
452            if (mPrinterDiscoverySession == null) {
453                return;
454            }
455            // Kick of discovery.
456            mPrinterDiscoverySession.startPrinterDiscoveryLocked(observer,
457                    printerIds);
458        }
459    }
460
461    public void stopPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer) {
462        synchronized (mLock) {
463            throwIfDestroyedLocked();
464
465            // No session - nothing to do.
466            if (mPrinterDiscoverySession == null) {
467                return;
468            }
469            // Kick of discovery.
470            mPrinterDiscoverySession.stopPrinterDiscoveryLocked(observer);
471        }
472    }
473
474    public void validatePrinters(@NonNull List<PrinterId> printerIds) {
475        synchronized (mLock) {
476            throwIfDestroyedLocked();
477            // No services - nothing to do.
478            if (mActiveServices.isEmpty()) {
479                return;
480            }
481            // No session - nothing to do.
482            if (mPrinterDiscoverySession == null) {
483                return;
484            }
485            // Request an updated.
486            mPrinterDiscoverySession.validatePrintersLocked(printerIds);
487        }
488    }
489
490    public void startPrinterStateTracking(@NonNull PrinterId printerId) {
491        synchronized (mLock) {
492            throwIfDestroyedLocked();
493            // No services - nothing to do.
494            if (mActiveServices.isEmpty()) {
495                return;
496            }
497            // No session - nothing to do.
498            if (mPrinterDiscoverySession == null) {
499                return;
500            }
501            // Request start tracking the printer.
502            mPrinterDiscoverySession.startPrinterStateTrackingLocked(printerId);
503        }
504    }
505
506    public void stopPrinterStateTracking(PrinterId printerId) {
507        synchronized (mLock) {
508            throwIfDestroyedLocked();
509            // No services - nothing to do.
510            if (mActiveServices.isEmpty()) {
511                return;
512            }
513            // No session - nothing to do.
514            if (mPrinterDiscoverySession == null) {
515                return;
516            }
517            // Request stop tracking the printer.
518            mPrinterDiscoverySession.stopPrinterStateTrackingLocked(printerId);
519        }
520    }
521
522    public void addPrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener,
523            int appId) throws RemoteException {
524        synchronized (mLock) {
525            throwIfDestroyedLocked();
526            if (mPrintJobStateChangeListenerRecords == null) {
527                mPrintJobStateChangeListenerRecords =
528                        new ArrayList<PrintJobStateChangeListenerRecord>();
529            }
530            mPrintJobStateChangeListenerRecords.add(
531                    new PrintJobStateChangeListenerRecord(listener, appId) {
532                @Override
533                public void onBinderDied() {
534                    mPrintJobStateChangeListenerRecords.remove(this);
535                }
536            });
537        }
538    }
539
540    public void removePrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener) {
541        synchronized (mLock) {
542            throwIfDestroyedLocked();
543            if (mPrintJobStateChangeListenerRecords == null) {
544                return;
545            }
546            final int recordCount = mPrintJobStateChangeListenerRecords.size();
547            for (int i = 0; i < recordCount; i++) {
548                PrintJobStateChangeListenerRecord record =
549                        mPrintJobStateChangeListenerRecords.get(i);
550                if (record.listener.asBinder().equals(listener.asBinder())) {
551                    mPrintJobStateChangeListenerRecords.remove(i);
552                    break;
553                }
554            }
555            if (mPrintJobStateChangeListenerRecords.isEmpty()) {
556                mPrintJobStateChangeListenerRecords = null;
557            }
558        }
559    }
560
561    public void addPrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener)
562            throws RemoteException {
563        synchronized (mLock) {
564            throwIfDestroyedLocked();
565            if (mPrintServicesChangeListenerRecords == null) {
566                mPrintServicesChangeListenerRecords = new ArrayList<>();
567            }
568            mPrintServicesChangeListenerRecords.add(
569                    new PrintServicesChangeListenerRecord(listener) {
570                        @Override
571                        public void onBinderDied() {
572                            mPrintServicesChangeListenerRecords.remove(this);
573                        }
574                    });
575        }
576    }
577
578    public void removePrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) {
579        synchronized (mLock) {
580            throwIfDestroyedLocked();
581            if (mPrintServicesChangeListenerRecords == null) {
582                return;
583            }
584            final int recordCount = mPrintServicesChangeListenerRecords.size();
585            for (int i = 0; i < recordCount; i++) {
586                PrintServicesChangeListenerRecord record =
587                        mPrintServicesChangeListenerRecords.get(i);
588                if (record.listener.asBinder().equals(listener.asBinder())) {
589                    mPrintServicesChangeListenerRecords.remove(i);
590                    break;
591                }
592            }
593            if (mPrintServicesChangeListenerRecords.isEmpty()) {
594                mPrintServicesChangeListenerRecords = null;
595            }
596        }
597    }
598
599    @Override
600    public void onPrintJobStateChanged(PrintJobInfo printJob) {
601        mPrintJobForAppCache.onPrintJobStateChanged(printJob);
602        mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_JOB_STATE_CHANGED,
603                printJob.getAppId(), 0, printJob.getId()).sendToTarget();
604    }
605
606    public void onPrintServicesChanged() {
607        mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_SERVICES_CHANGED).sendToTarget();
608    }
609
610    @Override
611    public void onPrintersAdded(List<PrinterInfo> printers) {
612        synchronized (mLock) {
613            throwIfDestroyedLocked();
614            // No services - nothing to do.
615            if (mActiveServices.isEmpty()) {
616                return;
617            }
618            // No session - nothing to do.
619            if (mPrinterDiscoverySession == null) {
620                return;
621            }
622            mPrinterDiscoverySession.onPrintersAddedLocked(printers);
623        }
624    }
625
626    @Override
627    public void onPrintersRemoved(List<PrinterId> printerIds) {
628        synchronized (mLock) {
629            throwIfDestroyedLocked();
630            // No services - nothing to do.
631            if (mActiveServices.isEmpty()) {
632                return;
633            }
634            // No session - nothing to do.
635            if (mPrinterDiscoverySession == null) {
636                return;
637            }
638            mPrinterDiscoverySession.onPrintersRemovedLocked(printerIds);
639        }
640    }
641
642    @Override
643    public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) {
644        synchronized (mLock) {
645            throwIfDestroyedLocked();
646
647            // No session - nothing to do.
648            if (mPrinterDiscoverySession == null) {
649                return;
650            }
651            mSpooler.onCustomPrinterIconLoaded(printerId, icon);
652            mPrinterDiscoverySession.onCustomPrinterIconLoadedLocked(printerId);
653        }
654    }
655
656    @Override
657    public void onServiceDied(RemotePrintService service) {
658        synchronized (mLock) {
659            throwIfDestroyedLocked();
660            // No services - nothing to do.
661            if (mActiveServices.isEmpty()) {
662                return;
663            }
664            // Fail all print jobs.
665            failActivePrintJobsForService(service.getComponentName());
666            service.onAllPrintJobsHandled();
667            // No session - nothing to do.
668            if (mPrinterDiscoverySession == null) {
669                return;
670            }
671            mPrinterDiscoverySession.onServiceDiedLocked(service);
672        }
673    }
674
675    public void updateIfNeededLocked() {
676        throwIfDestroyedLocked();
677        if (readConfigurationLocked()) {
678            onConfigurationChangedLocked();
679        }
680    }
681
682    public void destroyLocked() {
683        throwIfDestroyedLocked();
684        mSpooler.destroy();
685        for (RemotePrintService service : mActiveServices.values()) {
686            service.destroy();
687        }
688        mActiveServices.clear();
689        mInstalledServices.clear();
690        mDisabledServices.clear();
691        if (mPrinterDiscoverySession != null) {
692            mPrinterDiscoverySession.destroyLocked();
693            mPrinterDiscoverySession = null;
694        }
695        mDestroyed = true;
696    }
697
698    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String prefix) {
699        pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":");
700        pw.println();
701
702        String tab = "  ";
703
704        pw.append(prefix).append(tab).append("installed services:").println();
705        final int installedServiceCount = mInstalledServices.size();
706        for (int i = 0; i < installedServiceCount; i++) {
707            PrintServiceInfo installedService = mInstalledServices.get(i);
708            String installedServicePrefix = prefix + tab + tab;
709            pw.append(installedServicePrefix).append("service:").println();
710            ResolveInfo resolveInfo = installedService.getResolveInfo();
711            ComponentName componentName = new ComponentName(
712                    resolveInfo.serviceInfo.packageName,
713                    resolveInfo.serviceInfo.name);
714            pw.append(installedServicePrefix).append(tab).append("componentName=")
715                    .append(componentName.flattenToString()).println();
716            pw.append(installedServicePrefix).append(tab).append("settingsActivity=")
717                    .append(installedService.getSettingsActivityName()).println();
718            pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=")
719                    .append(installedService.getAddPrintersActivityName()).println();
720            pw.append(installedServicePrefix).append(tab).append("avancedOptionsActivity=")
721                   .append(installedService.getAdvancedOptionsActivityName()).println();
722        }
723
724        pw.append(prefix).append(tab).append("disabled services:").println();
725        for (ComponentName disabledService : mDisabledServices) {
726            String disabledServicePrefix = prefix + tab + tab;
727            pw.append(disabledServicePrefix).append("service:").println();
728            pw.append(disabledServicePrefix).append(tab).append("componentName=")
729                    .append(disabledService.flattenToString());
730            pw.println();
731        }
732
733        pw.append(prefix).append(tab).append("active services:").println();
734        final int activeServiceCount = mActiveServices.size();
735        for (int i = 0; i < activeServiceCount; i++) {
736            RemotePrintService activeService = mActiveServices.valueAt(i);
737            activeService.dump(pw, prefix + tab + tab);
738            pw.println();
739        }
740
741        pw.append(prefix).append(tab).append("cached print jobs:").println();
742        mPrintJobForAppCache.dump(pw, prefix + tab + tab);
743
744        pw.append(prefix).append(tab).append("discovery mediator:").println();
745        if (mPrinterDiscoverySession != null) {
746            mPrinterDiscoverySession.dump(pw, prefix + tab + tab);
747        }
748
749        pw.append(prefix).append(tab).append("print spooler:").println();
750        mSpooler.dump(fd, pw, prefix + tab + tab);
751        pw.println();
752    }
753
754    private boolean readConfigurationLocked() {
755        boolean somethingChanged = false;
756        somethingChanged |= readInstalledPrintServicesLocked();
757        somethingChanged |= readDisabledPrintServicesLocked();
758        return somethingChanged;
759    }
760
761    private boolean readInstalledPrintServicesLocked() {
762        Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>();
763
764        List<ResolveInfo> installedServices = mContext.getPackageManager()
765                .queryIntentServicesAsUser(mQueryIntent,
766                        GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING, mUserId);
767
768        final int installedCount = installedServices.size();
769        for (int i = 0, count = installedCount; i < count; i++) {
770            ResolveInfo installedService = installedServices.get(i);
771            if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals(
772                    installedService.serviceInfo.permission)) {
773                ComponentName serviceName = new ComponentName(
774                        installedService.serviceInfo.packageName,
775                        installedService.serviceInfo.name);
776                Slog.w(LOG_TAG, "Skipping print service "
777                        + serviceName.flattenToShortString()
778                        + " since it does not require permission "
779                        + android.Manifest.permission.BIND_PRINT_SERVICE);
780                continue;
781            }
782            tempPrintServices.add(PrintServiceInfo.create(installedService, mContext));
783        }
784
785        boolean someServiceChanged = false;
786
787        if (tempPrintServices.size() != mInstalledServices.size()) {
788            someServiceChanged = true;
789        } else {
790            for (PrintServiceInfo newService: tempPrintServices) {
791                final int oldServiceIndex = mInstalledServices.indexOf(newService);
792                if (oldServiceIndex < 0) {
793                    someServiceChanged = true;
794                    break;
795                }
796                // PrintServiceInfo#equals compares only the id not all members,
797                // so we are also comparing the members coming from meta-data.
798                PrintServiceInfo oldService = mInstalledServices.get(oldServiceIndex);
799                if (!TextUtils.equals(oldService.getAddPrintersActivityName(),
800                            newService.getAddPrintersActivityName())
801                        || !TextUtils.equals(oldService.getAdvancedOptionsActivityName(),
802                                newService.getAdvancedOptionsActivityName())
803                        || !TextUtils.equals(oldService.getSettingsActivityName(),
804                                newService.getSettingsActivityName())) {
805                    someServiceChanged = true;
806                    break;
807                }
808            }
809        }
810
811        if (someServiceChanged) {
812            mInstalledServices.clear();
813            mInstalledServices.addAll(tempPrintServices);
814            return true;
815        }
816
817        return false;
818    }
819
820    /**
821     * Update persistent state from a previous version of Android.
822     */
823    private void upgradePersistentStateIfNeeded() {
824        String enabledSettingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
825                Settings.Secure.ENABLED_PRINT_SERVICES, mUserId);
826
827        // Pre N we store the enabled services, in N and later we store the disabled services.
828        // Hence if enabledSettingValue is still set, we need to upgrade.
829        if (enabledSettingValue != null) {
830            Set<ComponentName> enabledServiceNameSet = new HashSet<ComponentName>();
831            readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
832                    enabledServiceNameSet);
833
834            ArraySet<ComponentName> disabledServices = new ArraySet<>();
835            final int numInstalledServices = mInstalledServices.size();
836            for (int i = 0; i < numInstalledServices; i++) {
837                ComponentName serviceName = mInstalledServices.get(i).getComponentName();
838                if (!enabledServiceNameSet.contains(serviceName)) {
839                    disabledServices.add(serviceName);
840                }
841            }
842
843            writeDisabledPrintServicesLocked(disabledServices);
844
845            // We won't needed ENABLED_PRINT_SERVICES anymore, set to null to prevent upgrade to run
846            // again.
847            Settings.Secure.putStringForUser(mContext.getContentResolver(),
848                    Settings.Secure.ENABLED_PRINT_SERVICES, null, mUserId);
849        }
850    }
851
852    /**
853     * Read the set of disabled print services from the secure settings.
854     *
855     * @return true if the state changed.
856     */
857    private boolean readDisabledPrintServicesLocked() {
858        Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>();
859        readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES,
860                tempDisabledServiceNameSet);
861        if (!tempDisabledServiceNameSet.equals(mDisabledServices)) {
862            mDisabledServices.clear();
863            mDisabledServices.addAll(tempDisabledServiceNameSet);
864            return true;
865        }
866        return false;
867    }
868
869    private void readPrintServicesFromSettingLocked(String setting,
870            Set<ComponentName> outServiceNames) {
871        String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
872                setting, mUserId);
873        if (!TextUtils.isEmpty(settingValue)) {
874            TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
875            splitter.setString(settingValue);
876            while (splitter.hasNext()) {
877                String string = splitter.next();
878                if (TextUtils.isEmpty(string)) {
879                    continue;
880                }
881                ComponentName componentName = ComponentName.unflattenFromString(string);
882                if (componentName != null) {
883                    outServiceNames.add(componentName);
884                }
885            }
886        }
887    }
888
889    /**
890     * Persist the disabled print services to the secure settings.
891     */
892    private void writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices) {
893        StringBuilder builder = new StringBuilder();
894        for (ComponentName componentName : disabledServices) {
895            if (builder.length() > 0) {
896                builder.append(COMPONENT_NAME_SEPARATOR);
897            }
898            builder.append(componentName.flattenToShortString());
899        }
900        Settings.Secure.putStringForUser(mContext.getContentResolver(),
901                Settings.Secure.DISABLED_PRINT_SERVICES, builder.toString(), mUserId);
902    }
903
904    /**
905     * Get the {@link ComponentName names} of the installed print services
906     *
907     * @return The names of the installed print services
908     */
909    private ArrayList<ComponentName> getInstalledComponents() {
910        ArrayList<ComponentName> installedComponents = new ArrayList<ComponentName>();
911
912        final int installedCount = mInstalledServices.size();
913        for (int i = 0; i < installedCount; i++) {
914            ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo();
915            ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName,
916                    resolveInfo.serviceInfo.name);
917
918            installedComponents.add(serviceName);
919        }
920
921        return installedComponents;
922    }
923
924    /**
925     * Prune persistent state if a print service was uninstalled
926     */
927    public void prunePrintServices() {
928        synchronized (mLock) {
929            ArrayList<ComponentName> installedComponents = getInstalledComponents();
930
931            // Remove unnecessary entries from persistent state "disabled services"
932            boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents);
933            if (disabledServicesUninstalled) {
934                writeDisabledPrintServicesLocked(mDisabledServices);
935            }
936
937            // Remove unnecessary entries from persistent state "approved services"
938            mSpooler.pruneApprovedPrintServices(installedComponents);
939        }
940    }
941
942    private void onConfigurationChangedLocked() {
943        ArrayList<ComponentName> installedComponents = getInstalledComponents();
944
945        final int installedCount = installedComponents.size();
946        for (int i = 0; i < installedCount; i++) {
947            ComponentName serviceName = installedComponents.get(i);
948
949            if (!mDisabledServices.contains(serviceName)) {
950                if (!mActiveServices.containsKey(serviceName)) {
951                    RemotePrintService service = new RemotePrintService(
952                            mContext, serviceName, mUserId, mSpooler, this);
953                    addServiceLocked(service);
954                }
955            } else {
956                RemotePrintService service = mActiveServices.remove(serviceName);
957                if (service != null) {
958                    removeServiceLocked(service);
959                }
960            }
961        }
962
963        Iterator<Map.Entry<ComponentName, RemotePrintService>> iterator =
964                mActiveServices.entrySet().iterator();
965        while (iterator.hasNext()) {
966            Map.Entry<ComponentName, RemotePrintService> entry = iterator.next();
967            ComponentName serviceName = entry.getKey();
968            RemotePrintService service = entry.getValue();
969            if (!installedComponents.contains(serviceName)) {
970                removeServiceLocked(service);
971                iterator.remove();
972            }
973        }
974
975        onPrintServicesChanged();
976    }
977
978    private void addServiceLocked(RemotePrintService service) {
979        mActiveServices.put(service.getComponentName(), service);
980        if (mPrinterDiscoverySession != null) {
981            mPrinterDiscoverySession.onServiceAddedLocked(service);
982        }
983    }
984
985    private void removeServiceLocked(RemotePrintService service) {
986        // Fail all print jobs.
987        failActivePrintJobsForService(service.getComponentName());
988        // If discovery is in progress, tear down the service.
989        if (mPrinterDiscoverySession != null) {
990            mPrinterDiscoverySession.onServiceRemovedLocked(service);
991        } else {
992            // Otherwise, just destroy it.
993            service.destroy();
994        }
995    }
996
997    private void failActivePrintJobsForService(final ComponentName serviceName) {
998        // Makes sure all active print jobs are failed since the service
999        // just died. Do this off the main thread since we do to allow
1000        // calls into the spooler on the main thread.
1001        if (Looper.getMainLooper().isCurrentThread()) {
1002            BackgroundThread.getHandler().post(new Runnable() {
1003                @Override
1004                public void run() {
1005                    failScheduledPrintJobsForServiceInternal(serviceName);
1006                }
1007            });
1008        } else {
1009            failScheduledPrintJobsForServiceInternal(serviceName);
1010        }
1011    }
1012
1013    private void failScheduledPrintJobsForServiceInternal(ComponentName serviceName) {
1014        List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(serviceName,
1015                PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
1016        if (printJobs == null) {
1017            return;
1018        }
1019        final long identity = Binder.clearCallingIdentity();
1020        try {
1021            final int printJobCount = printJobs.size();
1022            for (int i = 0; i < printJobCount; i++) {
1023                PrintJobInfo printJob = printJobs.get(i);
1024                mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
1025                        mContext.getString(R.string.reason_service_unavailable));
1026            }
1027        } finally {
1028            Binder.restoreCallingIdentity(identity);
1029        }
1030    }
1031
1032    private void throwIfDestroyedLocked() {
1033        if (mDestroyed) {
1034            throw new IllegalStateException("Cannot interact with a destroyed instance.");
1035        }
1036    }
1037
1038    private void handleDispatchPrintJobStateChanged(PrintJobId printJobId, int appId) {
1039        final List<PrintJobStateChangeListenerRecord> records;
1040        synchronized (mLock) {
1041            if (mPrintJobStateChangeListenerRecords == null) {
1042                return;
1043            }
1044            records = new ArrayList<PrintJobStateChangeListenerRecord>(
1045                    mPrintJobStateChangeListenerRecords);
1046        }
1047        final int recordCount = records.size();
1048        for (int i = 0; i < recordCount; i++) {
1049            PrintJobStateChangeListenerRecord record = records.get(i);
1050            if (record.appId == PrintManager.APP_ID_ANY
1051                    || record.appId == appId)
1052            try {
1053                record.listener.onPrintJobStateChanged(printJobId);
1054            } catch (RemoteException re) {
1055                Log.e(LOG_TAG, "Error notifying for print job state change", re);
1056            }
1057        }
1058    }
1059
1060    private void handleDispatchPrintServicesChanged() {
1061        final List<PrintServicesChangeListenerRecord> records;
1062        synchronized (mLock) {
1063            if (mPrintServicesChangeListenerRecords == null) {
1064                return;
1065            }
1066            records = new ArrayList<>(mPrintServicesChangeListenerRecords);
1067        }
1068        final int recordCount = records.size();
1069        for (int i = 0; i < recordCount; i++) {
1070            PrintServicesChangeListenerRecord record = records.get(i);
1071
1072            try {
1073                record.listener.onPrintServicesChanged();;
1074            } catch (RemoteException re) {
1075                Log.e(LOG_TAG, "Error notifying for print services change", re);
1076            }
1077        }
1078    }
1079
1080    private final class UserStateHandler extends Handler {
1081        public static final int MSG_DISPATCH_PRINT_JOB_STATE_CHANGED = 1;
1082        public static final int MSG_DISPATCH_PRINT_SERVICES_CHANGED = 2;
1083
1084        public UserStateHandler(Looper looper) {
1085            super(looper, null, false);
1086        }
1087
1088        @Override
1089        public void handleMessage(Message message) {
1090            switch (message.what) {
1091                case MSG_DISPATCH_PRINT_JOB_STATE_CHANGED:
1092                    PrintJobId printJobId = (PrintJobId) message.obj;
1093                    final int appId = message.arg1;
1094                    handleDispatchPrintJobStateChanged(printJobId, appId);
1095                    break;
1096                case MSG_DISPATCH_PRINT_SERVICES_CHANGED:
1097                    handleDispatchPrintServicesChanged();
1098                    break;
1099                default:
1100                    // not reached
1101            }
1102        }
1103    }
1104
1105    private abstract class PrintJobStateChangeListenerRecord implements DeathRecipient {
1106        @NonNull final IPrintJobStateChangeListener listener;
1107        final int appId;
1108
1109        public PrintJobStateChangeListenerRecord(@NonNull IPrintJobStateChangeListener listener,
1110                int appId) throws RemoteException {
1111            this.listener = listener;
1112            this.appId = appId;
1113            listener.asBinder().linkToDeath(this, 0);
1114        }
1115
1116        @Override
1117        public void binderDied() {
1118            listener.asBinder().unlinkToDeath(this, 0);
1119            onBinderDied();
1120        }
1121
1122        public abstract void onBinderDied();
1123    }
1124
1125    private abstract class PrintServicesChangeListenerRecord implements DeathRecipient {
1126        @NonNull final IPrintServicesChangeListener listener;
1127
1128        public PrintServicesChangeListenerRecord(@NonNull IPrintServicesChangeListener listener) throws RemoteException {
1129            this.listener = listener;
1130            listener.asBinder().linkToDeath(this, 0);
1131        }
1132
1133        @Override
1134        public void binderDied() {
1135            listener.asBinder().unlinkToDeath(this, 0);
1136            onBinderDied();
1137        }
1138
1139        public abstract void onBinderDied();
1140    }
1141
1142    private class PrinterDiscoverySessionMediator {
1143        private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
1144                new ArrayMap<PrinterId, PrinterInfo>();
1145
1146        private final RemoteCallbackList<IPrinterDiscoveryObserver> mDiscoveryObservers =
1147                new RemoteCallbackList<IPrinterDiscoveryObserver>() {
1148            @Override
1149            public void onCallbackDied(IPrinterDiscoveryObserver observer) {
1150                synchronized (mLock) {
1151                    stopPrinterDiscoveryLocked(observer);
1152                    removeObserverLocked(observer);
1153                }
1154            }
1155        };
1156
1157        private final List<IBinder> mStartedPrinterDiscoveryTokens = new ArrayList<IBinder>();
1158
1159        private final List<PrinterId> mStateTrackedPrinters = new ArrayList<PrinterId>();
1160
1161        private final Handler mSessionHandler;
1162
1163        private boolean mIsDestroyed;
1164
1165        public PrinterDiscoverySessionMediator(Context context) {
1166            mSessionHandler = new SessionHandler(context.getMainLooper());
1167            // Kick off the session creation.
1168            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1169                    mActiveServices.values());
1170            mSessionHandler.obtainMessage(SessionHandler
1171                    .MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION, services)
1172                    .sendToTarget();
1173        }
1174
1175        public void addObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
1176            // Add the observer.
1177            mDiscoveryObservers.register(observer);
1178
1179            // Bring the added observer up to speed with the printers.
1180            if (!mPrinters.isEmpty()) {
1181                List<PrinterInfo> printers = new ArrayList<PrinterInfo>(mPrinters.values());
1182                SomeArgs args = SomeArgs.obtain();
1183                args.arg1 = observer;
1184                args.arg2 = printers;
1185                mSessionHandler.obtainMessage(SessionHandler.MSG_PRINTERS_ADDED,
1186                        args).sendToTarget();
1187            }
1188        }
1189
1190        public void removeObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
1191            // Remove the observer.
1192            mDiscoveryObservers.unregister(observer);
1193            // No one else observing - then kill it.
1194            if (mDiscoveryObservers.getRegisteredCallbackCount() == 0) {
1195                destroyLocked();
1196            }
1197        }
1198
1199        public final void startPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer,
1200                @Nullable List<PrinterId> priorityList) {
1201            if (mIsDestroyed) {
1202                Log.w(LOG_TAG, "Not starting dicovery - session destroyed");
1203                return;
1204            }
1205
1206            final boolean discoveryStarted = !mStartedPrinterDiscoveryTokens.isEmpty();
1207
1208            // Remember we got a start request to match with an end.
1209            mStartedPrinterDiscoveryTokens.add(observer.asBinder());
1210
1211            // If printer discovery is ongoing and the start request has a list
1212            // of printer to be checked, then we just request validating them.
1213            if (discoveryStarted && priorityList != null && !priorityList.isEmpty()) {
1214                validatePrinters(priorityList);
1215                return;
1216            }
1217
1218            // The service are already performing discovery - nothing to do.
1219            if (mStartedPrinterDiscoveryTokens.size() > 1) {
1220                return;
1221            }
1222
1223            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1224                    mActiveServices.values());
1225            SomeArgs args = SomeArgs.obtain();
1226            args.arg1 = services;
1227            args.arg2 = priorityList;
1228            mSessionHandler.obtainMessage(SessionHandler
1229                    .MSG_DISPATCH_START_PRINTER_DISCOVERY, args)
1230                    .sendToTarget();
1231        }
1232
1233        public final void stopPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer) {
1234            if (mIsDestroyed) {
1235                Log.w(LOG_TAG, "Not stopping dicovery - session destroyed");
1236                return;
1237            }
1238            // This one did not make an active discovery request - nothing to do.
1239            if (!mStartedPrinterDiscoveryTokens.remove(observer.asBinder())) {
1240                return;
1241            }
1242            // There are other interested observers - do not stop discovery.
1243            if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
1244                return;
1245            }
1246            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1247                    mActiveServices.values());
1248            mSessionHandler.obtainMessage(SessionHandler
1249                    .MSG_DISPATCH_STOP_PRINTER_DISCOVERY, services)
1250                    .sendToTarget();
1251        }
1252
1253        public void validatePrintersLocked(@NonNull List<PrinterId> printerIds) {
1254            if (mIsDestroyed) {
1255                Log.w(LOG_TAG, "Not validating pritners - session destroyed");
1256                return;
1257            }
1258
1259            List<PrinterId> remainingList = new ArrayList<PrinterId>(printerIds);
1260            while (!remainingList.isEmpty()) {
1261                Iterator<PrinterId> iterator = remainingList.iterator();
1262                // Gather the printers per service and request a validation.
1263                List<PrinterId> updateList = new ArrayList<PrinterId>();
1264                ComponentName serviceName = null;
1265                while (iterator.hasNext()) {
1266                    PrinterId printerId = iterator.next();
1267                    if (printerId != null) {
1268                        if (updateList.isEmpty()) {
1269                            updateList.add(printerId);
1270                            serviceName = printerId.getServiceName();
1271                            iterator.remove();
1272                        } else if (printerId.getServiceName().equals(serviceName)) {
1273                            updateList.add(printerId);
1274                            iterator.remove();
1275                        }
1276                    }
1277                }
1278                // Schedule a notification of the service.
1279                RemotePrintService service = mActiveServices.get(serviceName);
1280                if (service != null) {
1281                    SomeArgs args = SomeArgs.obtain();
1282                    args.arg1 = service;
1283                    args.arg2 = updateList;
1284                    mSessionHandler.obtainMessage(SessionHandler
1285                            .MSG_VALIDATE_PRINTERS, args)
1286                            .sendToTarget();
1287                }
1288            }
1289        }
1290
1291        public final void startPrinterStateTrackingLocked(@NonNull PrinterId printerId) {
1292            if (mIsDestroyed) {
1293                Log.w(LOG_TAG, "Not starting printer state tracking - session destroyed");
1294                return;
1295            }
1296            // If printer discovery is not started - nothing to do.
1297            if (mStartedPrinterDiscoveryTokens.isEmpty()) {
1298                return;
1299            }
1300            final boolean containedPrinterId = mStateTrackedPrinters.contains(printerId);
1301            // Keep track of the number of requests to track this one.
1302            mStateTrackedPrinters.add(printerId);
1303            // If we were tracking this printer - nothing to do.
1304            if (containedPrinterId) {
1305                return;
1306            }
1307            // No service - nothing to do.
1308            RemotePrintService service = mActiveServices.get(printerId.getServiceName());
1309            if (service == null) {
1310                return;
1311            }
1312            // Ask the service to start tracking.
1313            SomeArgs args = SomeArgs.obtain();
1314            args.arg1 = service;
1315            args.arg2 = printerId;
1316            mSessionHandler.obtainMessage(SessionHandler
1317                    .MSG_START_PRINTER_STATE_TRACKING, args)
1318                    .sendToTarget();
1319        }
1320
1321        public final void stopPrinterStateTrackingLocked(PrinterId printerId) {
1322            if (mIsDestroyed) {
1323                Log.w(LOG_TAG, "Not stopping printer state tracking - session destroyed");
1324                return;
1325            }
1326            // If printer discovery is not started - nothing to do.
1327            if (mStartedPrinterDiscoveryTokens.isEmpty()) {
1328                return;
1329            }
1330            // If we did not track this printer - nothing to do.
1331            if (!mStateTrackedPrinters.remove(printerId)) {
1332                return;
1333            }
1334            // No service - nothing to do.
1335            RemotePrintService service = mActiveServices.get(printerId.getServiceName());
1336            if (service == null) {
1337                return;
1338            }
1339            // Ask the service to start tracking.
1340            SomeArgs args = SomeArgs.obtain();
1341            args.arg1 = service;
1342            args.arg2 = printerId;
1343            mSessionHandler.obtainMessage(SessionHandler
1344                    .MSG_STOP_PRINTER_STATE_TRACKING, args)
1345                    .sendToTarget();
1346        }
1347
1348        public void onDestroyed() {
1349            /* do nothing */
1350        }
1351
1352        public void destroyLocked() {
1353            if (mIsDestroyed) {
1354                Log.w(LOG_TAG, "Not destroying - session destroyed");
1355                return;
1356            }
1357            mIsDestroyed = true;
1358            // Make sure printer tracking is stopped.
1359            final int printerCount = mStateTrackedPrinters.size();
1360            for (int i = 0; i < printerCount; i++) {
1361                PrinterId printerId = mStateTrackedPrinters.get(i);
1362                stopPrinterStateTracking(printerId);
1363            }
1364            // Make sure discovery is stopped.
1365            final int observerCount = mStartedPrinterDiscoveryTokens.size();
1366            for (int i = 0; i < observerCount; i++) {
1367                IBinder token = mStartedPrinterDiscoveryTokens.get(i);
1368                stopPrinterDiscoveryLocked(IPrinterDiscoveryObserver.Stub.asInterface(token));
1369            }
1370            // Tell the services we are done.
1371            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1372                    mActiveServices.values());
1373            mSessionHandler.obtainMessage(SessionHandler
1374                    .MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION, services)
1375                    .sendToTarget();
1376        }
1377
1378        public void onPrintersAddedLocked(List<PrinterInfo> printers) {
1379            if (DEBUG) {
1380                Log.i(LOG_TAG, "onPrintersAddedLocked()");
1381            }
1382            if (mIsDestroyed) {
1383                Log.w(LOG_TAG, "Not adding printers - session destroyed");
1384                return;
1385            }
1386            List<PrinterInfo> addedPrinters = null;
1387            final int addedPrinterCount = printers.size();
1388            for (int i = 0; i < addedPrinterCount; i++) {
1389                PrinterInfo printer = printers.get(i);
1390                PrinterInfo oldPrinter = mPrinters.put(printer.getId(), printer);
1391                if (oldPrinter == null || !oldPrinter.equals(printer)) {
1392                    if (addedPrinters == null) {
1393                        addedPrinters = new ArrayList<PrinterInfo>();
1394                    }
1395                    addedPrinters.add(printer);
1396                }
1397            }
1398            if (addedPrinters != null) {
1399                mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED,
1400                        addedPrinters).sendToTarget();
1401            }
1402        }
1403
1404        public void onPrintersRemovedLocked(List<PrinterId> printerIds) {
1405            if (DEBUG) {
1406                Log.i(LOG_TAG, "onPrintersRemovedLocked()");
1407            }
1408            if (mIsDestroyed) {
1409                Log.w(LOG_TAG, "Not removing printers - session destroyed");
1410                return;
1411            }
1412            List<PrinterId> removedPrinterIds = null;
1413            final int removedPrinterCount = printerIds.size();
1414            for (int i = 0; i < removedPrinterCount; i++) {
1415                PrinterId removedPrinterId = printerIds.get(i);
1416                if (mPrinters.remove(removedPrinterId) != null) {
1417                    if (removedPrinterIds == null) {
1418                        removedPrinterIds = new ArrayList<PrinterId>();
1419                    }
1420                    removedPrinterIds.add(removedPrinterId);
1421                }
1422            }
1423            if (removedPrinterIds != null) {
1424                mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED,
1425                        removedPrinterIds).sendToTarget();
1426            }
1427        }
1428
1429        public void onServiceRemovedLocked(RemotePrintService service) {
1430            if (mIsDestroyed) {
1431                Log.w(LOG_TAG, "Not updating removed service - session destroyed");
1432                return;
1433            }
1434            // Remove the reported and tracked printers for that service.
1435            ComponentName serviceName = service.getComponentName();
1436            removePrintersForServiceLocked(serviceName);
1437            service.destroy();
1438        }
1439
1440        /**
1441         * Handle that a custom icon for a printer was loaded.
1442         *
1443         * This increments the icon generation and adds the printer again which triggers an update
1444         * in all users of the currently known printers.
1445         *
1446         * @param printerId the id of the printer the icon belongs to
1447         * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
1448         */
1449        public void onCustomPrinterIconLoadedLocked(PrinterId printerId) {
1450            if (DEBUG) {
1451                Log.i(LOG_TAG, "onCustomPrinterIconLoadedLocked()");
1452            }
1453            if (mIsDestroyed) {
1454                Log.w(LOG_TAG, "Not updating printer - session destroyed");
1455                return;
1456            }
1457
1458            PrinterInfo printer = mPrinters.get(printerId);
1459            if (printer != null) {
1460                PrinterInfo newPrinter = (new PrinterInfo.Builder(printer))
1461                        .incCustomPrinterIconGen().build();
1462                mPrinters.put(printerId, newPrinter);
1463
1464                ArrayList<PrinterInfo> addedPrinters = new ArrayList<>(1);
1465                addedPrinters.add(newPrinter);
1466                mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED,
1467                        addedPrinters).sendToTarget();
1468            }
1469        }
1470
1471        public void onServiceDiedLocked(RemotePrintService service) {
1472            // Remove the reported by that service.
1473            removePrintersForServiceLocked(service.getComponentName());
1474        }
1475
1476        public void onServiceAddedLocked(RemotePrintService service) {
1477            if (mIsDestroyed) {
1478                Log.w(LOG_TAG, "Not updating added service - session destroyed");
1479                return;
1480            }
1481            // Tell the service to create a session.
1482            mSessionHandler.obtainMessage(
1483                    SessionHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
1484                    service).sendToTarget();
1485            // Start printer discovery if necessary.
1486            if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
1487                mSessionHandler.obtainMessage(
1488                        SessionHandler.MSG_START_PRINTER_DISCOVERY,
1489                        service).sendToTarget();
1490            }
1491            // Start tracking printers if necessary
1492            final int trackedPrinterCount = mStateTrackedPrinters.size();
1493            for (int i = 0; i < trackedPrinterCount; i++) {
1494                PrinterId printerId = mStateTrackedPrinters.get(i);
1495                if (printerId.getServiceName().equals(service.getComponentName())) {
1496                    SomeArgs args = SomeArgs.obtain();
1497                    args.arg1 = service;
1498                    args.arg2 = printerId;
1499                    mSessionHandler.obtainMessage(SessionHandler
1500                            .MSG_START_PRINTER_STATE_TRACKING, args)
1501                            .sendToTarget();
1502                }
1503            }
1504        }
1505
1506        public void dump(PrintWriter pw, String prefix) {
1507            pw.append(prefix).append("destroyed=")
1508                    .append(String.valueOf(mDestroyed)).println();
1509
1510            pw.append(prefix).append("printDiscoveryInProgress=")
1511                    .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println();
1512
1513            String tab = "  ";
1514
1515            pw.append(prefix).append(tab).append("printer discovery observers:").println();
1516            final int observerCount = mDiscoveryObservers.beginBroadcast();
1517            for (int i = 0; i < observerCount; i++) {
1518                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1519                pw.append(prefix).append(prefix).append(observer.toString());
1520                pw.println();
1521            }
1522            mDiscoveryObservers.finishBroadcast();
1523
1524            pw.append(prefix).append(tab).append("start discovery requests:").println();
1525            final int tokenCount = this.mStartedPrinterDiscoveryTokens.size();
1526            for (int i = 0; i < tokenCount; i++) {
1527                IBinder token = mStartedPrinterDiscoveryTokens.get(i);
1528                pw.append(prefix).append(tab).append(tab).append(token.toString()).println();
1529            }
1530
1531            pw.append(prefix).append(tab).append("tracked printer requests:").println();
1532            final int trackedPrinters = mStateTrackedPrinters.size();
1533            for (int i = 0; i < trackedPrinters; i++) {
1534                PrinterId printer = mStateTrackedPrinters.get(i);
1535                pw.append(prefix).append(tab).append(tab).append(printer.toString()).println();
1536            }
1537
1538            pw.append(prefix).append(tab).append("printers:").println();
1539            final int pritnerCount = mPrinters.size();
1540            for (int i = 0; i < pritnerCount; i++) {
1541                PrinterInfo printer = mPrinters.valueAt(i);
1542                pw.append(prefix).append(tab).append(tab).append(
1543                        printer.toString()).println();
1544            }
1545        }
1546
1547        private void removePrintersForServiceLocked(ComponentName serviceName) {
1548            // No printers - nothing to do.
1549            if (mPrinters.isEmpty()) {
1550                return;
1551            }
1552            // Remove the printers for that service.
1553            List<PrinterId> removedPrinterIds = null;
1554            final int printerCount = mPrinters.size();
1555            for (int i = 0; i < printerCount; i++) {
1556                PrinterId printerId = mPrinters.keyAt(i);
1557                if (printerId.getServiceName().equals(serviceName)) {
1558                    if (removedPrinterIds == null) {
1559                        removedPrinterIds = new ArrayList<PrinterId>();
1560                    }
1561                    removedPrinterIds.add(printerId);
1562                }
1563            }
1564            if (removedPrinterIds != null) {
1565                final int removedPrinterCount = removedPrinterIds.size();
1566                for (int i = 0; i < removedPrinterCount; i++) {
1567                    mPrinters.remove(removedPrinterIds.get(i));
1568                }
1569                mSessionHandler.obtainMessage(
1570                        SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED,
1571                        removedPrinterIds).sendToTarget();
1572            }
1573        }
1574
1575        private void handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters) {
1576            final int observerCount = mDiscoveryObservers.beginBroadcast();
1577            for (int i = 0; i < observerCount; i++) {
1578                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1579                handlePrintersAdded(observer, addedPrinters);
1580            }
1581            mDiscoveryObservers.finishBroadcast();
1582        }
1583
1584        private void handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds) {
1585            final int observerCount = mDiscoveryObservers.beginBroadcast();
1586            for (int i = 0; i < observerCount; i++) {
1587                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1588                handlePrintersRemoved(observer, removedPrinterIds);
1589            }
1590            mDiscoveryObservers.finishBroadcast();
1591        }
1592
1593        private void handleDispatchCreatePrinterDiscoverySession(
1594                List<RemotePrintService> services) {
1595            final int serviceCount = services.size();
1596            for (int i = 0; i < serviceCount; i++) {
1597                RemotePrintService service = services.get(i);
1598                service.createPrinterDiscoverySession();
1599            }
1600        }
1601
1602        private void handleDispatchDestroyPrinterDiscoverySession(
1603                List<RemotePrintService> services) {
1604            final int serviceCount = services.size();
1605            for (int i = 0; i < serviceCount; i++) {
1606                RemotePrintService service = services.get(i);
1607                service.destroyPrinterDiscoverySession();
1608            }
1609            onDestroyed();
1610        }
1611
1612        private void handleDispatchStartPrinterDiscovery(
1613                List<RemotePrintService> services, List<PrinterId> printerIds) {
1614            final int serviceCount = services.size();
1615            for (int i = 0; i < serviceCount; i++) {
1616                RemotePrintService service = services.get(i);
1617                service.startPrinterDiscovery(printerIds);
1618            }
1619        }
1620
1621        private void handleDispatchStopPrinterDiscovery(List<RemotePrintService> services) {
1622            final int serviceCount = services.size();
1623            for (int i = 0; i < serviceCount; i++) {
1624                RemotePrintService service = services.get(i);
1625                service.stopPrinterDiscovery();
1626            }
1627        }
1628
1629        private void handleValidatePrinters(RemotePrintService service,
1630                List<PrinterId> printerIds) {
1631            service.validatePrinters(printerIds);
1632        }
1633
1634        private void handleStartPrinterStateTracking(@NonNull RemotePrintService service,
1635                @NonNull PrinterId printerId) {
1636            service.startPrinterStateTracking(printerId);
1637        }
1638
1639        private void handleStopPrinterStateTracking(RemotePrintService service,
1640                PrinterId printerId) {
1641            service.stopPrinterStateTracking(printerId);
1642        }
1643
1644        private void handlePrintersAdded(IPrinterDiscoveryObserver observer,
1645            List<PrinterInfo> printers) {
1646            try {
1647                observer.onPrintersAdded(new ParceledListSlice<PrinterInfo>(printers));
1648            } catch (RemoteException re) {
1649                Log.e(LOG_TAG, "Error sending added printers", re);
1650            }
1651        }
1652
1653        private void handlePrintersRemoved(IPrinterDiscoveryObserver observer,
1654            List<PrinterId> printerIds) {
1655            try {
1656                observer.onPrintersRemoved(new ParceledListSlice<PrinterId>(printerIds));
1657            } catch (RemoteException re) {
1658                Log.e(LOG_TAG, "Error sending removed printers", re);
1659            }
1660        }
1661
1662        private final class SessionHandler extends Handler {
1663            public static final int MSG_PRINTERS_ADDED = 1;
1664            public static final int MSG_PRINTERS_REMOVED = 2;
1665            public static final int MSG_DISPATCH_PRINTERS_ADDED = 3;
1666            public static final int MSG_DISPATCH_PRINTERS_REMOVED = 4;
1667
1668            public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 5;
1669            public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 6;
1670            public static final int MSG_START_PRINTER_DISCOVERY = 7;
1671            public static final int MSG_STOP_PRINTER_DISCOVERY = 8;
1672            public static final int MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION = 9;
1673            public static final int MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION = 10;
1674            public static final int MSG_DISPATCH_START_PRINTER_DISCOVERY = 11;
1675            public static final int MSG_DISPATCH_STOP_PRINTER_DISCOVERY = 12;
1676            public static final int MSG_VALIDATE_PRINTERS = 13;
1677            public static final int MSG_START_PRINTER_STATE_TRACKING = 14;
1678            public static final int MSG_STOP_PRINTER_STATE_TRACKING = 15;
1679            public static final int MSG_DESTROY_SERVICE = 16;
1680
1681            SessionHandler(Looper looper) {
1682                super(looper, null, false);
1683            }
1684
1685            @Override
1686            @SuppressWarnings("unchecked")
1687            public void handleMessage(Message message) {
1688                switch (message.what) {
1689                    case MSG_PRINTERS_ADDED: {
1690                        SomeArgs args = (SomeArgs) message.obj;
1691                        IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1;
1692                        List<PrinterInfo> addedPrinters = (List<PrinterInfo>) args.arg2;
1693                        args.recycle();
1694                        handlePrintersAdded(observer, addedPrinters);
1695                    } break;
1696
1697                    case MSG_PRINTERS_REMOVED: {
1698                        SomeArgs args = (SomeArgs) message.obj;
1699                        IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1;
1700                        List<PrinterId> removedPrinterIds = (List<PrinterId>) args.arg2;
1701                        args.recycle();
1702                        handlePrintersRemoved(observer, removedPrinterIds);
1703                    }
1704
1705                    case MSG_DISPATCH_PRINTERS_ADDED: {
1706                        List<PrinterInfo> addedPrinters = (List<PrinterInfo>) message.obj;
1707                        handleDispatchPrintersAdded(addedPrinters);
1708                    } break;
1709
1710                    case MSG_DISPATCH_PRINTERS_REMOVED: {
1711                        List<PrinterId> removedPrinterIds = (List<PrinterId>) message.obj;
1712                        handleDispatchPrintersRemoved(removedPrinterIds);
1713                    } break;
1714
1715                    case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
1716                        RemotePrintService service = (RemotePrintService) message.obj;
1717                        service.createPrinterDiscoverySession();
1718                    } break;
1719
1720                    case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
1721                        RemotePrintService service = (RemotePrintService) message.obj;
1722                        service.destroyPrinterDiscoverySession();
1723                    } break;
1724
1725                    case MSG_START_PRINTER_DISCOVERY: {
1726                        RemotePrintService service = (RemotePrintService) message.obj;
1727                        service.startPrinterDiscovery(null);
1728                    } break;
1729
1730                    case MSG_STOP_PRINTER_DISCOVERY: {
1731                        RemotePrintService service = (RemotePrintService) message.obj;
1732                        service.stopPrinterDiscovery();
1733                    } break;
1734
1735                    case MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION: {
1736                        List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
1737                        handleDispatchCreatePrinterDiscoverySession(services);
1738                    } break;
1739
1740                    case MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION: {
1741                        List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
1742                        handleDispatchDestroyPrinterDiscoverySession(services);
1743                    } break;
1744
1745                    case MSG_DISPATCH_START_PRINTER_DISCOVERY: {
1746                        SomeArgs args = (SomeArgs) message.obj;
1747                        List<RemotePrintService> services = (List<RemotePrintService>) args.arg1;
1748                        List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
1749                        args.recycle();
1750                        handleDispatchStartPrinterDiscovery(services, printerIds);
1751                    } break;
1752
1753                    case MSG_DISPATCH_STOP_PRINTER_DISCOVERY: {
1754                        List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
1755                        handleDispatchStopPrinterDiscovery(services);
1756                    } break;
1757
1758                    case MSG_VALIDATE_PRINTERS: {
1759                        SomeArgs args = (SomeArgs) message.obj;
1760                        RemotePrintService service = (RemotePrintService) args.arg1;
1761                        List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
1762                        args.recycle();
1763                        handleValidatePrinters(service, printerIds);
1764                    } break;
1765
1766                    case MSG_START_PRINTER_STATE_TRACKING: {
1767                        SomeArgs args = (SomeArgs) message.obj;
1768                        RemotePrintService service = (RemotePrintService) args.arg1;
1769                        PrinterId printerId = (PrinterId) args.arg2;
1770                        args.recycle();
1771                        handleStartPrinterStateTracking(service, printerId);
1772                    } break;
1773
1774                    case MSG_STOP_PRINTER_STATE_TRACKING: {
1775                        SomeArgs args = (SomeArgs) message.obj;
1776                        RemotePrintService service = (RemotePrintService) args.arg1;
1777                        PrinterId printerId = (PrinterId) args.arg2;
1778                        args.recycle();
1779                        handleStopPrinterStateTracking(service, printerId);
1780                    } break;
1781
1782                    case MSG_DESTROY_SERVICE: {
1783                        RemotePrintService service = (RemotePrintService) message.obj;
1784                        service.destroy();
1785                    } break;
1786                }
1787            }
1788        }
1789    }
1790
1791    private final class PrintJobForAppCache {
1792        private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp =
1793                new SparseArray<List<PrintJobInfo>>();
1794
1795        public boolean onPrintJobCreated(final IBinder creator, final int appId,
1796                PrintJobInfo printJob) {
1797            try {
1798                creator.linkToDeath(new DeathRecipient() {
1799                    @Override
1800                    public void binderDied() {
1801                        creator.unlinkToDeath(this, 0);
1802                        synchronized (mLock) {
1803                            mPrintJobsForRunningApp.remove(appId);
1804                        }
1805                    }
1806                }, 0);
1807            } catch (RemoteException re) {
1808                /* The process is already dead - we just failed. */
1809                return false;
1810            }
1811            synchronized (mLock) {
1812                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
1813                if (printJobsForApp == null) {
1814                    printJobsForApp = new ArrayList<PrintJobInfo>();
1815                    mPrintJobsForRunningApp.put(appId, printJobsForApp);
1816                }
1817                printJobsForApp.add(printJob);
1818            }
1819            return true;
1820        }
1821
1822        public void onPrintJobStateChanged(PrintJobInfo printJob) {
1823            synchronized (mLock) {
1824                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(
1825                        printJob.getAppId());
1826                if (printJobsForApp == null) {
1827                    return;
1828                }
1829                final int printJobCount = printJobsForApp.size();
1830                for (int i = 0; i < printJobCount; i++) {
1831                    PrintJobInfo oldPrintJob = printJobsForApp.get(i);
1832                    if (oldPrintJob.getId().equals(printJob.getId())) {
1833                        printJobsForApp.set(i, printJob);
1834                    }
1835                }
1836            }
1837        }
1838
1839        public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) {
1840            synchronized (mLock) {
1841                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
1842                if (printJobsForApp == null) {
1843                    return null;
1844                }
1845                final int printJobCount = printJobsForApp.size();
1846                for (int i = 0; i < printJobCount; i++) {
1847                    PrintJobInfo printJob = printJobsForApp.get(i);
1848                    if (printJob.getId().equals(printJobId)) {
1849                        return printJob;
1850                    }
1851                }
1852            }
1853            return null;
1854        }
1855
1856        public List<PrintJobInfo> getPrintJobs(int appId) {
1857            synchronized (mLock) {
1858                List<PrintJobInfo> printJobs = null;
1859                if (appId == PrintManager.APP_ID_ANY) {
1860                    final int bucketCount = mPrintJobsForRunningApp.size();
1861                    for (int i = 0; i < bucketCount; i++) {
1862                        List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
1863                        if (printJobs == null) {
1864                            printJobs = new ArrayList<PrintJobInfo>();
1865                        }
1866                        printJobs.addAll(bucket);
1867                    }
1868                } else {
1869                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId);
1870                    if (bucket != null) {
1871                        if (printJobs == null) {
1872                            printJobs = new ArrayList<PrintJobInfo>();
1873                        }
1874                        printJobs.addAll(bucket);
1875                    }
1876                }
1877                if (printJobs != null) {
1878                    return printJobs;
1879                }
1880                return Collections.emptyList();
1881            }
1882        }
1883
1884        public void dump(PrintWriter pw, String prefix) {
1885            synchronized (mLock) {
1886                String tab = "  ";
1887                final int bucketCount = mPrintJobsForRunningApp.size();
1888                for (int i = 0; i < bucketCount; i++) {
1889                    final int appId = mPrintJobsForRunningApp.keyAt(i);
1890                    pw.append(prefix).append("appId=" + appId).append(':').println();
1891                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
1892                    final int printJobCount = bucket.size();
1893                    for (int j = 0; j < printJobCount; j++) {
1894                        PrintJobInfo printJob = bucket.get(j);
1895                        pw.append(prefix).append(tab).append(printJob.toString()).println();
1896                    }
1897                }
1898            }
1899        }
1900    }
1901}
1902