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