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