• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.printspooler.ui;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Loader;
22 import android.content.pm.ServiceInfo;
23 import android.os.AsyncTask;
24 import android.print.PrintManager;
25 import android.print.PrinterDiscoverySession;
26 import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
27 import android.print.PrinterId;
28 import android.print.PrinterInfo;
29 import android.printservice.PrintServiceInfo;
30 import android.text.TextUtils;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 import android.util.AtomicFile;
34 import android.util.Log;
35 import android.util.Slog;
36 import android.util.Xml;
37 
38 import com.android.internal.util.FastXmlSerializer;
39 
40 import org.xmlpull.v1.XmlPullParser;
41 import org.xmlpull.v1.XmlPullParserException;
42 import org.xmlpull.v1.XmlSerializer;
43 
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.nio.charset.StandardCharsets;
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.LinkedHashMap;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 
57 import libcore.io.IoUtils;
58 
59 /**
60  * This class is responsible for loading printers by doing discovery
61  * and merging the discovered printers with the previously used ones.
62  */
63 public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
64     private static final String LOG_TAG = "FusedPrintersProvider";
65 
66     private static final boolean DEBUG = false;
67 
68     private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
69     private static final int MAX_HISTORY_LENGTH = 50;
70 
71     private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
72 
73     private final List<PrinterInfo> mPrinters =
74             new ArrayList<>();
75 
76     private final List<PrinterInfo> mFavoritePrinters =
77             new ArrayList<>();
78 
79     private final PersistenceManager mPersistenceManager;
80 
81     private PrinterDiscoverySession mDiscoverySession;
82 
83     private PrinterId mTrackedPrinter;
84 
85     private boolean mPrintersUpdatedBefore;
86 
FusedPrintersProvider(Context context)87     public FusedPrintersProvider(Context context) {
88         super(context);
89         mPersistenceManager = new PersistenceManager(context);
90     }
91 
addHistoricalPrinter(PrinterInfo printer)92     public void addHistoricalPrinter(PrinterInfo printer) {
93         mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
94     }
95 
computeAndDeliverResult(Map<PrinterId, PrinterInfo> discoveredPrinters, List<PrinterInfo> favoritePrinters)96     private void computeAndDeliverResult(Map<PrinterId, PrinterInfo> discoveredPrinters,
97             List<PrinterInfo> favoritePrinters) {
98         List<PrinterInfo> printers = new ArrayList<>();
99 
100         // Add the updated favorite printers.
101         final int favoritePrinterCount = favoritePrinters.size();
102         for (int i = 0; i < favoritePrinterCount; i++) {
103             PrinterInfo favoritePrinter = favoritePrinters.get(i);
104             PrinterInfo updatedPrinter = discoveredPrinters.remove(
105                     favoritePrinter.getId());
106             if (updatedPrinter != null) {
107                 printers.add(updatedPrinter);
108             } else {
109                 printers.add(favoritePrinter);
110             }
111         }
112 
113         // Add other updated printers.
114         final int printerCount = mPrinters.size();
115         for (int i = 0; i < printerCount; i++) {
116             PrinterInfo printer = mPrinters.get(i);
117             PrinterInfo updatedPrinter = discoveredPrinters.remove(
118                     printer.getId());
119             if (updatedPrinter != null) {
120                 printers.add(updatedPrinter);
121             }
122         }
123 
124         // Add the new printers, i.e. what is left.
125         printers.addAll(discoveredPrinters.values());
126 
127         // Update the list of printers.
128         mPrinters.clear();
129         mPrinters.addAll(printers);
130 
131         if (isStarted()) {
132             // If stated deliver the new printers.
133             deliverResult(printers);
134         } else {
135             // Otherwise, take a note for the change.
136             onContentChanged();
137         }
138     }
139 
140     @Override
onStartLoading()141     protected void onStartLoading() {
142         if (DEBUG) {
143             Log.i(LOG_TAG, "onStartLoading() " + FusedPrintersProvider.this.hashCode());
144         }
145         // The contract is that if we already have a valid,
146         // result the we have to deliver it immediately.
147         if (!mPrinters.isEmpty()) {
148             deliverResult(new ArrayList<>(mPrinters));
149         }
150         // Always load the data to ensure discovery period is
151         // started and to make sure obsolete printers are updated.
152         onForceLoad();
153     }
154 
155     @Override
onStopLoading()156     protected void onStopLoading() {
157         if (DEBUG) {
158             Log.i(LOG_TAG, "onStopLoading() " + FusedPrintersProvider.this.hashCode());
159         }
160         onCancelLoad();
161     }
162 
163     @Override
onForceLoad()164     protected void onForceLoad() {
165         if (DEBUG) {
166             Log.i(LOG_TAG, "onForceLoad() " + FusedPrintersProvider.this.hashCode());
167         }
168         loadInternal();
169     }
170 
loadInternal()171     private void loadInternal() {
172         if (mDiscoverySession == null) {
173             PrintManager printManager = (PrintManager) getContext()
174                     .getSystemService(Context.PRINT_SERVICE);
175             mDiscoverySession = printManager.createPrinterDiscoverySession();
176             mPersistenceManager.readPrinterHistory();
177         } else if (mPersistenceManager.isHistoryChanged()) {
178             mPersistenceManager.readPrinterHistory();
179         }
180         if (mPersistenceManager.isReadHistoryCompleted()
181                 && !mDiscoverySession.isPrinterDiscoveryStarted()) {
182             mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() {
183                 @Override
184                 public void onPrintersChanged() {
185                     if (DEBUG) {
186                         Log.i(LOG_TAG, "onPrintersChanged() count:"
187                                 + mDiscoverySession.getPrinters().size()
188                                 + " " + FusedPrintersProvider.this.hashCode());
189                     }
190 
191                     updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
192                 }
193             });
194             final int favoriteCount = mFavoritePrinters.size();
195             List<PrinterId> printerIds = new ArrayList<>(favoriteCount);
196             for (int i = 0; i < favoriteCount; i++) {
197                 printerIds.add(mFavoritePrinters.get(i).getId());
198             }
199             mDiscoverySession.startPrinterDiscovery(printerIds);
200             List<PrinterInfo> printers = mDiscoverySession.getPrinters();
201             if (!printers.isEmpty()) {
202                 updatePrinters(printers, mFavoritePrinters);
203             }
204         }
205     }
206 
updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters)207     private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) {
208         if (mPrintersUpdatedBefore && mPrinters.equals(printers)
209                 && mFavoritePrinters.equals(favoritePrinters)) {
210             return;
211         }
212 
213         mPrintersUpdatedBefore = true;
214 
215         // Some of the found printers may have be a printer that is in the
216         // history but with its name changed. Hence, we try to update the
217         // printer to use its current name instead of the historical one.
218         mPersistenceManager.updatePrintersHistoricalNamesIfNeeded(printers);
219 
220         Map<PrinterId, PrinterInfo> printersMap = new LinkedHashMap<>();
221         final int printerCount = printers.size();
222         for (int i = 0; i < printerCount; i++) {
223             PrinterInfo printer = printers.get(i);
224             printersMap.put(printer.getId(), printer);
225         }
226 
227         computeAndDeliverResult(printersMap, favoritePrinters);
228     }
229 
230     @Override
onCancelLoad()231     protected boolean onCancelLoad() {
232         if (DEBUG) {
233             Log.i(LOG_TAG, "onCancelLoad() " + FusedPrintersProvider.this.hashCode());
234         }
235         return cancelInternal();
236     }
237 
cancelInternal()238     private boolean cancelInternal() {
239         if (mDiscoverySession != null
240                 && mDiscoverySession.isPrinterDiscoveryStarted()) {
241             if (mTrackedPrinter != null) {
242                 mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
243                 mTrackedPrinter = null;
244             }
245             mDiscoverySession.stopPrinterDiscovery();
246             return true;
247         } else if (mPersistenceManager.isReadHistoryInProgress()) {
248             return mPersistenceManager.stopReadPrinterHistory();
249         }
250         return false;
251     }
252 
253     @Override
onReset()254     protected void onReset() {
255         if (DEBUG) {
256             Log.i(LOG_TAG, "onReset() " + FusedPrintersProvider.this.hashCode());
257         }
258         onStopLoading();
259         mPrinters.clear();
260         if (mDiscoverySession != null) {
261             mDiscoverySession.destroy();
262             mDiscoverySession = null;
263         }
264     }
265 
266     @Override
onAbandon()267     protected void onAbandon() {
268         if (DEBUG) {
269             Log.i(LOG_TAG, "onAbandon() " + FusedPrintersProvider.this.hashCode());
270         }
271         onStopLoading();
272     }
273 
areHistoricalPrintersLoaded()274     public boolean areHistoricalPrintersLoaded() {
275         return mPersistenceManager.mReadHistoryCompleted;
276     }
277 
setTrackedPrinter(PrinterId printerId)278     public void setTrackedPrinter(PrinterId printerId) {
279         if (isStarted() && mDiscoverySession != null
280                 && mDiscoverySession.isPrinterDiscoveryStarted()) {
281             if (mTrackedPrinter != null) {
282                 if (mTrackedPrinter.equals(printerId)) {
283                     return;
284                 }
285                 mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
286             }
287             mTrackedPrinter = printerId;
288             if (printerId != null) {
289                 mDiscoverySession.startPrinterStateTracking(printerId);
290             }
291         }
292     }
293 
isFavoritePrinter(PrinterId printerId)294     public boolean isFavoritePrinter(PrinterId printerId) {
295         final int printerCount = mFavoritePrinters.size();
296         for (int i = 0; i < printerCount; i++) {
297             PrinterInfo favoritePritner = mFavoritePrinters.get(i);
298             if (favoritePritner.getId().equals(printerId)) {
299                 return true;
300             }
301         }
302         return false;
303     }
304 
forgetFavoritePrinter(PrinterId printerId)305     public void forgetFavoritePrinter(PrinterId printerId) {
306         List<PrinterInfo> newFavoritePrinters = null;
307 
308         // Remove the printer from the favorites.
309         final int favoritePrinterCount = mFavoritePrinters.size();
310         for (int i = 0; i < favoritePrinterCount; i++) {
311             PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
312             if (favoritePrinter.getId().equals(printerId)) {
313                 newFavoritePrinters = new ArrayList<>();
314                 newFavoritePrinters.addAll(mPrinters);
315                 newFavoritePrinters.remove(i);
316                 break;
317             }
318         }
319 
320         // If we removed a favorite printer, we have work to do.
321         if (newFavoritePrinters != null) {
322             // Remove the printer from history and persist the latter.
323             mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId);
324 
325             // Recompute and deliver the printers.
326             updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters);
327         }
328     }
329 
330     private final class PersistenceManager {
331         private static final String PERSIST_FILE_NAME = "printer_history.xml";
332 
333         private static final String TAG_PRINTERS = "printers";
334 
335         private static final String TAG_PRINTER = "printer";
336         private static final String TAG_PRINTER_ID = "printerId";
337 
338         private static final String ATTR_LOCAL_ID = "localId";
339         private static final String ATTR_SERVICE_NAME = "serviceName";
340 
341         private static final String ATTR_NAME = "name";
342         private static final String ATTR_DESCRIPTION = "description";
343         private static final String ATTR_STATUS = "status";
344 
345         private final AtomicFile mStatePersistFile;
346 
347         private List<PrinterInfo> mHistoricalPrinters = new ArrayList<>();
348 
349         private boolean mReadHistoryCompleted;
350 
351         private ReadTask mReadTask;
352 
353         private volatile long mLastReadHistoryTimestamp;
354 
PersistenceManager(Context context)355         private PersistenceManager(Context context) {
356             mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
357                     PERSIST_FILE_NAME));
358         }
359 
isReadHistoryInProgress()360         public boolean isReadHistoryInProgress() {
361             return mReadTask != null;
362         }
363 
isReadHistoryCompleted()364         public boolean isReadHistoryCompleted() {
365             return mReadHistoryCompleted;
366         }
367 
stopReadPrinterHistory()368         public boolean stopReadPrinterHistory() {
369             return mReadTask.cancel(true);
370         }
371 
readPrinterHistory()372         public void readPrinterHistory() {
373             if (DEBUG) {
374                 Log.i(LOG_TAG, "read history started "
375                         + FusedPrintersProvider.this.hashCode());
376             }
377             mReadTask = new ReadTask();
378             mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
379         }
380 
updatePrintersHistoricalNamesIfNeeded(List<PrinterInfo> printers)381         public void updatePrintersHistoricalNamesIfNeeded(List<PrinterInfo> printers) {
382             boolean writeHistory = false;
383 
384             final int printerCount = printers.size();
385             for (int i = 0; i < printerCount; i++) {
386                 PrinterInfo printer = printers.get(i);
387                 writeHistory |= renamePrinterIfNeeded(printer);
388             }
389 
390             if (writeHistory) {
391                 writePrinterHistory();
392             }
393         }
394 
renamePrinterIfNeeded(PrinterInfo printer)395         public boolean renamePrinterIfNeeded(PrinterInfo printer) {
396             boolean renamed = false;
397             final int printerCount = mHistoricalPrinters.size();
398             for (int i = 0; i < printerCount; i++) {
399                 PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
400                 if (historicalPrinter.getId().equals(printer.getId())
401                         && !TextUtils.equals(historicalPrinter.getName(), printer.getName())) {
402                     mHistoricalPrinters.set(i, printer);
403                     renamed = true;
404                 }
405             }
406             return renamed;
407         }
408 
addPrinterAndWritePrinterHistory(PrinterInfo printer)409         public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
410             if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
411                 mHistoricalPrinters.remove(0);
412             }
413             mHistoricalPrinters.add(printer);
414             writePrinterHistory();
415         }
416 
removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId)417         public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
418             boolean writeHistory = false;
419             final int printerCount = mHistoricalPrinters.size();
420             for (int i = printerCount - 1; i >= 0; i--) {
421                 PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
422                 if (historicalPrinter.getId().equals(printerId)) {
423                     mHistoricalPrinters.remove(i);
424                     writeHistory = true;
425                 }
426             }
427             if (writeHistory) {
428                 writePrinterHistory();
429             }
430         }
431 
432         @SuppressWarnings("unchecked")
writePrinterHistory()433         private void writePrinterHistory() {
434             new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
435                     new ArrayList<>(mHistoricalPrinters));
436         }
437 
isHistoryChanged()438         public boolean isHistoryChanged() {
439             return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
440         }
441 
computeFavoritePrinters(List<PrinterInfo> printers)442         private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
443             Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<>();
444 
445             // Recompute the weights.
446             float currentWeight = 1.0f;
447             final int printerCount = printers.size();
448             for (int i = printerCount - 1; i >= 0; i--) {
449                 PrinterInfo printer = printers.get(i);
450                 // Aggregate weight for the same printer
451                 PrinterRecord record = recordMap.get(printer.getId());
452                 if (record == null) {
453                     record = new PrinterRecord(printer);
454                     recordMap.put(printer.getId(), record);
455                 }
456                 record.weight += currentWeight;
457                 currentWeight *= WEIGHT_DECAY_COEFFICIENT;
458             }
459 
460             // Soft the favorite printers.
461             List<PrinterRecord> favoriteRecords = new ArrayList<>(
462                     recordMap.values());
463             Collections.sort(favoriteRecords);
464 
465             // Write the favorites to the output.
466             final int favoriteCount = Math.min(favoriteRecords.size(),
467                     MAX_FAVORITE_PRINTER_COUNT);
468             List<PrinterInfo> favoritePrinters = new ArrayList<>(favoriteCount);
469             for (int i = 0; i < favoriteCount; i++) {
470                 PrinterInfo printer = favoriteRecords.get(i).printer;
471                 favoritePrinters.add(printer);
472             }
473 
474             return favoritePrinters;
475         }
476 
477         private final class PrinterRecord implements Comparable<PrinterRecord> {
478             public final PrinterInfo printer;
479             public float weight;
480 
PrinterRecord(PrinterInfo printer)481             public PrinterRecord(PrinterInfo printer) {
482                 this.printer = printer;
483             }
484 
485             @Override
compareTo(PrinterRecord another)486             public int compareTo(PrinterRecord another) {
487                 return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
488             }
489         }
490 
491         private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> {
492             @Override
doInBackground(Void... args)493             protected List<PrinterInfo> doInBackground(Void... args) {
494                return doReadPrinterHistory();
495             }
496 
497             @Override
onPostExecute(List<PrinterInfo> printers)498             protected void onPostExecute(List<PrinterInfo> printers) {
499                 if (DEBUG) {
500                     Log.i(LOG_TAG, "read history completed "
501                             + FusedPrintersProvider.this.hashCode());
502                 }
503 
504                 // Ignore printer records whose target services are not enabled.
505                 PrintManager printManager = (PrintManager) getContext()
506                         .getSystemService(Context.PRINT_SERVICE);
507                 List<PrintServiceInfo> services = printManager
508                         .getEnabledPrintServices();
509 
510                 Set<ComponentName> enabledComponents = new ArraySet<>();
511                 final int installedServiceCount = services.size();
512                 for (int i = 0; i < installedServiceCount; i++) {
513                     ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
514                     ComponentName componentName = new ComponentName(
515                             serviceInfo.packageName, serviceInfo.name);
516                     enabledComponents.add(componentName);
517                 }
518 
519                 final int printerCount = printers.size();
520                 for (int i = printerCount - 1; i >= 0; i--) {
521                     ComponentName printerServiceName = printers.get(i).getId().getServiceName();
522                     if (!enabledComponents.contains(printerServiceName)) {
523                         printers.remove(i);
524                     }
525                 }
526 
527                 // Store the filtered list.
528                 mHistoricalPrinters = printers;
529 
530                 // Compute the favorite printers.
531                 mFavoritePrinters.clear();
532                 mFavoritePrinters.addAll(computeFavoritePrinters(mHistoricalPrinters));
533 
534                 mReadHistoryCompleted = true;
535 
536                 // Deliver the printers.
537                 updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
538 
539                 // We are done.
540                 mReadTask = null;
541 
542                 // Loading the available printers if needed.
543                 loadInternal();
544             }
545 
546             @Override
onCancelled(List<PrinterInfo> printerInfos)547             protected void onCancelled(List<PrinterInfo> printerInfos) {
548                 // We are done.
549                 mReadTask = null;
550             }
551 
doReadPrinterHistory()552             private List<PrinterInfo> doReadPrinterHistory() {
553                 final FileInputStream in;
554                 try {
555                     in = mStatePersistFile.openRead();
556                 } catch (FileNotFoundException fnfe) {
557                     if (DEBUG) {
558                         Log.i(LOG_TAG, "No existing printer history "
559                                 + FusedPrintersProvider.this.hashCode());
560                     }
561                     return new ArrayList<>();
562                 }
563                 try {
564                     List<PrinterInfo> printers = new ArrayList<>();
565                     XmlPullParser parser = Xml.newPullParser();
566                     parser.setInput(in, StandardCharsets.UTF_8.name());
567                     parseState(parser, printers);
568                     // Take a note which version of the history was read.
569                     mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
570                     return printers;
571                 } catch (IllegalStateException
572                         | NullPointerException
573                         | NumberFormatException
574                         | XmlPullParserException
575                         | IOException
576                         | IndexOutOfBoundsException e) {
577                     Slog.w(LOG_TAG, "Failed parsing ", e);
578                 } finally {
579                     IoUtils.closeQuietly(in);
580                 }
581 
582                 return Collections.emptyList();
583             }
584 
parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)585             private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)
586                     throws IOException, XmlPullParserException {
587                 parser.next();
588                 skipEmptyTextTags(parser);
589                 expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
590                 parser.next();
591 
592                 while (parsePrinter(parser, outPrinters)) {
593                     // Be nice and respond to cancellation
594                     if (isCancelled()) {
595                         return;
596                     }
597                     parser.next();
598                 }
599 
600                 skipEmptyTextTags(parser);
601                 expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
602             }
603 
parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)604             private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
605                     throws IOException, XmlPullParserException {
606                 skipEmptyTextTags(parser);
607                 if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
608                     return false;
609                 }
610 
611                 String name = parser.getAttributeValue(null, ATTR_NAME);
612                 String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
613                 final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
614 
615                 parser.next();
616 
617                 skipEmptyTextTags(parser);
618                 expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
619                 String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
620                 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
621                         null, ATTR_SERVICE_NAME));
622                 PrinterId printerId =  new PrinterId(service, localId);
623                 parser.next();
624                 skipEmptyTextTags(parser);
625                 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
626                 parser.next();
627 
628                 PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
629                 builder.setDescription(description);
630                 PrinterInfo printer = builder.build();
631 
632                 outPrinters.add(printer);
633 
634                 if (DEBUG) {
635                     Log.i(LOG_TAG, "[RESTORED] " + printer);
636                 }
637 
638                 skipEmptyTextTags(parser);
639                 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
640 
641                 return true;
642             }
643 
expect(XmlPullParser parser, int type, String tag)644             private void expect(XmlPullParser parser, int type, String tag)
645                     throws IOException, XmlPullParserException {
646                 if (!accept(parser, type, tag)) {
647                     throw new XmlPullParserException("Exepected event: " + type
648                             + " and tag: " + tag + " but got event: " + parser.getEventType()
649                             + " and tag:" + parser.getName());
650                 }
651             }
652 
skipEmptyTextTags(XmlPullParser parser)653             private void skipEmptyTextTags(XmlPullParser parser)
654                     throws IOException, XmlPullParserException {
655                 while (accept(parser, XmlPullParser.TEXT, null)
656                         && "\n".equals(parser.getText())) {
657                     parser.next();
658                 }
659             }
660 
accept(XmlPullParser parser, int type, String tag)661             private boolean accept(XmlPullParser parser, int type, String tag)
662                     throws IOException, XmlPullParserException {
663                 if (parser.getEventType() != type) {
664                     return false;
665                 }
666                 if (tag != null) {
667                     if (!tag.equals(parser.getName())) {
668                         return false;
669                     }
670                 } else if (parser.getName() != null) {
671                     return false;
672                 }
673                 return true;
674             }
675         }
676 
677         private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> {
678             @Override
doInBackground(List<PrinterInfo>.... printers)679             protected Void doInBackground(List<PrinterInfo>... printers) {
680                 doWritePrinterHistory(printers[0]);
681                 return null;
682             }
683 
doWritePrinterHistory(List<PrinterInfo> printers)684             private void doWritePrinterHistory(List<PrinterInfo> printers) {
685                 FileOutputStream out = null;
686                 try {
687                     out = mStatePersistFile.startWrite();
688 
689                     XmlSerializer serializer = new FastXmlSerializer();
690                     serializer.setOutput(out, StandardCharsets.UTF_8.name());
691                     serializer.startDocument(null, true);
692                     serializer.startTag(null, TAG_PRINTERS);
693 
694                     final int printerCount = printers.size();
695                     for (int i = 0; i < printerCount; i++) {
696                         PrinterInfo printer = printers.get(i);
697 
698                         serializer.startTag(null, TAG_PRINTER);
699 
700                         serializer.attribute(null, ATTR_NAME, printer.getName());
701                         // Historical printers are always stored as unavailable.
702                         serializer.attribute(null, ATTR_STATUS, String.valueOf(
703                                 PrinterInfo.STATUS_UNAVAILABLE));
704                         String description = printer.getDescription();
705                         if (description != null) {
706                             serializer.attribute(null, ATTR_DESCRIPTION, description);
707                         }
708 
709                         PrinterId printerId = printer.getId();
710                         serializer.startTag(null, TAG_PRINTER_ID);
711                         serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
712                         serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
713                                 .flattenToString());
714                         serializer.endTag(null, TAG_PRINTER_ID);
715 
716                         serializer.endTag(null, TAG_PRINTER);
717 
718                         if (DEBUG) {
719                             Log.i(LOG_TAG, "[PERSISTED] " + printer);
720                         }
721                     }
722 
723                     serializer.endTag(null, TAG_PRINTERS);
724                     serializer.endDocument();
725                     mStatePersistFile.finishWrite(out);
726 
727                     if (DEBUG) {
728                         Log.i(LOG_TAG, "[PERSIST END]");
729                     }
730                 } catch (IOException ioe) {
731                     Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
732                     mStatePersistFile.failWrite(out);
733                 } finally {
734                     IoUtils.closeQuietly(out);
735                 }
736             }
737         }
738     }
739 }
740