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