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