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