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