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