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