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