1 /* 2 * Copyright (C) 2009 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.server; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.IPackageManager; 23 import android.os.Build; 24 import android.os.DropBoxManager; 25 import android.os.Environment; 26 import android.os.FileObserver; 27 import android.os.FileUtils; 28 import android.os.RecoverySystem; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.os.SystemProperties; 32 import android.os.storage.StorageManager; 33 import android.provider.Downloads; 34 import android.util.AtomicFile; 35 import android.util.Slog; 36 import android.util.Xml; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.logging.MetricsLogger; 40 import com.android.internal.util.ArrayUtils; 41 import com.android.internal.util.FastXmlSerializer; 42 import com.android.internal.util.XmlUtils; 43 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileOutputStream; 47 import java.io.IOException; 48 import java.io.FileNotFoundException; 49 import java.nio.charset.StandardCharsets; 50 import java.util.HashMap; 51 import java.util.Iterator; 52 import java.util.regex.Matcher; 53 import java.util.regex.Pattern; 54 55 import org.xmlpull.v1.XmlPullParser; 56 import org.xmlpull.v1.XmlPullParserException; 57 import org.xmlpull.v1.XmlSerializer; 58 59 /** 60 * Performs a number of miscellaneous, non-system-critical actions 61 * after the system has finished booting. 62 */ 63 public class BootReceiver extends BroadcastReceiver { 64 private static final String TAG = "BootReceiver"; 65 66 // Maximum size of a logged event (files get truncated if they're longer). 67 // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. 68 private static final int LOG_SIZE = 69 SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; 70 71 private static final File TOMBSTONE_DIR = new File("/data/tombstones"); 72 73 // The pre-froyo package and class of the system updater, which 74 // ran in the system process. We need to remove its packages here 75 // in order to clean up after a pre-froyo-to-froyo update. 76 private static final String OLD_UPDATER_PACKAGE = 77 "com.google.android.systemupdater"; 78 private static final String OLD_UPDATER_CLASS = 79 "com.google.android.systemupdater.SystemUpdateReceiver"; 80 81 // Keep a reference to the observer so the finalizer doesn't disable it. 82 private static FileObserver sTombstoneObserver = null; 83 84 private static final String LOG_FILES_FILE = "log-files.xml"; 85 private static final AtomicFile sFile = new AtomicFile(new File( 86 Environment.getDataSystemDirectory(), LOG_FILES_FILE)); 87 private static final String LAST_HEADER_FILE = "last-header.txt"; 88 private static final File lastHeaderFile = new File( 89 Environment.getDataSystemDirectory(), LAST_HEADER_FILE); 90 91 // example: fs_stat,/dev/block/platform/soc/by-name/userdata,0x5 92 private static final String FS_STAT_PATTERN = "fs_stat,[^,]*/([^/,]+),(0x[0-9a-fA-F]+)"; 93 private static final int FS_STAT_FS_FIXED = 0x400; // should match with fs_mgr.cpp:FsStatFlags 94 private static final String FSCK_PASS_PATTERN = "Pass ([1-9]E?):"; 95 private static final String FSCK_TREE_OPTIMIZATION_PATTERN = 96 "Inode [0-9]+ extent tree.*could be shorter"; 97 private static final String FSCK_FS_MODIFIED = "FILE SYSTEM WAS MODIFIED"; 98 // ro.boottime.init.mount_all. + postfix for mount_all duration 99 private static final String[] MOUNT_DURATION_PROPS_POSTFIX = 100 new String[] { "early", "default", "late" }; 101 // for reboot, fs shutdown time is recorded in last_kmsg. 102 private static final String[] LAST_KMSG_FILES = 103 new String[] { "/sys/fs/pstore/console-ramoops", "/proc/last_kmsg" }; 104 // first: fs shutdown time in ms, second: umount status defined in init/reboot.h 105 private static final String LAST_SHUTDOWN_TIME_PATTERN = 106 "powerctl_shutdown_time_ms:([0-9]+):([0-9]+)"; 107 private static final int UMOUNT_STATUS_NOT_AVAILABLE = 4; // should match with init/reboot.h 108 109 @Override onReceive(final Context context, Intent intent)110 public void onReceive(final Context context, Intent intent) { 111 // Log boot events in the background to avoid blocking the main thread with I/O 112 new Thread() { 113 @Override 114 public void run() { 115 try { 116 logBootEvents(context); 117 } catch (Exception e) { 118 Slog.e(TAG, "Can't log boot events", e); 119 } 120 try { 121 boolean onlyCore = false; 122 try { 123 onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService( 124 "package")).isOnlyCoreApps(); 125 } catch (RemoteException e) { 126 } 127 if (!onlyCore) { 128 removeOldUpdatePackages(context); 129 } 130 } catch (Exception e) { 131 Slog.e(TAG, "Can't remove old update packages", e); 132 } 133 134 } 135 }.start(); 136 } 137 removeOldUpdatePackages(Context context)138 private void removeOldUpdatePackages(Context context) { 139 Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); 140 } 141 getPreviousBootHeaders()142 private String getPreviousBootHeaders() { 143 try { 144 return FileUtils.readTextFile(lastHeaderFile, 0, null); 145 } catch (IOException e) { 146 Slog.e(TAG, "Error reading " + lastHeaderFile, e); 147 return null; 148 } 149 } 150 getCurrentBootHeaders()151 private String getCurrentBootHeaders() throws IOException { 152 return new StringBuilder(512) 153 .append("Build: ").append(Build.FINGERPRINT).append("\n") 154 .append("Hardware: ").append(Build.BOARD).append("\n") 155 .append("Revision: ") 156 .append(SystemProperties.get("ro.revision", "")).append("\n") 157 .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") 158 .append("Radio: ").append(Build.RADIO).append("\n") 159 .append("Kernel: ") 160 .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) 161 .append("\n").toString(); 162 } 163 164 getBootHeadersToLogAndUpdate()165 private String getBootHeadersToLogAndUpdate() throws IOException { 166 final String oldHeaders = getPreviousBootHeaders(); 167 final String newHeaders = getCurrentBootHeaders(); 168 169 try { 170 FileUtils.stringToFile(lastHeaderFile, newHeaders); 171 } catch (IOException e) { 172 Slog.e(TAG, "Error writing " + lastHeaderFile, e); 173 } 174 175 if (oldHeaders == null) { 176 // If we failed to read the old headers, use the current headers 177 // but note this in the headers so we know 178 return "isPrevious: false\n" + newHeaders; 179 } 180 181 return "isPrevious: true\n" + oldHeaders; 182 } 183 logBootEvents(Context ctx)184 private void logBootEvents(Context ctx) throws IOException { 185 final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); 186 final String headers = getBootHeadersToLogAndUpdate(); 187 final String bootReason = SystemProperties.get("ro.boot.bootreason", null); 188 189 String recovery = RecoverySystem.handleAftermath(ctx); 190 if (recovery != null && db != null) { 191 db.addText("SYSTEM_RECOVERY_LOG", headers + recovery); 192 } 193 194 String lastKmsgFooter = ""; 195 if (bootReason != null) { 196 lastKmsgFooter = new StringBuilder(512) 197 .append("\n") 198 .append("Boot info:\n") 199 .append("Last boot reason: ").append(bootReason).append("\n") 200 .toString(); 201 } 202 203 HashMap<String, Long> timestamps = readTimestamps(); 204 205 if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) { 206 if (StorageManager.inCryptKeeperBounce()) { 207 // Encrypted, first boot to get PIN/pattern/password so data is tmpfs 208 // Don't set ro.runtime.firstboot so that we will do this again 209 // when data is properly mounted 210 } else { 211 String now = Long.toString(System.currentTimeMillis()); 212 SystemProperties.set("ro.runtime.firstboot", now); 213 } 214 if (db != null) db.addText("SYSTEM_BOOT", headers); 215 216 // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile()) 217 addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter, 218 "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG"); 219 addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter, 220 "/sys/fs/pstore/console-ramoops", -LOG_SIZE, "SYSTEM_LAST_KMSG"); 221 addFileToDropBox(db, timestamps, headers, "/cache/recovery/log", -LOG_SIZE, 222 "SYSTEM_RECOVERY_LOG"); 223 addFileToDropBox(db, timestamps, headers, "/cache/recovery/last_kmsg", 224 -LOG_SIZE, "SYSTEM_RECOVERY_KMSG"); 225 addAuditErrorsToDropBox(db, timestamps, headers, -LOG_SIZE, "SYSTEM_AUDIT"); 226 } else { 227 if (db != null) db.addText("SYSTEM_RESTART", headers); 228 } 229 // log always available fs_stat last so that logcat collecting tools can wait until 230 // fs_stat to get all file system metrics. 231 logFsShutdownTime(); 232 logFsMountTime(); 233 addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK"); 234 235 // Scan existing tombstones (in case any new ones appeared) 236 File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); 237 for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { 238 if (tombstoneFiles[i].isFile()) { 239 addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(), 240 LOG_SIZE, "SYSTEM_TOMBSTONE"); 241 } 242 } 243 244 writeTimestamps(timestamps); 245 246 // Start watching for new tombstone files; will record them as they occur. 247 // This gets registered with the singleton file observer thread. 248 sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) { 249 @Override 250 public void onEvent(int event, String path) { 251 HashMap<String, Long> timestamps = readTimestamps(); 252 try { 253 File file = new File(TOMBSTONE_DIR, path); 254 if (file.isFile()) { 255 addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE, 256 "SYSTEM_TOMBSTONE"); 257 } 258 } catch (IOException e) { 259 Slog.e(TAG, "Can't log tombstone", e); 260 } 261 writeTimestamps(timestamps); 262 } 263 }; 264 265 sTombstoneObserver.startWatching(); 266 } 267 addFileToDropBox( DropBoxManager db, HashMap<String, Long> timestamps, String headers, String filename, int maxSize, String tag)268 private static void addFileToDropBox( 269 DropBoxManager db, HashMap<String, Long> timestamps, 270 String headers, String filename, int maxSize, String tag) throws IOException { 271 addFileWithFootersToDropBox(db, timestamps, headers, "", filename, maxSize, tag); 272 } 273 addFileWithFootersToDropBox( DropBoxManager db, HashMap<String, Long> timestamps, String headers, String footers, String filename, int maxSize, String tag)274 private static void addFileWithFootersToDropBox( 275 DropBoxManager db, HashMap<String, Long> timestamps, 276 String headers, String footers, String filename, int maxSize, 277 String tag) throws IOException { 278 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 279 280 File file = new File(filename); 281 long fileTime = file.lastModified(); 282 if (fileTime <= 0) return; // File does not exist 283 284 if (timestamps.containsKey(filename) && timestamps.get(filename) == fileTime) { 285 return; // Already logged this particular file 286 } 287 288 timestamps.put(filename, fileTime); 289 290 Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); 291 db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") + 292 footers); 293 } 294 addAuditErrorsToDropBox(DropBoxManager db, HashMap<String, Long> timestamps, String headers, int maxSize, String tag)295 private static void addAuditErrorsToDropBox(DropBoxManager db, 296 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 297 throws IOException { 298 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 299 Slog.i(TAG, "Copying audit failures to DropBox"); 300 301 File file = new File("/proc/last_kmsg"); 302 long fileTime = file.lastModified(); 303 if (fileTime <= 0) { 304 file = new File("/sys/fs/pstore/console-ramoops"); 305 fileTime = file.lastModified(); 306 } 307 308 if (fileTime <= 0) return; // File does not exist 309 310 if (timestamps.containsKey(tag) && timestamps.get(tag) == fileTime) { 311 return; // Already logged this particular file 312 } 313 314 timestamps.put(tag, fileTime); 315 316 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 317 StringBuilder sb = new StringBuilder(); 318 for (String line : log.split("\n")) { 319 if (line.contains("audit")) { 320 sb.append(line + "\n"); 321 } 322 } 323 Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); 324 db.addText(tag, headers + sb.toString()); 325 } 326 addFsckErrorsToDropBoxAndLogFsStat(DropBoxManager db, HashMap<String, Long> timestamps, String headers, int maxSize, String tag)327 private static void addFsckErrorsToDropBoxAndLogFsStat(DropBoxManager db, 328 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 329 throws IOException { 330 boolean uploadEnabled = true; 331 if (db == null || !db.isTagEnabled(tag)) { 332 uploadEnabled = false; 333 } 334 boolean uploadNeeded = false; 335 Slog.i(TAG, "Checking for fsck errors"); 336 337 File file = new File("/dev/fscklogs/log"); 338 long fileTime = file.lastModified(); 339 if (fileTime <= 0) return; // File does not exist 340 341 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 342 Pattern pattern = Pattern.compile(FS_STAT_PATTERN); 343 String lines[] = log.split("\n"); 344 int lineNumber = 0; 345 int lastFsStatLineNumber = 0; 346 for (String line : lines) { // should check all lines 347 if (line.contains(FSCK_FS_MODIFIED)) { 348 uploadNeeded = true; 349 } else if (line.contains("fs_stat")){ 350 Matcher matcher = pattern.matcher(line); 351 if (matcher.find()) { 352 handleFsckFsStat(matcher, lines, lastFsStatLineNumber, lineNumber); 353 lastFsStatLineNumber = lineNumber; 354 } else { 355 Slog.w(TAG, "cannot parse fs_stat:" + line); 356 } 357 } 358 lineNumber++; 359 } 360 361 if (uploadEnabled && uploadNeeded ) { 362 addFileToDropBox(db, timestamps, headers, "/dev/fscklogs/log", maxSize, tag); 363 } 364 365 // Remove the file so we don't re-upload if the runtime restarts. 366 file.delete(); 367 } 368 logFsMountTime()369 private static void logFsMountTime() { 370 for (String propPostfix : MOUNT_DURATION_PROPS_POSTFIX) { 371 int duration = SystemProperties.getInt("ro.boottime.init.mount_all." + propPostfix, 0); 372 if (duration != 0) { 373 MetricsLogger.histogram(null, "boot_mount_all_duration_" + propPostfix, duration); 374 } 375 } 376 } 377 logFsShutdownTime()378 private static void logFsShutdownTime() { 379 File f = null; 380 for (String fileName : LAST_KMSG_FILES) { 381 File file = new File(fileName); 382 if (!file.exists()) continue; 383 f = file; 384 break; 385 } 386 if (f == null) { // no last_kmsg 387 return; 388 } 389 390 final int maxReadSize = 16*1024; 391 // last_kmsg can be very big, so only parse the last part 392 String lines; 393 try { 394 lines = FileUtils.readTextFile(f, -maxReadSize, null); 395 } catch (IOException e) { 396 Slog.w(TAG, "cannot read last msg", e); 397 return; 398 } 399 Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE); 400 Matcher matcher = pattern.matcher(lines); 401 if (matcher.find()) { 402 MetricsLogger.histogram(null, "boot_fs_shutdown_duration", 403 Integer.parseInt(matcher.group(1))); 404 MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat", 405 Integer.parseInt(matcher.group(2))); 406 Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2)); 407 } else { // not found 408 // This can happen when a device has too much kernel log after file system unmount 409 // ,exceeding maxReadSize. And having that much kernel logging can affect overall 410 // performance as well. So it is better to fix the kernel to reduce the amount of log. 411 MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat", 412 UMOUNT_STATUS_NOT_AVAILABLE); 413 Slog.w(TAG, "boot_fs_shutdown, string not found"); 414 } 415 } 416 417 /** 418 * Fix fs_stat from e2fsck. 419 * For now, only handle the case of quota warning caused by tree optimization. Clear fs fix 420 * flag (=0x400) caused by that. 421 * 422 * @param partition partition name 423 * @param statOrg original stat reported from e2fsck log 424 * @param lines e2fsck logs broken down into lines 425 * @param startLineNumber start line to parse 426 * @param endLineNumber end line. exclusive. 427 * @return updated fs_stat. For tree optimization, will clear bit 0x400. 428 */ 429 @VisibleForTesting fixFsckFsStat(String partition, int statOrg, String[] lines, int startLineNumber, int endLineNumber)430 public static int fixFsckFsStat(String partition, int statOrg, String[] lines, 431 int startLineNumber, int endLineNumber) { 432 int stat = statOrg; 433 if ((stat & FS_STAT_FS_FIXED) != 0) { 434 // fs was fixed. should check if quota warning was caused by tree optimization. 435 // This is not a real fix but optimization, so should not be counted as a fs fix. 436 Pattern passPattern = Pattern.compile(FSCK_PASS_PATTERN); 437 Pattern treeOptPattern = Pattern.compile(FSCK_TREE_OPTIMIZATION_PATTERN); 438 String currentPass = ""; 439 boolean foundTreeOptimization = false; 440 boolean foundQuotaFix = false; 441 boolean foundOtherFix = false; 442 String otherFixLine = null; 443 for (int i = startLineNumber; i < endLineNumber; i++) { 444 String line = lines[i]; 445 if (line.contains(FSCK_FS_MODIFIED)) { // no need to parse above this 446 break; 447 } else if (line.startsWith("Pass ")) { 448 Matcher matcher = passPattern.matcher(line); 449 if (matcher.find()) { 450 currentPass = matcher.group(1); 451 } 452 } else if (line.startsWith("Inode ")) { 453 Matcher matcher = treeOptPattern.matcher(line); 454 if (matcher.find() && currentPass.equals("1")) { 455 foundTreeOptimization = true; 456 Slog.i(TAG, "fs_stat, partition:" + partition + " found tree optimization:" 457 + line); 458 } else { 459 foundOtherFix = true; 460 otherFixLine = line; 461 break; 462 } 463 } else if (line.startsWith("[QUOTA WARNING]") && currentPass.equals("5")) { 464 Slog.i(TAG, "fs_stat, partition:" + partition + " found quota warning:" 465 + line); 466 foundQuotaFix = true; 467 if (!foundTreeOptimization) { // only quota warning, this is real fix. 468 otherFixLine = line; 469 break; 470 } 471 } else if (line.startsWith("Update quota info") && currentPass.equals("5")) { 472 // follows "[QUOTA WARNING]", ignore 473 } else { 474 line = line.trim(); 475 // ignore empty msg or any msg before Pass 1 476 if (!line.isEmpty() && !currentPass.isEmpty()) { 477 foundOtherFix = true; 478 otherFixLine = line; 479 break; 480 } 481 } 482 } 483 if (!foundOtherFix && foundTreeOptimization && foundQuotaFix) { 484 // not a real fix, so clear it. 485 Slog.i(TAG, "fs_stat, partition:" + partition + 486 " quota fix due to tree optimization"); 487 stat &= ~FS_STAT_FS_FIXED; 488 } else { 489 if (otherFixLine != null) { 490 Slog.i(TAG, "fs_stat, partition:" + partition + " fix:" + otherFixLine); 491 } 492 } 493 } 494 return stat; 495 } 496 handleFsckFsStat(Matcher match, String[] lines, int startLineNumber, int endLineNumber)497 private static void handleFsckFsStat(Matcher match, String[] lines, int startLineNumber, 498 int endLineNumber) { 499 String partition = match.group(1); 500 int stat; 501 try { 502 stat = Integer.decode(match.group(2)); 503 } catch (NumberFormatException e) { 504 Slog.w(TAG, "cannot parse fs_stat: partition:" + partition + " stat:" + match.group(2)); 505 return; 506 } 507 stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber); 508 MetricsLogger.histogram(null, "boot_fs_stat_" + partition, stat); 509 Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat)); 510 } 511 readTimestamps()512 private static HashMap<String, Long> readTimestamps() { 513 synchronized (sFile) { 514 HashMap<String, Long> timestamps = new HashMap<String, Long>(); 515 boolean success = false; 516 try (final FileInputStream stream = sFile.openRead()) { 517 XmlPullParser parser = Xml.newPullParser(); 518 parser.setInput(stream, StandardCharsets.UTF_8.name()); 519 520 int type; 521 while ((type = parser.next()) != XmlPullParser.START_TAG 522 && type != XmlPullParser.END_DOCUMENT) { 523 ; 524 } 525 526 if (type != XmlPullParser.START_TAG) { 527 throw new IllegalStateException("no start tag found"); 528 } 529 530 int outerDepth = parser.getDepth(); // Skip the outer <log-files> tag. 531 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 532 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 533 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 534 continue; 535 } 536 537 String tagName = parser.getName(); 538 if (tagName.equals("log")) { 539 final String filename = parser.getAttributeValue(null, "filename"); 540 final long timestamp = Long.valueOf(parser.getAttributeValue( 541 null, "timestamp")); 542 timestamps.put(filename, timestamp); 543 } else { 544 Slog.w(TAG, "Unknown tag: " + parser.getName()); 545 XmlUtils.skipCurrentTag(parser); 546 } 547 } 548 success = true; 549 } catch (FileNotFoundException e) { 550 Slog.i(TAG, "No existing last log timestamp file " + sFile.getBaseFile() + 551 "; starting empty"); 552 } catch (IOException e) { 553 Slog.w(TAG, "Failed parsing " + e); 554 } catch (IllegalStateException e) { 555 Slog.w(TAG, "Failed parsing " + e); 556 } catch (NullPointerException e) { 557 Slog.w(TAG, "Failed parsing " + e); 558 } catch (XmlPullParserException e) { 559 Slog.w(TAG, "Failed parsing " + e); 560 } finally { 561 if (!success) { 562 timestamps.clear(); 563 } 564 } 565 return timestamps; 566 } 567 } 568 writeTimestamps(HashMap<String, Long> timestamps)569 private void writeTimestamps(HashMap<String, Long> timestamps) { 570 synchronized (sFile) { 571 final FileOutputStream stream; 572 try { 573 stream = sFile.startWrite(); 574 } catch (IOException e) { 575 Slog.w(TAG, "Failed to write timestamp file: " + e); 576 return; 577 } 578 579 try { 580 XmlSerializer out = new FastXmlSerializer(); 581 out.setOutput(stream, StandardCharsets.UTF_8.name()); 582 out.startDocument(null, true); 583 out.startTag(null, "log-files"); 584 585 Iterator<String> itor = timestamps.keySet().iterator(); 586 while (itor.hasNext()) { 587 String filename = itor.next(); 588 out.startTag(null, "log"); 589 out.attribute(null, "filename", filename); 590 out.attribute(null, "timestamp", timestamps.get(filename).toString()); 591 out.endTag(null, "log"); 592 } 593 594 out.endTag(null, "log-files"); 595 out.endDocument(); 596 597 sFile.finishWrite(stream); 598 } catch (IOException e) { 599 Slog.w(TAG, "Failed to write timestamp file, using the backup: " + e); 600 sFile.failWrite(stream); 601 } 602 } 603 } 604 } 605