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