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