UserState.java revision 0de4834dd8280474a11f16470d94f53dd54eb1cc
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        synchronized (mLock) {
438            throwIfDestroyedLocked();
439
440            if (mPrinterDiscoverySession == null) {
441                mSpooler.clearCustomPrinterIconCache();
442
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        synchronized (mLock) {
735            throwIfDestroyedLocked();
736
737            // No session - nothing to do.
738            if (mPrinterDiscoverySession == null) {
739                return;
740            }
741            mSpooler.onCustomPrinterIconLoaded(printerId, icon);
742            mPrinterDiscoverySession.onCustomPrinterIconLoadedLocked(printerId);
743        }
744    }
745
746    @Override
747    public void onServiceDied(RemotePrintService service) {
748        synchronized (mLock) {
749            throwIfDestroyedLocked();
750            // No services - nothing to do.
751            if (mActiveServices.isEmpty()) {
752                return;
753            }
754            // Fail all print jobs.
755            failActivePrintJobsForService(service.getComponentName());
756            service.onAllPrintJobsHandled();
757            // No session - nothing to do.
758            if (mPrinterDiscoverySession == null) {
759                return;
760            }
761            mPrinterDiscoverySession.onServiceDiedLocked(service);
762        }
763    }
764
765    public void updateIfNeededLocked() {
766        throwIfDestroyedLocked();
767        readConfigurationLocked();
768        onConfigurationChangedLocked();
769    }
770
771    public void destroyLocked() {
772        throwIfDestroyedLocked();
773        mSpooler.destroy();
774        for (RemotePrintService service : mActiveServices.values()) {
775            service.destroy();
776        }
777        mActiveServices.clear();
778        mInstalledServices.clear();
779        mDisabledServices.clear();
780        if (mPrinterDiscoverySession != null) {
781            mPrinterDiscoverySession.destroyLocked();
782            mPrinterDiscoverySession = null;
783        }
784        mDestroyed = true;
785    }
786
787    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String prefix) {
788        pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":");
789        pw.println();
790
791        String tab = "  ";
792
793        pw.append(prefix).append(tab).append("installed services:").println();
794        final int installedServiceCount = mInstalledServices.size();
795        for (int i = 0; i < installedServiceCount; i++) {
796            PrintServiceInfo installedService = mInstalledServices.get(i);
797            String installedServicePrefix = prefix + tab + tab;
798            pw.append(installedServicePrefix).append("service:").println();
799            ResolveInfo resolveInfo = installedService.getResolveInfo();
800            ComponentName componentName = new ComponentName(
801                    resolveInfo.serviceInfo.packageName,
802                    resolveInfo.serviceInfo.name);
803            pw.append(installedServicePrefix).append(tab).append("componentName=")
804                    .append(componentName.flattenToString()).println();
805            pw.append(installedServicePrefix).append(tab).append("settingsActivity=")
806                    .append(installedService.getSettingsActivityName()).println();
807            pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=")
808                    .append(installedService.getAddPrintersActivityName()).println();
809            pw.append(installedServicePrefix).append(tab).append("avancedOptionsActivity=")
810                   .append(installedService.getAdvancedOptionsActivityName()).println();
811        }
812
813        pw.append(prefix).append(tab).append("disabled services:").println();
814        for (ComponentName disabledService : mDisabledServices) {
815            String disabledServicePrefix = prefix + tab + tab;
816            pw.append(disabledServicePrefix).append("service:").println();
817            pw.append(disabledServicePrefix).append(tab).append("componentName=")
818                    .append(disabledService.flattenToString());
819            pw.println();
820        }
821
822        pw.append(prefix).append(tab).append("active services:").println();
823        final int activeServiceCount = mActiveServices.size();
824        for (int i = 0; i < activeServiceCount; i++) {
825            RemotePrintService activeService = mActiveServices.valueAt(i);
826            activeService.dump(pw, prefix + tab + tab);
827            pw.println();
828        }
829
830        pw.append(prefix).append(tab).append("cached print jobs:").println();
831        mPrintJobForAppCache.dump(pw, prefix + tab + tab);
832
833        pw.append(prefix).append(tab).append("discovery mediator:").println();
834        if (mPrinterDiscoverySession != null) {
835            mPrinterDiscoverySession.dump(pw, prefix + tab + tab);
836        }
837
838        pw.append(prefix).append(tab).append("print spooler:").println();
839        mSpooler.dump(fd, pw, prefix + tab + tab);
840        pw.println();
841    }
842
843    private void readConfigurationLocked() {
844        readInstalledPrintServicesLocked();
845        readDisabledPrintServicesLocked();
846    }
847
848    private void readInstalledPrintServicesLocked() {
849        Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>();
850
851        List<ResolveInfo> installedServices = mContext.getPackageManager()
852                .queryIntentServicesAsUser(mQueryIntent,
853                        GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING, mUserId);
854
855        final int installedCount = installedServices.size();
856        for (int i = 0, count = installedCount; i < count; i++) {
857            ResolveInfo installedService = installedServices.get(i);
858            if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals(
859                    installedService.serviceInfo.permission)) {
860                ComponentName serviceName = new ComponentName(
861                        installedService.serviceInfo.packageName,
862                        installedService.serviceInfo.name);
863                Slog.w(LOG_TAG, "Skipping print service "
864                        + serviceName.flattenToShortString()
865                        + " since it does not require permission "
866                        + android.Manifest.permission.BIND_PRINT_SERVICE);
867                continue;
868            }
869            tempPrintServices.add(PrintServiceInfo.create(installedService, mContext));
870        }
871
872        mInstalledServices.clear();
873        mInstalledServices.addAll(tempPrintServices);
874    }
875
876    /**
877     * Update persistent state from a previous version of Android.
878     */
879    private void upgradePersistentStateIfNeeded() {
880        String enabledSettingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
881                Settings.Secure.ENABLED_PRINT_SERVICES, mUserId);
882
883        // Pre N we store the enabled services, in N and later we store the disabled services.
884        // Hence if enabledSettingValue is still set, we need to upgrade.
885        if (enabledSettingValue != null) {
886            Set<ComponentName> enabledServiceNameSet = new HashSet<ComponentName>();
887            readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
888                    enabledServiceNameSet);
889
890            ArraySet<ComponentName> disabledServices = new ArraySet<>();
891            final int numInstalledServices = mInstalledServices.size();
892            for (int i = 0; i < numInstalledServices; i++) {
893                ComponentName serviceName = mInstalledServices.get(i).getComponentName();
894                if (!enabledServiceNameSet.contains(serviceName)) {
895                    disabledServices.add(serviceName);
896                }
897            }
898
899            writeDisabledPrintServicesLocked(disabledServices);
900
901            // We won't needed ENABLED_PRINT_SERVICES anymore, set to null to prevent upgrade to run
902            // again.
903            Settings.Secure.putStringForUser(mContext.getContentResolver(),
904                    Settings.Secure.ENABLED_PRINT_SERVICES, null, mUserId);
905        }
906    }
907
908    /**
909     * Read the set of disabled print services from the secure settings.
910     *
911     * @return true if the state changed.
912     */
913    private void readDisabledPrintServicesLocked() {
914        Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>();
915        readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES,
916                tempDisabledServiceNameSet);
917        if (!tempDisabledServiceNameSet.equals(mDisabledServices)) {
918            mDisabledServices.clear();
919            mDisabledServices.addAll(tempDisabledServiceNameSet);
920        }
921    }
922
923    private void readPrintServicesFromSettingLocked(String setting,
924            Set<ComponentName> outServiceNames) {
925        String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
926                setting, mUserId);
927        if (!TextUtils.isEmpty(settingValue)) {
928            TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
929            splitter.setString(settingValue);
930            while (splitter.hasNext()) {
931                String string = splitter.next();
932                if (TextUtils.isEmpty(string)) {
933                    continue;
934                }
935                ComponentName componentName = ComponentName.unflattenFromString(string);
936                if (componentName != null) {
937                    outServiceNames.add(componentName);
938                }
939            }
940        }
941    }
942
943    /**
944     * Persist the disabled print services to the secure settings.
945     */
946    private void writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices) {
947        StringBuilder builder = new StringBuilder();
948        for (ComponentName componentName : disabledServices) {
949            if (builder.length() > 0) {
950                builder.append(COMPONENT_NAME_SEPARATOR);
951            }
952            builder.append(componentName.flattenToShortString());
953        }
954        Settings.Secure.putStringForUser(mContext.getContentResolver(),
955                Settings.Secure.DISABLED_PRINT_SERVICES, builder.toString(), mUserId);
956    }
957
958    /**
959     * Get the {@link ComponentName names} of the installed print services
960     *
961     * @return The names of the installed print services
962     */
963    private ArrayList<ComponentName> getInstalledComponents() {
964        ArrayList<ComponentName> installedComponents = new ArrayList<ComponentName>();
965
966        final int installedCount = mInstalledServices.size();
967        for (int i = 0; i < installedCount; i++) {
968            ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo();
969            ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName,
970                    resolveInfo.serviceInfo.name);
971
972            installedComponents.add(serviceName);
973        }
974
975        return installedComponents;
976    }
977
978    /**
979     * Prune persistent state if a print service was uninstalled
980     */
981    public void prunePrintServices() {
982        synchronized (mLock) {
983            ArrayList<ComponentName> installedComponents = getInstalledComponents();
984
985            // Remove unnecessary entries from persistent state "disabled services"
986            boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents);
987            if (disabledServicesUninstalled) {
988                writeDisabledPrintServicesLocked(mDisabledServices);
989            }
990
991            // Remove unnecessary entries from persistent state "approved services"
992            mSpooler.pruneApprovedPrintServices(installedComponents);
993        }
994    }
995
996    private void onConfigurationChangedLocked() {
997        ArrayList<ComponentName> installedComponents = getInstalledComponents();
998
999        final int installedCount = installedComponents.size();
1000        for (int i = 0; i < installedCount; i++) {
1001            ComponentName serviceName = installedComponents.get(i);
1002
1003            if (!mDisabledServices.contains(serviceName)) {
1004                if (!mActiveServices.containsKey(serviceName)) {
1005                    RemotePrintService service = new RemotePrintService(
1006                            mContext, serviceName, mUserId, mSpooler, this);
1007                    addServiceLocked(service);
1008                }
1009            } else {
1010                RemotePrintService service = mActiveServices.remove(serviceName);
1011                if (service != null) {
1012                    removeServiceLocked(service);
1013                }
1014            }
1015        }
1016
1017        Iterator<Map.Entry<ComponentName, RemotePrintService>> iterator =
1018                mActiveServices.entrySet().iterator();
1019        while (iterator.hasNext()) {
1020            Map.Entry<ComponentName, RemotePrintService> entry = iterator.next();
1021            ComponentName serviceName = entry.getKey();
1022            RemotePrintService service = entry.getValue();
1023            if (!installedComponents.contains(serviceName)) {
1024                removeServiceLocked(service);
1025                iterator.remove();
1026            }
1027        }
1028
1029        onPrintServicesChanged();
1030    }
1031
1032    private void addServiceLocked(RemotePrintService service) {
1033        mActiveServices.put(service.getComponentName(), service);
1034        if (mPrinterDiscoverySession != null) {
1035            mPrinterDiscoverySession.onServiceAddedLocked(service);
1036        }
1037    }
1038
1039    private void removeServiceLocked(RemotePrintService service) {
1040        // Fail all print jobs.
1041        failActivePrintJobsForService(service.getComponentName());
1042        // If discovery is in progress, tear down the service.
1043        if (mPrinterDiscoverySession != null) {
1044            mPrinterDiscoverySession.onServiceRemovedLocked(service);
1045        } else {
1046            // Otherwise, just destroy it.
1047            service.destroy();
1048        }
1049    }
1050
1051    private void failActivePrintJobsForService(final ComponentName serviceName) {
1052        // Makes sure all active print jobs are failed since the service
1053        // just died. Do this off the main thread since we do to allow
1054        // calls into the spooler on the main thread.
1055        if (Looper.getMainLooper().isCurrentThread()) {
1056            BackgroundThread.getHandler().post(new Runnable() {
1057                @Override
1058                public void run() {
1059                    failScheduledPrintJobsForServiceInternal(serviceName);
1060                }
1061            });
1062        } else {
1063            failScheduledPrintJobsForServiceInternal(serviceName);
1064        }
1065    }
1066
1067    private void failScheduledPrintJobsForServiceInternal(ComponentName serviceName) {
1068        List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(serviceName,
1069                PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
1070        if (printJobs == null) {
1071            return;
1072        }
1073        final long identity = Binder.clearCallingIdentity();
1074        try {
1075            final int printJobCount = printJobs.size();
1076            for (int i = 0; i < printJobCount; i++) {
1077                PrintJobInfo printJob = printJobs.get(i);
1078                mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
1079                        mContext.getString(R.string.reason_service_unavailable));
1080            }
1081        } finally {
1082            Binder.restoreCallingIdentity(identity);
1083        }
1084    }
1085
1086    private void throwIfDestroyedLocked() {
1087        if (mDestroyed) {
1088            throw new IllegalStateException("Cannot interact with a destroyed instance.");
1089        }
1090    }
1091
1092    private void handleDispatchPrintJobStateChanged(PrintJobId printJobId, int appId) {
1093        final List<PrintJobStateChangeListenerRecord> records;
1094        synchronized (mLock) {
1095            if (mPrintJobStateChangeListenerRecords == null) {
1096                return;
1097            }
1098            records = new ArrayList<PrintJobStateChangeListenerRecord>(
1099                    mPrintJobStateChangeListenerRecords);
1100        }
1101        final int recordCount = records.size();
1102        for (int i = 0; i < recordCount; i++) {
1103            PrintJobStateChangeListenerRecord record = records.get(i);
1104            if (record.appId == PrintManager.APP_ID_ANY
1105                    || record.appId == appId)
1106            try {
1107                record.listener.onPrintJobStateChanged(printJobId);
1108            } catch (RemoteException re) {
1109                Log.e(LOG_TAG, "Error notifying for print job state change", re);
1110            }
1111        }
1112    }
1113
1114    private void handleDispatchPrintServicesChanged() {
1115        final List<ListenerRecord<IPrintServicesChangeListener>> records;
1116        synchronized (mLock) {
1117            if (mPrintServicesChangeListenerRecords == null) {
1118                return;
1119            }
1120            records = new ArrayList<>(mPrintServicesChangeListenerRecords);
1121        }
1122        final int recordCount = records.size();
1123        for (int i = 0; i < recordCount; i++) {
1124            ListenerRecord<IPrintServicesChangeListener> record = records.get(i);
1125
1126            try {
1127                record.listener.onPrintServicesChanged();;
1128            } catch (RemoteException re) {
1129                Log.e(LOG_TAG, "Error notifying for print services change", re);
1130            }
1131        }
1132    }
1133
1134    private void handleDispatchPrintServiceRecommendationsUpdated(
1135            @Nullable List<RecommendationInfo> recommendations) {
1136        final List<ListenerRecord<IRecommendationsChangeListener>> records;
1137        synchronized (mLock) {
1138            if (mPrintServiceRecommendationsChangeListenerRecords == null) {
1139                return;
1140            }
1141            records = new ArrayList<>(mPrintServiceRecommendationsChangeListenerRecords);
1142
1143            mPrintServiceRecommendations = recommendations;
1144        }
1145        final int recordCount = records.size();
1146        for (int i = 0; i < recordCount; i++) {
1147            ListenerRecord<IRecommendationsChangeListener> record = records.get(i);
1148
1149            try {
1150                record.listener.onRecommendationsChanged();
1151            } catch (RemoteException re) {
1152                Log.e(LOG_TAG, "Error notifying for print service recommendations change", re);
1153            }
1154        }
1155    }
1156
1157    private final class UserStateHandler extends Handler {
1158        public static final int MSG_DISPATCH_PRINT_JOB_STATE_CHANGED = 1;
1159        public static final int MSG_DISPATCH_PRINT_SERVICES_CHANGED = 2;
1160        public static final int MSG_DISPATCH_PRINT_SERVICES_RECOMMENDATIONS_UPDATED = 3;
1161
1162        public UserStateHandler(Looper looper) {
1163            super(looper, null, false);
1164        }
1165
1166        @Override
1167        public void handleMessage(Message message) {
1168            switch (message.what) {
1169                case MSG_DISPATCH_PRINT_JOB_STATE_CHANGED:
1170                    PrintJobId printJobId = (PrintJobId) message.obj;
1171                    final int appId = message.arg1;
1172                    handleDispatchPrintJobStateChanged(printJobId, appId);
1173                    break;
1174                case MSG_DISPATCH_PRINT_SERVICES_CHANGED:
1175                    handleDispatchPrintServicesChanged();
1176                    break;
1177                case MSG_DISPATCH_PRINT_SERVICES_RECOMMENDATIONS_UPDATED:
1178                    handleDispatchPrintServiceRecommendationsUpdated(
1179                            (List<RecommendationInfo>) message.obj);
1180                    break;
1181                default:
1182                    // not reached
1183            }
1184        }
1185    }
1186
1187    private abstract class PrintJobStateChangeListenerRecord implements DeathRecipient {
1188        @NonNull final IPrintJobStateChangeListener listener;
1189        final int appId;
1190
1191        public PrintJobStateChangeListenerRecord(@NonNull IPrintJobStateChangeListener listener,
1192                int appId) throws RemoteException {
1193            this.listener = listener;
1194            this.appId = appId;
1195            listener.asBinder().linkToDeath(this, 0);
1196        }
1197
1198        @Override
1199        public void binderDied() {
1200            listener.asBinder().unlinkToDeath(this, 0);
1201            onBinderDied();
1202        }
1203
1204        public abstract void onBinderDied();
1205    }
1206
1207    private abstract class ListenerRecord<T extends IInterface> implements DeathRecipient {
1208        @NonNull final T listener;
1209
1210        public ListenerRecord(@NonNull T listener) throws RemoteException {
1211            this.listener = listener;
1212            listener.asBinder().linkToDeath(this, 0);
1213        }
1214
1215        @Override
1216        public void binderDied() {
1217            listener.asBinder().unlinkToDeath(this, 0);
1218            onBinderDied();
1219        }
1220
1221        public abstract void onBinderDied();
1222    }
1223
1224    private class PrinterDiscoverySessionMediator {
1225        private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
1226                new ArrayMap<PrinterId, PrinterInfo>();
1227
1228        private final RemoteCallbackList<IPrinterDiscoveryObserver> mDiscoveryObservers =
1229                new RemoteCallbackList<IPrinterDiscoveryObserver>() {
1230            @Override
1231            public void onCallbackDied(IPrinterDiscoveryObserver observer) {
1232                synchronized (mLock) {
1233                    stopPrinterDiscoveryLocked(observer);
1234                    removeObserverLocked(observer);
1235                }
1236            }
1237        };
1238
1239        private final List<IBinder> mStartedPrinterDiscoveryTokens = new ArrayList<IBinder>();
1240
1241        private final List<PrinterId> mStateTrackedPrinters = new ArrayList<PrinterId>();
1242
1243        private final Handler mSessionHandler;
1244
1245        private boolean mIsDestroyed;
1246
1247        public PrinterDiscoverySessionMediator(Context context) {
1248            mSessionHandler = new SessionHandler(context.getMainLooper());
1249            // Kick off the session creation.
1250            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1251                    mActiveServices.values());
1252            mSessionHandler.obtainMessage(SessionHandler
1253                    .MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION, services)
1254                    .sendToTarget();
1255        }
1256
1257        public void addObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
1258            // Add the observer.
1259            mDiscoveryObservers.register(observer);
1260
1261            // Bring the added observer up to speed with the printers.
1262            if (!mPrinters.isEmpty()) {
1263                List<PrinterInfo> printers = new ArrayList<PrinterInfo>(mPrinters.values());
1264                SomeArgs args = SomeArgs.obtain();
1265                args.arg1 = observer;
1266                args.arg2 = printers;
1267                mSessionHandler.obtainMessage(SessionHandler.MSG_PRINTERS_ADDED,
1268                        args).sendToTarget();
1269            }
1270        }
1271
1272        public void removeObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
1273            // Remove the observer.
1274            mDiscoveryObservers.unregister(observer);
1275            // No one else observing - then kill it.
1276            if (mDiscoveryObservers.getRegisteredCallbackCount() == 0) {
1277                destroyLocked();
1278            }
1279        }
1280
1281        public final void startPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer,
1282                @Nullable List<PrinterId> priorityList) {
1283            if (mIsDestroyed) {
1284                Log.w(LOG_TAG, "Not starting dicovery - session destroyed");
1285                return;
1286            }
1287
1288            final boolean discoveryStarted = !mStartedPrinterDiscoveryTokens.isEmpty();
1289
1290            // Remember we got a start request to match with an end.
1291            mStartedPrinterDiscoveryTokens.add(observer.asBinder());
1292
1293            // If printer discovery is ongoing and the start request has a list
1294            // of printer to be checked, then we just request validating them.
1295            if (discoveryStarted && priorityList != null && !priorityList.isEmpty()) {
1296                validatePrinters(priorityList);
1297                return;
1298            }
1299
1300            // The service are already performing discovery - nothing to do.
1301            if (mStartedPrinterDiscoveryTokens.size() > 1) {
1302                return;
1303            }
1304
1305            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1306                    mActiveServices.values());
1307            SomeArgs args = SomeArgs.obtain();
1308            args.arg1 = services;
1309            args.arg2 = priorityList;
1310            mSessionHandler.obtainMessage(SessionHandler
1311                    .MSG_DISPATCH_START_PRINTER_DISCOVERY, args)
1312                    .sendToTarget();
1313        }
1314
1315        public final void stopPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer) {
1316            if (mIsDestroyed) {
1317                Log.w(LOG_TAG, "Not stopping dicovery - session destroyed");
1318                return;
1319            }
1320            // This one did not make an active discovery request - nothing to do.
1321            if (!mStartedPrinterDiscoveryTokens.remove(observer.asBinder())) {
1322                return;
1323            }
1324            // There are other interested observers - do not stop discovery.
1325            if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
1326                return;
1327            }
1328            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1329                    mActiveServices.values());
1330            mSessionHandler.obtainMessage(SessionHandler
1331                    .MSG_DISPATCH_STOP_PRINTER_DISCOVERY, services)
1332                    .sendToTarget();
1333        }
1334
1335        public void validatePrintersLocked(@NonNull List<PrinterId> printerIds) {
1336            if (mIsDestroyed) {
1337                Log.w(LOG_TAG, "Not validating pritners - session destroyed");
1338                return;
1339            }
1340
1341            List<PrinterId> remainingList = new ArrayList<PrinterId>(printerIds);
1342            while (!remainingList.isEmpty()) {
1343                Iterator<PrinterId> iterator = remainingList.iterator();
1344                // Gather the printers per service and request a validation.
1345                List<PrinterId> updateList = new ArrayList<PrinterId>();
1346                ComponentName serviceName = null;
1347                while (iterator.hasNext()) {
1348                    PrinterId printerId = iterator.next();
1349                    if (printerId != null) {
1350                        if (updateList.isEmpty()) {
1351                            updateList.add(printerId);
1352                            serviceName = printerId.getServiceName();
1353                            iterator.remove();
1354                        } else if (printerId.getServiceName().equals(serviceName)) {
1355                            updateList.add(printerId);
1356                            iterator.remove();
1357                        }
1358                    }
1359                }
1360                // Schedule a notification of the service.
1361                RemotePrintService service = mActiveServices.get(serviceName);
1362                if (service != null) {
1363                    SomeArgs args = SomeArgs.obtain();
1364                    args.arg1 = service;
1365                    args.arg2 = updateList;
1366                    mSessionHandler.obtainMessage(SessionHandler
1367                            .MSG_VALIDATE_PRINTERS, args)
1368                            .sendToTarget();
1369                }
1370            }
1371        }
1372
1373        public final void startPrinterStateTrackingLocked(@NonNull PrinterId printerId) {
1374            if (mIsDestroyed) {
1375                Log.w(LOG_TAG, "Not starting printer state tracking - session destroyed");
1376                return;
1377            }
1378            // If printer discovery is not started - nothing to do.
1379            if (mStartedPrinterDiscoveryTokens.isEmpty()) {
1380                return;
1381            }
1382            final boolean containedPrinterId = mStateTrackedPrinters.contains(printerId);
1383            // Keep track of the number of requests to track this one.
1384            mStateTrackedPrinters.add(printerId);
1385            // If we were tracking this printer - nothing to do.
1386            if (containedPrinterId) {
1387                return;
1388            }
1389            // No service - nothing to do.
1390            RemotePrintService service = mActiveServices.get(printerId.getServiceName());
1391            if (service == null) {
1392                return;
1393            }
1394            // Ask the service to start tracking.
1395            SomeArgs args = SomeArgs.obtain();
1396            args.arg1 = service;
1397            args.arg2 = printerId;
1398            mSessionHandler.obtainMessage(SessionHandler
1399                    .MSG_START_PRINTER_STATE_TRACKING, args)
1400                    .sendToTarget();
1401        }
1402
1403        public final void stopPrinterStateTrackingLocked(PrinterId printerId) {
1404            if (mIsDestroyed) {
1405                Log.w(LOG_TAG, "Not stopping printer state tracking - session destroyed");
1406                return;
1407            }
1408            // If printer discovery is not started - nothing to do.
1409            if (mStartedPrinterDiscoveryTokens.isEmpty()) {
1410                return;
1411            }
1412            // If we did not track this printer - nothing to do.
1413            if (!mStateTrackedPrinters.remove(printerId)) {
1414                return;
1415            }
1416            // No service - nothing to do.
1417            RemotePrintService service = mActiveServices.get(printerId.getServiceName());
1418            if (service == null) {
1419                return;
1420            }
1421            // Ask the service to start tracking.
1422            SomeArgs args = SomeArgs.obtain();
1423            args.arg1 = service;
1424            args.arg2 = printerId;
1425            mSessionHandler.obtainMessage(SessionHandler
1426                    .MSG_STOP_PRINTER_STATE_TRACKING, args)
1427                    .sendToTarget();
1428        }
1429
1430        public void onDestroyed() {
1431            /* do nothing */
1432        }
1433
1434        public void destroyLocked() {
1435            if (mIsDestroyed) {
1436                Log.w(LOG_TAG, "Not destroying - session destroyed");
1437                return;
1438            }
1439            mIsDestroyed = true;
1440            // Make sure printer tracking is stopped.
1441            final int printerCount = mStateTrackedPrinters.size();
1442            for (int i = 0; i < printerCount; i++) {
1443                PrinterId printerId = mStateTrackedPrinters.get(i);
1444                stopPrinterStateTracking(printerId);
1445            }
1446            // Make sure discovery is stopped.
1447            final int observerCount = mStartedPrinterDiscoveryTokens.size();
1448            for (int i = 0; i < observerCount; i++) {
1449                IBinder token = mStartedPrinterDiscoveryTokens.get(i);
1450                stopPrinterDiscoveryLocked(IPrinterDiscoveryObserver.Stub.asInterface(token));
1451            }
1452            // Tell the services we are done.
1453            List<RemotePrintService> services = new ArrayList<RemotePrintService>(
1454                    mActiveServices.values());
1455            mSessionHandler.obtainMessage(SessionHandler
1456                    .MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION, services)
1457                    .sendToTarget();
1458        }
1459
1460        public void onPrintersAddedLocked(List<PrinterInfo> printers) {
1461            if (DEBUG) {
1462                Log.i(LOG_TAG, "onPrintersAddedLocked()");
1463            }
1464            if (mIsDestroyed) {
1465                Log.w(LOG_TAG, "Not adding printers - session destroyed");
1466                return;
1467            }
1468            List<PrinterInfo> addedPrinters = null;
1469            final int addedPrinterCount = printers.size();
1470            for (int i = 0; i < addedPrinterCount; i++) {
1471                PrinterInfo printer = printers.get(i);
1472                PrinterInfo oldPrinter = mPrinters.put(printer.getId(), printer);
1473                if (oldPrinter == null || !oldPrinter.equals(printer)) {
1474                    if (addedPrinters == null) {
1475                        addedPrinters = new ArrayList<PrinterInfo>();
1476                    }
1477                    addedPrinters.add(printer);
1478                }
1479            }
1480            if (addedPrinters != null) {
1481                mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED,
1482                        addedPrinters).sendToTarget();
1483            }
1484        }
1485
1486        public void onPrintersRemovedLocked(List<PrinterId> printerIds) {
1487            if (DEBUG) {
1488                Log.i(LOG_TAG, "onPrintersRemovedLocked()");
1489            }
1490            if (mIsDestroyed) {
1491                Log.w(LOG_TAG, "Not removing printers - session destroyed");
1492                return;
1493            }
1494            List<PrinterId> removedPrinterIds = null;
1495            final int removedPrinterCount = printerIds.size();
1496            for (int i = 0; i < removedPrinterCount; i++) {
1497                PrinterId removedPrinterId = printerIds.get(i);
1498                if (mPrinters.remove(removedPrinterId) != null) {
1499                    if (removedPrinterIds == null) {
1500                        removedPrinterIds = new ArrayList<PrinterId>();
1501                    }
1502                    removedPrinterIds.add(removedPrinterId);
1503                }
1504            }
1505            if (removedPrinterIds != null) {
1506                mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED,
1507                        removedPrinterIds).sendToTarget();
1508            }
1509        }
1510
1511        public void onServiceRemovedLocked(RemotePrintService service) {
1512            if (mIsDestroyed) {
1513                Log.w(LOG_TAG, "Not updating removed service - session destroyed");
1514                return;
1515            }
1516            // Remove the reported and tracked printers for that service.
1517            ComponentName serviceName = service.getComponentName();
1518            removePrintersForServiceLocked(serviceName);
1519            service.destroy();
1520        }
1521
1522        /**
1523         * Handle that a custom icon for a printer was loaded.
1524         *
1525         * This increments the icon generation and adds the printer again which triggers an update
1526         * in all users of the currently known printers.
1527         *
1528         * @param printerId the id of the printer the icon belongs to
1529         * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
1530         */
1531        public void onCustomPrinterIconLoadedLocked(PrinterId printerId) {
1532            if (DEBUG) {
1533                Log.i(LOG_TAG, "onCustomPrinterIconLoadedLocked()");
1534            }
1535            if (mIsDestroyed) {
1536                Log.w(LOG_TAG, "Not updating printer - session destroyed");
1537                return;
1538            }
1539
1540            PrinterInfo printer = mPrinters.get(printerId);
1541            if (printer != null) {
1542                PrinterInfo newPrinter = (new PrinterInfo.Builder(printer))
1543                        .incCustomPrinterIconGen().build();
1544                mPrinters.put(printerId, newPrinter);
1545
1546                ArrayList<PrinterInfo> addedPrinters = new ArrayList<>(1);
1547                addedPrinters.add(newPrinter);
1548                mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED,
1549                        addedPrinters).sendToTarget();
1550            }
1551        }
1552
1553        public void onServiceDiedLocked(RemotePrintService service) {
1554            // Remove the reported by that service.
1555            removePrintersForServiceLocked(service.getComponentName());
1556        }
1557
1558        public void onServiceAddedLocked(RemotePrintService service) {
1559            if (mIsDestroyed) {
1560                Log.w(LOG_TAG, "Not updating added service - session destroyed");
1561                return;
1562            }
1563            // Tell the service to create a session.
1564            mSessionHandler.obtainMessage(
1565                    SessionHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
1566                    service).sendToTarget();
1567            // Start printer discovery if necessary.
1568            if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
1569                mSessionHandler.obtainMessage(
1570                        SessionHandler.MSG_START_PRINTER_DISCOVERY,
1571                        service).sendToTarget();
1572            }
1573            // Start tracking printers if necessary
1574            final int trackedPrinterCount = mStateTrackedPrinters.size();
1575            for (int i = 0; i < trackedPrinterCount; i++) {
1576                PrinterId printerId = mStateTrackedPrinters.get(i);
1577                if (printerId.getServiceName().equals(service.getComponentName())) {
1578                    SomeArgs args = SomeArgs.obtain();
1579                    args.arg1 = service;
1580                    args.arg2 = printerId;
1581                    mSessionHandler.obtainMessage(SessionHandler
1582                            .MSG_START_PRINTER_STATE_TRACKING, args)
1583                            .sendToTarget();
1584                }
1585            }
1586        }
1587
1588        public void dump(PrintWriter pw, String prefix) {
1589            pw.append(prefix).append("destroyed=")
1590                    .append(String.valueOf(mDestroyed)).println();
1591
1592            pw.append(prefix).append("printDiscoveryInProgress=")
1593                    .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println();
1594
1595            String tab = "  ";
1596
1597            pw.append(prefix).append(tab).append("printer discovery observers:").println();
1598            final int observerCount = mDiscoveryObservers.beginBroadcast();
1599            for (int i = 0; i < observerCount; i++) {
1600                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1601                pw.append(prefix).append(prefix).append(observer.toString());
1602                pw.println();
1603            }
1604            mDiscoveryObservers.finishBroadcast();
1605
1606            pw.append(prefix).append(tab).append("start discovery requests:").println();
1607            final int tokenCount = this.mStartedPrinterDiscoveryTokens.size();
1608            for (int i = 0; i < tokenCount; i++) {
1609                IBinder token = mStartedPrinterDiscoveryTokens.get(i);
1610                pw.append(prefix).append(tab).append(tab).append(token.toString()).println();
1611            }
1612
1613            pw.append(prefix).append(tab).append("tracked printer requests:").println();
1614            final int trackedPrinters = mStateTrackedPrinters.size();
1615            for (int i = 0; i < trackedPrinters; i++) {
1616                PrinterId printer = mStateTrackedPrinters.get(i);
1617                pw.append(prefix).append(tab).append(tab).append(printer.toString()).println();
1618            }
1619
1620            pw.append(prefix).append(tab).append("printers:").println();
1621            final int pritnerCount = mPrinters.size();
1622            for (int i = 0; i < pritnerCount; i++) {
1623                PrinterInfo printer = mPrinters.valueAt(i);
1624                pw.append(prefix).append(tab).append(tab).append(
1625                        printer.toString()).println();
1626            }
1627        }
1628
1629        private void removePrintersForServiceLocked(ComponentName serviceName) {
1630            // No printers - nothing to do.
1631            if (mPrinters.isEmpty()) {
1632                return;
1633            }
1634            // Remove the printers for that service.
1635            List<PrinterId> removedPrinterIds = null;
1636            final int printerCount = mPrinters.size();
1637            for (int i = 0; i < printerCount; i++) {
1638                PrinterId printerId = mPrinters.keyAt(i);
1639                if (printerId.getServiceName().equals(serviceName)) {
1640                    if (removedPrinterIds == null) {
1641                        removedPrinterIds = new ArrayList<PrinterId>();
1642                    }
1643                    removedPrinterIds.add(printerId);
1644                }
1645            }
1646            if (removedPrinterIds != null) {
1647                final int removedPrinterCount = removedPrinterIds.size();
1648                for (int i = 0; i < removedPrinterCount; i++) {
1649                    mPrinters.remove(removedPrinterIds.get(i));
1650                }
1651                mSessionHandler.obtainMessage(
1652                        SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED,
1653                        removedPrinterIds).sendToTarget();
1654            }
1655        }
1656
1657        private void handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters) {
1658            final int observerCount = mDiscoveryObservers.beginBroadcast();
1659            for (int i = 0; i < observerCount; i++) {
1660                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1661                handlePrintersAdded(observer, addedPrinters);
1662            }
1663            mDiscoveryObservers.finishBroadcast();
1664        }
1665
1666        private void handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds) {
1667            final int observerCount = mDiscoveryObservers.beginBroadcast();
1668            for (int i = 0; i < observerCount; i++) {
1669                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1670                handlePrintersRemoved(observer, removedPrinterIds);
1671            }
1672            mDiscoveryObservers.finishBroadcast();
1673        }
1674
1675        private void handleDispatchCreatePrinterDiscoverySession(
1676                List<RemotePrintService> services) {
1677            final int serviceCount = services.size();
1678            for (int i = 0; i < serviceCount; i++) {
1679                RemotePrintService service = services.get(i);
1680                service.createPrinterDiscoverySession();
1681            }
1682        }
1683
1684        private void handleDispatchDestroyPrinterDiscoverySession(
1685                List<RemotePrintService> services) {
1686            final int serviceCount = services.size();
1687            for (int i = 0; i < serviceCount; i++) {
1688                RemotePrintService service = services.get(i);
1689                service.destroyPrinterDiscoverySession();
1690            }
1691            onDestroyed();
1692        }
1693
1694        private void handleDispatchStartPrinterDiscovery(
1695                List<RemotePrintService> services, List<PrinterId> printerIds) {
1696            final int serviceCount = services.size();
1697            for (int i = 0; i < serviceCount; i++) {
1698                RemotePrintService service = services.get(i);
1699                service.startPrinterDiscovery(printerIds);
1700            }
1701        }
1702
1703        private void handleDispatchStopPrinterDiscovery(List<RemotePrintService> services) {
1704            final int serviceCount = services.size();
1705            for (int i = 0; i < serviceCount; i++) {
1706                RemotePrintService service = services.get(i);
1707                service.stopPrinterDiscovery();
1708            }
1709        }
1710
1711        private void handleValidatePrinters(RemotePrintService service,
1712                List<PrinterId> printerIds) {
1713            service.validatePrinters(printerIds);
1714        }
1715
1716        private void handleStartPrinterStateTracking(@NonNull RemotePrintService service,
1717                @NonNull PrinterId printerId) {
1718            service.startPrinterStateTracking(printerId);
1719        }
1720
1721        private void handleStopPrinterStateTracking(RemotePrintService service,
1722                PrinterId printerId) {
1723            service.stopPrinterStateTracking(printerId);
1724        }
1725
1726        private void handlePrintersAdded(IPrinterDiscoveryObserver observer,
1727            List<PrinterInfo> printers) {
1728            try {
1729                observer.onPrintersAdded(new ParceledListSlice<PrinterInfo>(printers));
1730            } catch (RemoteException re) {
1731                Log.e(LOG_TAG, "Error sending added printers", re);
1732            }
1733        }
1734
1735        private void handlePrintersRemoved(IPrinterDiscoveryObserver observer,
1736            List<PrinterId> printerIds) {
1737            try {
1738                observer.onPrintersRemoved(new ParceledListSlice<PrinterId>(printerIds));
1739            } catch (RemoteException re) {
1740                Log.e(LOG_TAG, "Error sending removed printers", re);
1741            }
1742        }
1743
1744        private final class SessionHandler extends Handler {
1745            public static final int MSG_PRINTERS_ADDED = 1;
1746            public static final int MSG_PRINTERS_REMOVED = 2;
1747            public static final int MSG_DISPATCH_PRINTERS_ADDED = 3;
1748            public static final int MSG_DISPATCH_PRINTERS_REMOVED = 4;
1749
1750            public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 5;
1751            public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 6;
1752            public static final int MSG_START_PRINTER_DISCOVERY = 7;
1753            public static final int MSG_STOP_PRINTER_DISCOVERY = 8;
1754            public static final int MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION = 9;
1755            public static final int MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION = 10;
1756            public static final int MSG_DISPATCH_START_PRINTER_DISCOVERY = 11;
1757            public static final int MSG_DISPATCH_STOP_PRINTER_DISCOVERY = 12;
1758            public static final int MSG_VALIDATE_PRINTERS = 13;
1759            public static final int MSG_START_PRINTER_STATE_TRACKING = 14;
1760            public static final int MSG_STOP_PRINTER_STATE_TRACKING = 15;
1761            public static final int MSG_DESTROY_SERVICE = 16;
1762
1763            SessionHandler(Looper looper) {
1764                super(looper, null, false);
1765            }
1766
1767            @Override
1768            @SuppressWarnings("unchecked")
1769            public void handleMessage(Message message) {
1770                switch (message.what) {
1771                    case MSG_PRINTERS_ADDED: {
1772                        SomeArgs args = (SomeArgs) message.obj;
1773                        IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1;
1774                        List<PrinterInfo> addedPrinters = (List<PrinterInfo>) args.arg2;
1775                        args.recycle();
1776                        handlePrintersAdded(observer, addedPrinters);
1777                    } break;
1778
1779                    case MSG_PRINTERS_REMOVED: {
1780                        SomeArgs args = (SomeArgs) message.obj;
1781                        IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1;
1782                        List<PrinterId> removedPrinterIds = (List<PrinterId>) args.arg2;
1783                        args.recycle();
1784                        handlePrintersRemoved(observer, removedPrinterIds);
1785                    }
1786
1787                    case MSG_DISPATCH_PRINTERS_ADDED: {
1788                        List<PrinterInfo> addedPrinters = (List<PrinterInfo>) message.obj;
1789                        handleDispatchPrintersAdded(addedPrinters);
1790                    } break;
1791
1792                    case MSG_DISPATCH_PRINTERS_REMOVED: {
1793                        List<PrinterId> removedPrinterIds = (List<PrinterId>) message.obj;
1794                        handleDispatchPrintersRemoved(removedPrinterIds);
1795                    } break;
1796
1797                    case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
1798                        RemotePrintService service = (RemotePrintService) message.obj;
1799                        service.createPrinterDiscoverySession();
1800                    } break;
1801
1802                    case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
1803                        RemotePrintService service = (RemotePrintService) message.obj;
1804                        service.destroyPrinterDiscoverySession();
1805                    } break;
1806
1807                    case MSG_START_PRINTER_DISCOVERY: {
1808                        RemotePrintService service = (RemotePrintService) message.obj;
1809                        service.startPrinterDiscovery(null);
1810                    } break;
1811
1812                    case MSG_STOP_PRINTER_DISCOVERY: {
1813                        RemotePrintService service = (RemotePrintService) message.obj;
1814                        service.stopPrinterDiscovery();
1815                    } break;
1816
1817                    case MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION: {
1818                        List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
1819                        handleDispatchCreatePrinterDiscoverySession(services);
1820                    } break;
1821
1822                    case MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION: {
1823                        List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
1824                        handleDispatchDestroyPrinterDiscoverySession(services);
1825                    } break;
1826
1827                    case MSG_DISPATCH_START_PRINTER_DISCOVERY: {
1828                        SomeArgs args = (SomeArgs) message.obj;
1829                        List<RemotePrintService> services = (List<RemotePrintService>) args.arg1;
1830                        List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
1831                        args.recycle();
1832                        handleDispatchStartPrinterDiscovery(services, printerIds);
1833                    } break;
1834
1835                    case MSG_DISPATCH_STOP_PRINTER_DISCOVERY: {
1836                        List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
1837                        handleDispatchStopPrinterDiscovery(services);
1838                    } break;
1839
1840                    case MSG_VALIDATE_PRINTERS: {
1841                        SomeArgs args = (SomeArgs) message.obj;
1842                        RemotePrintService service = (RemotePrintService) args.arg1;
1843                        List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
1844                        args.recycle();
1845                        handleValidatePrinters(service, printerIds);
1846                    } break;
1847
1848                    case MSG_START_PRINTER_STATE_TRACKING: {
1849                        SomeArgs args = (SomeArgs) message.obj;
1850                        RemotePrintService service = (RemotePrintService) args.arg1;
1851                        PrinterId printerId = (PrinterId) args.arg2;
1852                        args.recycle();
1853                        handleStartPrinterStateTracking(service, printerId);
1854                    } break;
1855
1856                    case MSG_STOP_PRINTER_STATE_TRACKING: {
1857                        SomeArgs args = (SomeArgs) message.obj;
1858                        RemotePrintService service = (RemotePrintService) args.arg1;
1859                        PrinterId printerId = (PrinterId) args.arg2;
1860                        args.recycle();
1861                        handleStopPrinterStateTracking(service, printerId);
1862                    } break;
1863
1864                    case MSG_DESTROY_SERVICE: {
1865                        RemotePrintService service = (RemotePrintService) message.obj;
1866                        service.destroy();
1867                    } break;
1868                }
1869            }
1870        }
1871    }
1872
1873    private final class PrintJobForAppCache {
1874        private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp =
1875                new SparseArray<List<PrintJobInfo>>();
1876
1877        public boolean onPrintJobCreated(final IBinder creator, final int appId,
1878                PrintJobInfo printJob) {
1879            try {
1880                creator.linkToDeath(new DeathRecipient() {
1881                    @Override
1882                    public void binderDied() {
1883                        creator.unlinkToDeath(this, 0);
1884                        synchronized (mLock) {
1885                            mPrintJobsForRunningApp.remove(appId);
1886                        }
1887                    }
1888                }, 0);
1889            } catch (RemoteException re) {
1890                /* The process is already dead - we just failed. */
1891                return false;
1892            }
1893            synchronized (mLock) {
1894                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
1895                if (printJobsForApp == null) {
1896                    printJobsForApp = new ArrayList<PrintJobInfo>();
1897                    mPrintJobsForRunningApp.put(appId, printJobsForApp);
1898                }
1899                printJobsForApp.add(printJob);
1900            }
1901            return true;
1902        }
1903
1904        public void onPrintJobStateChanged(PrintJobInfo printJob) {
1905            synchronized (mLock) {
1906                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(
1907                        printJob.getAppId());
1908                if (printJobsForApp == null) {
1909                    return;
1910                }
1911                final int printJobCount = printJobsForApp.size();
1912                for (int i = 0; i < printJobCount; i++) {
1913                    PrintJobInfo oldPrintJob = printJobsForApp.get(i);
1914                    if (oldPrintJob.getId().equals(printJob.getId())) {
1915                        printJobsForApp.set(i, printJob);
1916                    }
1917                }
1918            }
1919        }
1920
1921        public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) {
1922            synchronized (mLock) {
1923                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
1924                if (printJobsForApp == null) {
1925                    return null;
1926                }
1927                final int printJobCount = printJobsForApp.size();
1928                for (int i = 0; i < printJobCount; i++) {
1929                    PrintJobInfo printJob = printJobsForApp.get(i);
1930                    if (printJob.getId().equals(printJobId)) {
1931                        return printJob;
1932                    }
1933                }
1934            }
1935            return null;
1936        }
1937
1938        public List<PrintJobInfo> getPrintJobs(int appId) {
1939            synchronized (mLock) {
1940                List<PrintJobInfo> printJobs = null;
1941                if (appId == PrintManager.APP_ID_ANY) {
1942                    final int bucketCount = mPrintJobsForRunningApp.size();
1943                    for (int i = 0; i < bucketCount; i++) {
1944                        List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
1945                        if (printJobs == null) {
1946                            printJobs = new ArrayList<PrintJobInfo>();
1947                        }
1948                        printJobs.addAll(bucket);
1949                    }
1950                } else {
1951                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId);
1952                    if (bucket != null) {
1953                        if (printJobs == null) {
1954                            printJobs = new ArrayList<PrintJobInfo>();
1955                        }
1956                        printJobs.addAll(bucket);
1957                    }
1958                }
1959                if (printJobs != null) {
1960                    return printJobs;
1961                }
1962                return Collections.emptyList();
1963            }
1964        }
1965
1966        public void dump(PrintWriter pw, String prefix) {
1967            synchronized (mLock) {
1968                String tab = "  ";
1969                final int bucketCount = mPrintJobsForRunningApp.size();
1970                for (int i = 0; i < bucketCount; i++) {
1971                    final int appId = mPrintJobsForRunningApp.keyAt(i);
1972                    pw.append(prefix).append("appId=" + appId).append(':').println();
1973                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
1974                    final int printJobCount = bucket.size();
1975                    for (int j = 0; j < printJobCount; j++) {
1976                        PrintJobInfo printJob = bucket.get(j);
1977                        pw.append(prefix).append(tab).append(printJob.toString()).println();
1978                    }
1979                }
1980            }
1981        }
1982    }
1983}
1984