FusedPrintersProvider.java revision 48fec5c9a3e4d78bc4cd175fae05be153ac587af
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.printspooler.ui; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Loader; 22import android.content.pm.ServiceInfo; 23import android.os.AsyncTask; 24import android.print.PrintManager; 25import android.print.PrinterDiscoverySession; 26import android.print.PrinterDiscoverySession.OnPrintersChangeListener; 27import android.print.PrinterId; 28import android.print.PrinterInfo; 29import android.printservice.PrintServiceInfo; 30import android.util.ArrayMap; 31import android.util.ArraySet; 32import android.util.AtomicFile; 33import android.util.Log; 34import android.util.Slog; 35import android.util.Xml; 36 37import com.android.internal.util.FastXmlSerializer; 38 39import org.xmlpull.v1.XmlPullParser; 40import org.xmlpull.v1.XmlPullParserException; 41import org.xmlpull.v1.XmlSerializer; 42 43import java.io.File; 44import java.io.FileInputStream; 45import java.io.FileNotFoundException; 46import java.io.FileOutputStream; 47import java.io.IOException; 48import java.util.ArrayList; 49import java.util.Collections; 50import java.util.List; 51import java.util.Map; 52import java.util.Set; 53 54import 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 */ 60public final 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.startPrinterDiscovery(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 boolean areHistoricalPrintersLoaded() { 275 return mPersistenceManager.mReadHistoryCompleted; 276 } 277 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 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 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<PrinterInfo>(); 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<PrinterInfo>(); 348 349 private boolean mReadHistoryCompleted; 350 private boolean mReadHistoryInProgress; 351 352 private ReadTask mReadTask; 353 354 private volatile long mLastReadHistoryTimestamp; 355 356 private PersistenceManager(Context context) { 357 mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), 358 PERSIST_FILE_NAME)); 359 } 360 361 public boolean isReadHistoryInProgress() { 362 return mReadHistoryInProgress; 363 } 364 365 public boolean isReadHistoryCompleted() { 366 return mReadHistoryCompleted; 367 } 368 369 public boolean stopReadPrinterHistory() { 370 final boolean cancelled = mReadTask.cancel(true); 371 mReadTask = null; 372 return cancelled; 373 } 374 375 public void readPrinterHistory() { 376 if (DEBUG) { 377 Log.i(LOG_TAG, "read history started " 378 + FusedPrintersProvider.this.hashCode()); 379 } 380 mReadHistoryInProgress = true; 381 mReadTask = new ReadTask(); 382 mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); 383 } 384 385 @SuppressWarnings("unchecked") 386 public void addPrinterAndWritePrinterHistory(PrinterInfo printer) { 387 if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) { 388 mHistoricalPrinters.remove(0); 389 } 390 mHistoricalPrinters.add(printer); 391 new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, 392 new ArrayList<PrinterInfo>(mHistoricalPrinters)); 393 } 394 395 @SuppressWarnings("unchecked") 396 public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) { 397 boolean writeHistory = false; 398 final int printerCount = mHistoricalPrinters.size(); 399 for (int i = printerCount - 1; i >= 0; i--) { 400 PrinterInfo historicalPrinter = mHistoricalPrinters.get(i); 401 if (historicalPrinter.getId().equals(printerId)) { 402 mHistoricalPrinters.remove(i); 403 writeHistory = true; 404 } 405 } 406 if (writeHistory) { 407 new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, 408 new ArrayList<PrinterInfo>(mHistoricalPrinters)); 409 } 410 } 411 412 public boolean isHistoryChanged() { 413 return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified(); 414 } 415 416 private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) { 417 Map<PrinterId, PrinterRecord> recordMap = 418 new ArrayMap<PrinterId, PrinterRecord>(); 419 420 // Recompute the weights. 421 float currentWeight = 1.0f; 422 final int printerCount = printers.size(); 423 for (int i = printerCount - 1; i >= 0; i--) { 424 PrinterInfo printer = printers.get(i); 425 // Aggregate weight for the same printer 426 PrinterRecord record = recordMap.get(printer.getId()); 427 if (record == null) { 428 record = new PrinterRecord(printer); 429 recordMap.put(printer.getId(), record); 430 } 431 record.weight += currentWeight; 432 currentWeight *= WEIGHT_DECAY_COEFFICIENT; 433 } 434 435 // Soft the favorite printers. 436 List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>( 437 recordMap.values()); 438 Collections.sort(favoriteRecords); 439 440 // Write the favorites to the output. 441 final int favoriteCount = Math.min(favoriteRecords.size(), 442 MAX_FAVORITE_PRINTER_COUNT); 443 List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount); 444 for (int i = 0; i < favoriteCount; i++) { 445 PrinterInfo printer = favoriteRecords.get(i).printer; 446 favoritePrinters.add(printer); 447 } 448 449 return favoritePrinters; 450 } 451 452 private final class PrinterRecord implements Comparable<PrinterRecord> { 453 public final PrinterInfo printer; 454 public float weight; 455 456 public PrinterRecord(PrinterInfo printer) { 457 this.printer = printer; 458 } 459 460 @Override 461 public int compareTo(PrinterRecord another) { 462 return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); 463 } 464 } 465 466 private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> { 467 @Override 468 protected List<PrinterInfo> doInBackground(Void... args) { 469 return doReadPrinterHistory(); 470 } 471 472 @Override 473 protected void onPostExecute(List<PrinterInfo> printers) { 474 if (DEBUG) { 475 Log.i(LOG_TAG, "read history completed " 476 + FusedPrintersProvider.this.hashCode()); 477 } 478 479 // Ignore printer records whose target services are not enabled. 480 PrintManager printManager = (PrintManager) getContext() 481 .getSystemService(Context.PRINT_SERVICE); 482 List<PrintServiceInfo> services = printManager 483 .getEnabledPrintServices(); 484 485 Set<ComponentName> enabledComponents = new ArraySet<ComponentName>(); 486 final int installedServiceCount = services.size(); 487 for (int i = 0; i < installedServiceCount; i++) { 488 ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo; 489 ComponentName componentName = new ComponentName( 490 serviceInfo.packageName, serviceInfo.name); 491 enabledComponents.add(componentName); 492 } 493 494 final int printerCount = printers.size(); 495 for (int i = printerCount - 1; i >= 0; i--) { 496 ComponentName printerServiceName = printers.get(i).getId().getServiceName(); 497 if (!enabledComponents.contains(printerServiceName)) { 498 printers.remove(i); 499 } 500 } 501 502 // Store the filtered list. 503 mHistoricalPrinters = printers; 504 505 // Compute the favorite printers. 506 mFavoritePrinters.clear(); 507 mFavoritePrinters.addAll(computeFavoritePrinters(mHistoricalPrinters)); 508 509 mReadHistoryInProgress = false; 510 mReadHistoryCompleted = true; 511 512 // Deliver the printers. 513 updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters); 514 515 // Loading the available printers if needed. 516 loadInternal(); 517 518 // We are done. 519 mReadTask = null; 520 } 521 522 private List<PrinterInfo> doReadPrinterHistory() { 523 final FileInputStream in; 524 try { 525 in = mStatePersistFile.openRead(); 526 } catch (FileNotFoundException fnfe) { 527 if (DEBUG) { 528 Log.i(LOG_TAG, "No existing printer history " 529 + FusedPrintersProvider.this.hashCode()); 530 } 531 return new ArrayList<PrinterInfo>(); 532 } 533 try { 534 List<PrinterInfo> printers = new ArrayList<PrinterInfo>(); 535 XmlPullParser parser = Xml.newPullParser(); 536 parser.setInput(in, null); 537 parseState(parser, printers); 538 // Take a note which version of the history was read. 539 mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified(); 540 return printers; 541 } catch (IllegalStateException ise) { 542 Slog.w(LOG_TAG, "Failed parsing ", ise); 543 } catch (NullPointerException npe) { 544 Slog.w(LOG_TAG, "Failed parsing ", npe); 545 } catch (NumberFormatException nfe) { 546 Slog.w(LOG_TAG, "Failed parsing ", nfe); 547 } catch (XmlPullParserException xppe) { 548 Slog.w(LOG_TAG, "Failed parsing ", xppe); 549 } catch (IOException ioe) { 550 Slog.w(LOG_TAG, "Failed parsing ", ioe); 551 } catch (IndexOutOfBoundsException iobe) { 552 Slog.w(LOG_TAG, "Failed parsing ", iobe); 553 } finally { 554 IoUtils.closeQuietly(in); 555 } 556 557 return Collections.emptyList(); 558 } 559 560 private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters) 561 throws IOException, XmlPullParserException { 562 parser.next(); 563 skipEmptyTextTags(parser); 564 expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS); 565 parser.next(); 566 567 while (parsePrinter(parser, outPrinters)) { 568 // Be nice and respond to cancellation 569 if (isCancelled()) { 570 return; 571 } 572 parser.next(); 573 } 574 575 skipEmptyTextTags(parser); 576 expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS); 577 } 578 579 private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters) 580 throws IOException, XmlPullParserException { 581 skipEmptyTextTags(parser); 582 if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) { 583 return false; 584 } 585 586 String name = parser.getAttributeValue(null, ATTR_NAME); 587 String description = parser.getAttributeValue(null, ATTR_DESCRIPTION); 588 final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS)); 589 590 parser.next(); 591 592 skipEmptyTextTags(parser); 593 expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID); 594 String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID); 595 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue( 596 null, ATTR_SERVICE_NAME)); 597 PrinterId printerId = new PrinterId(service, localId); 598 parser.next(); 599 skipEmptyTextTags(parser); 600 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID); 601 parser.next(); 602 603 PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status); 604 builder.setDescription(description); 605 PrinterInfo printer = builder.build(); 606 607 outPrinters.add(printer); 608 609 if (DEBUG) { 610 Log.i(LOG_TAG, "[RESTORED] " + printer); 611 } 612 613 skipEmptyTextTags(parser); 614 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER); 615 616 return true; 617 } 618 619 private void expect(XmlPullParser parser, int type, String tag) 620 throws IOException, XmlPullParserException { 621 if (!accept(parser, type, tag)) { 622 throw new XmlPullParserException("Exepected event: " + type 623 + " and tag: " + tag + " but got event: " + parser.getEventType() 624 + " and tag:" + parser.getName()); 625 } 626 } 627 628 private void skipEmptyTextTags(XmlPullParser parser) 629 throws IOException, XmlPullParserException { 630 while (accept(parser, XmlPullParser.TEXT, null) 631 && "\n".equals(parser.getText())) { 632 parser.next(); 633 } 634 } 635 636 private boolean accept(XmlPullParser parser, int type, String tag) 637 throws IOException, XmlPullParserException { 638 if (parser.getEventType() != type) { 639 return false; 640 } 641 if (tag != null) { 642 if (!tag.equals(parser.getName())) { 643 return false; 644 } 645 } else if (parser.getName() != null) { 646 return false; 647 } 648 return true; 649 } 650 } 651 652 private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> { 653 @Override 654 protected Void doInBackground(List<PrinterInfo>... printers) { 655 doWritePrinterHistory(printers[0]); 656 return null; 657 } 658 659 private void doWritePrinterHistory(List<PrinterInfo> printers) { 660 FileOutputStream out = null; 661 try { 662 out = mStatePersistFile.startWrite(); 663 664 XmlSerializer serializer = new FastXmlSerializer(); 665 serializer.setOutput(out, "utf-8"); 666 serializer.startDocument(null, true); 667 serializer.startTag(null, TAG_PRINTERS); 668 669 final int printerCount = printers.size(); 670 for (int i = 0; i < printerCount; i++) { 671 PrinterInfo printer = printers.get(i); 672 673 serializer.startTag(null, TAG_PRINTER); 674 675 serializer.attribute(null, ATTR_NAME, printer.getName()); 676 // Historical printers are always stored as unavailable. 677 serializer.attribute(null, ATTR_STATUS, String.valueOf( 678 PrinterInfo.STATUS_UNAVAILABLE)); 679 String description = printer.getDescription(); 680 if (description != null) { 681 serializer.attribute(null, ATTR_DESCRIPTION, description); 682 } 683 684 PrinterId printerId = printer.getId(); 685 serializer.startTag(null, TAG_PRINTER_ID); 686 serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId()); 687 serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName() 688 .flattenToString()); 689 serializer.endTag(null, TAG_PRINTER_ID); 690 691 serializer.endTag(null, TAG_PRINTER); 692 693 if (DEBUG) { 694 Log.i(LOG_TAG, "[PERSISTED] " + printer); 695 } 696 } 697 698 serializer.endTag(null, TAG_PRINTERS); 699 serializer.endDocument(); 700 mStatePersistFile.finishWrite(out); 701 702 if (DEBUG) { 703 Log.i(LOG_TAG, "[PERSIST END]"); 704 } 705 } catch (IOException ioe) { 706 Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe); 707 mStatePersistFile.failWrite(out); 708 } finally { 709 IoUtils.closeQuietly(out); 710 } 711 } 712 } 713 } 714} 715