1 /* 2 * Copyright (C) 2019 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.power; 18 19 import android.os.PowerManager; 20 import android.text.TextUtils; 21 import android.util.Slog; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.internal.os.BackgroundThread; 25 26 import java.io.PrintWriter; 27 import java.text.SimpleDateFormat; 28 import java.util.Arrays; 29 import java.util.ConcurrentModificationException; 30 import java.util.Date; 31 import java.util.Iterator; 32 import java.util.NoSuchElementException; 33 34 /** 35 * Simple Log for wake lock events. Optimized to reduce memory usage. 36 * 37 * The wake lock events are ultimately saved in-memory in a pre-allocated byte-based ring-buffer. 38 * 39 * Most of the work of this log happens in the {@link BackgroundThread}. 40 * 41 * The main log is basically just a sequence of the two wake lock events (ACQUIRE and RELEASE). 42 * Each entry in the log stores the following data: 43 * { 44 * event type (RELEASE | ACQUIRE), 45 * time (64-bit from System.currentTimeMillis()), 46 * wake-lock ID {ownerUID (int) + tag (String)}, 47 * wake-lock flags 48 * } 49 * 50 * In order to maximize the number of entries that fit into the log, there are various efforts made 51 * to compress what we store; of which two are fairly significant and contribute the most to the 52 * complexity of this code: 53 * A) Relative Time 54 * - Time in each log entry is stored as an 8-bit value and is relative to the time of the 55 * previous event. When relative time is too large for 8-bits, we add a third type of event 56 * called TIME_RESET, which is used to add a new 64-bit reference-time event to the log. 57 * In practice, TIME_RESETs seem to make up about 10% or less of the total events depending 58 * on the device usage. 59 * B) Wake-lock tag/ID as indexes 60 * - Wake locks are often reused many times. To avoid storing large strings in the ring buffer, 61 * we maintain a {@link TagDatabase} that associates each wakelock tag with an 7-bit index. 62 * The main log stores only these 7-bit indexes instead of whole strings. 63 * 64 * To make the code a bit more organized, there exists a class {@link EntryByteTranslator} which 65 * uses the tag database, and reference-times to convert between a {@link LogEntry} and the 66 * byte sequence that is ultimately stored in the main log, {@link TheLog}. 67 */ 68 final class WakeLockLog { 69 private static final String TAG = "PowerManagerService.WLLog"; 70 71 private static final boolean DEBUG = false; 72 73 private static final int TYPE_TIME_RESET = 0x0; 74 private static final int TYPE_ACQUIRE = 0x1; 75 private static final int TYPE_RELEASE = 0x2; 76 private static final int MAX_LOG_ENTRY_BYTE_SIZE = 9; 77 private static final int LOG_SIZE = 1024 * 10; 78 private static final int LOG_SIZE_MIN = MAX_LOG_ENTRY_BYTE_SIZE + 1; 79 80 private static final int TAG_DATABASE_SIZE = 128; 81 private static final int TAG_DATABASE_SIZE_MAX = 128; 82 83 private static final int LEVEL_UNKNOWN = 0; 84 private static final int LEVEL_PARTIAL_WAKE_LOCK = 1; 85 private static final int LEVEL_FULL_WAKE_LOCK = 2; 86 private static final int LEVEL_SCREEN_DIM_WAKE_LOCK = 3; 87 private static final int LEVEL_SCREEN_BRIGHT_WAKE_LOCK = 4; 88 private static final int LEVEL_PROXIMITY_SCREEN_OFF_WAKE_LOCK = 5; 89 private static final int LEVEL_DOZE_WAKE_LOCK = 6; 90 private static final int LEVEL_DRAW_WAKE_LOCK = 7; 91 92 private static final String[] LEVEL_TO_STRING = { 93 "unknown", 94 "partial", 95 "full", 96 "screen-dim", 97 "screen-bright", 98 "prox", 99 "doze", 100 "draw" 101 }; 102 103 /** 104 * Flags use the same bit field as the level, so must start at the next available bit 105 * after the largest level. 106 */ 107 private static final int FLAG_ON_AFTER_RELEASE = 0x8; 108 private static final int FLAG_ACQUIRE_CAUSES_WAKEUP = 0x10; 109 private static final int FLAG_SYSTEM_WAKELOCK = 0x20; 110 111 private static final int MASK_LOWER_6_BITS = 0x3F; 112 private static final int MASK_LOWER_7_BITS = 0x7F; 113 114 private static final String[] REDUCED_TAG_PREFIXES = 115 {"*job*/", "*gms_scheduler*/", "IntentOp:"}; 116 117 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 118 119 /** 120 * Lock protects WakeLockLock.dump (binder thread) from conflicting with changes to the log 121 * happening on the background thread. 122 */ 123 private final Object mLock = new Object(); 124 125 private final Injector mInjector; 126 private final TheLog mLog; 127 private final TagDatabase mTagDatabase; 128 private final SimpleDateFormat mDumpsysDateFormat; 129 WakeLockLog()130 WakeLockLog() { 131 this(new Injector()); 132 } 133 134 @VisibleForTesting WakeLockLog(Injector injector)135 WakeLockLog(Injector injector) { 136 mInjector = injector; 137 mTagDatabase = new TagDatabase(injector); 138 EntryByteTranslator translator = new EntryByteTranslator(mTagDatabase); 139 mLog = new TheLog(injector, translator, mTagDatabase); 140 mDumpsysDateFormat = injector.getDateFormat(); 141 } 142 143 /** 144 * Receives notifications of an ACQUIRE wake lock event from PowerManager. 145 * 146 * @param tag The wake lock tag 147 * @param ownerUid The owner UID of the wake lock. 148 * @param flags Flags used for the wake lock. 149 */ onWakeLockAcquired(String tag, int ownerUid, int flags)150 public void onWakeLockAcquired(String tag, int ownerUid, int flags) { 151 onWakeLockEvent(TYPE_ACQUIRE, tag, ownerUid, flags); 152 } 153 154 /** 155 * Receives notifications of a RELEASE wake lock event from PowerManager. 156 * 157 * @param tag The wake lock tag 158 * @param ownerUid The owner UID of the wake lock. 159 */ onWakeLockReleased(String tag, int ownerUid)160 public void onWakeLockReleased(String tag, int ownerUid) { 161 onWakeLockEvent(TYPE_RELEASE, tag, ownerUid, 0 /* flags */); 162 } 163 164 /** 165 * Dumps all the wake lock data currently saved in the wake lock log to the specified 166 * {@code PrintWriter}. 167 * 168 * @param pw The {@code PrintWriter} to write to. 169 */ dump(PrintWriter pw)170 public void dump(PrintWriter pw) { 171 dump(pw, false); 172 } 173 174 @VisibleForTesting dump(PrintWriter pw, boolean includeTagDb)175 void dump(PrintWriter pw, boolean includeTagDb) { 176 try { 177 synchronized (mLock) { 178 pw.println("Wake Lock Log"); 179 LogEntry tempEntry = new LogEntry(); // Temporary entry for the iterator to reuse. 180 final Iterator<LogEntry> iterator = mLog.getAllItems(tempEntry); 181 int numEvents = 0; 182 int numResets = 0; 183 while (iterator.hasNext()) { 184 String address = null; 185 if (DEBUG) { 186 // Gets the byte index in the log for the current entry. 187 address = iterator.toString(); 188 } 189 LogEntry entry = iterator.next(); 190 if (entry != null) { 191 if (entry.type == TYPE_TIME_RESET) { 192 numResets++; 193 } else { 194 numEvents++; 195 if (DEBUG) { 196 pw.print(address); 197 } 198 entry.dump(pw, mDumpsysDateFormat); 199 } 200 } 201 } 202 pw.println(" -"); 203 pw.println(" Events: " + numEvents + ", Time-Resets: " + numResets); 204 pw.println(" Buffer, Bytes used: " + mLog.getUsedBufferSize()); 205 if (DEBUG || includeTagDb) { 206 pw.println(" " + mTagDatabase); 207 } 208 } 209 } catch (Exception e) { 210 pw.println("Exception dumping wake-lock log: " + e.toString()); 211 } 212 } 213 214 /** 215 * Adds a new entry to the log based on the specified wake lock parameters. 216 * 217 * @param eventType The type of event (ACQUIRE, RELEASE); 218 * @param tag The wake lock's identifying tag. 219 * @param ownerUid The owner UID of the wake lock. 220 * @param flags The flags used with the wake lock. 221 */ onWakeLockEvent(int eventType, String tag, int ownerUid, int flags)222 private void onWakeLockEvent(int eventType, String tag, int ownerUid, 223 int flags) { 224 if (tag == null) { 225 Slog.w(TAG, "Insufficient data to log wakelock [tag: " + tag 226 + ", ownerUid: " + ownerUid 227 + ", flags: 0x" + Integer.toHexString(flags)); 228 return; 229 } 230 231 final long time = mInjector.currentTimeMillis(); 232 final int translatedFlags = eventType == TYPE_ACQUIRE 233 ? translateFlagsFromPowerManager(flags) 234 : 0; 235 handleWakeLockEventInternal(eventType, tagNameReducer(tag), ownerUid, translatedFlags, 236 time); 237 } 238 239 /** 240 * Handles a new wakelock event in the background thread. 241 * 242 * @param eventType The type of event (ACQUIRE, RELEASE) 243 * @param tag The wake lock's identifying tag. 244 * @param ownerUid The owner UID of the wake lock. 245 * @param flags the flags used with the wake lock. 246 */ handleWakeLockEventInternal(int eventType, String tag, int ownerUid, int flags, long time)247 private void handleWakeLockEventInternal(int eventType, String tag, int ownerUid, int flags, 248 long time) { 249 synchronized (mLock) { 250 final TagData tagData = mTagDatabase.findOrCreateTag( 251 tag, ownerUid, true /* shouldCreate */); 252 mLog.addEntry(new LogEntry(time, eventType, tagData, flags)); 253 } 254 } 255 256 /** 257 * Translates wake lock flags from PowerManager into a redefined set that fits 258 * in the lower 6-bits of the return value. The results are an OR-ed combination of the 259 * flags, {@code WakeLockLog.FLAG_*}, and a log-level, {@code WakeLockLog.LEVEL_*}. 260 * 261 * @param flags Wake lock flags including {@code PowerManager.*_WAKE_LOCK} 262 * {@link PowerManager#ACQUIRE_CAUSES_WAKEUP}, and 263 * {@link PowerManager#ON_AFTER_RELEASE}. 264 * @return The compressed flags value. 265 */ translateFlagsFromPowerManager(int flags)266 int translateFlagsFromPowerManager(int flags) { 267 int newFlags = 0; 268 switch(PowerManager.WAKE_LOCK_LEVEL_MASK & flags) { 269 case PowerManager.PARTIAL_WAKE_LOCK: 270 newFlags = LEVEL_PARTIAL_WAKE_LOCK; 271 break; 272 case PowerManager.SCREEN_DIM_WAKE_LOCK: 273 newFlags = LEVEL_SCREEN_DIM_WAKE_LOCK; 274 break; 275 case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: 276 newFlags = LEVEL_SCREEN_BRIGHT_WAKE_LOCK; 277 break; 278 case PowerManager.FULL_WAKE_LOCK: 279 newFlags = LEVEL_FULL_WAKE_LOCK; 280 break; 281 case PowerManager.DOZE_WAKE_LOCK: 282 newFlags = LEVEL_DOZE_WAKE_LOCK; 283 break; 284 case PowerManager.DRAW_WAKE_LOCK: 285 newFlags = LEVEL_DRAW_WAKE_LOCK; 286 break; 287 case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: 288 newFlags = LEVEL_PROXIMITY_SCREEN_OFF_WAKE_LOCK; 289 break; 290 default: 291 Slog.w(TAG, "Unsupported lock level for logging, flags: " + flags); 292 break; 293 } 294 if ((flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { 295 newFlags |= FLAG_ACQUIRE_CAUSES_WAKEUP; 296 } 297 if ((flags & PowerManager.ON_AFTER_RELEASE) != 0) { 298 newFlags |= FLAG_ON_AFTER_RELEASE; 299 } 300 if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) { 301 newFlags |= FLAG_SYSTEM_WAKELOCK; 302 } 303 return newFlags; 304 } 305 306 /** 307 * Reduce certain wakelock tags to something smaller. 308 * e.g. "*job* /com.aye.bee.cee/dee.ee.eff.Gee$Eich" -> "*job* /c.a.b.c/d.e.e.Gee$Eich" 309 * This is used to save space when storing the tags in the Tag Database. 310 * 311 * @param tag The tag name to reduce 312 * @return A reduced version of the tag name. 313 */ tagNameReducer(String tag)314 private String tagNameReducer(String tag) { 315 if (tag == null) { 316 return null; 317 } 318 319 String reduciblePrefix = null; 320 for (String reducedTagPrefix : REDUCED_TAG_PREFIXES) { 321 if (tag.startsWith(reducedTagPrefix)) { 322 reduciblePrefix = reducedTagPrefix; 323 break; 324 } 325 } 326 327 if (reduciblePrefix != null) { 328 final StringBuilder sb = new StringBuilder(); 329 330 // add prefix first 331 sb.append(tag, 0, reduciblePrefix.length()); 332 333 // Stop looping on final marker 334 final int end = Math.max(tag.lastIndexOf("/"), tag.lastIndexOf(".")); 335 boolean printNext = true; 336 int index = sb.length(); // Start looping after the prefix 337 for (; index < end; index++) { 338 char c = tag.charAt(index); 339 boolean isMarker = (c == '.' || c == '/'); 340 // We print all markers and the character that follows each marker 341 if (isMarker || printNext) { 342 sb.append(c); 343 } 344 printNext = isMarker; 345 } 346 sb.append(tag.substring(index)); // append everything that is left 347 return sb.toString(); 348 } 349 return tag; 350 } 351 352 /** 353 * Represents a wakelock-event entry in the log. 354 * Holds all the data of a wakelock. The lifetime of this class is fairly short as the data 355 * within this type is eventually written to the log as bytes and the instances discarded until 356 * the log is read again which is fairly infrequent. 357 * 358 * At the time of this writing, this can be one of three types of entries: 359 * 1) Wake lock acquire 360 * 2) Wake lock release 361 * 3) Time reset 362 */ 363 static class LogEntry { 364 /** 365 * Type of wake lock from the {@code WakeLockLog.TYPE_*} set. 366 */ 367 public int type; 368 369 /** 370 * Time of the wake lock entry as taken from System.currentTimeMillis(). 371 */ 372 public long time; 373 374 /** 375 * Data about the wake lock tag. 376 */ 377 public TagData tag; 378 379 /** 380 * Flags used with the wake lock. 381 */ 382 public int flags; 383 LogEntry()384 LogEntry() {} 385 LogEntry(long time, int type, TagData tag, int flags)386 LogEntry(long time, int type, TagData tag, int flags) { 387 set(time, type, tag, flags); 388 } 389 390 /** 391 * Sets the values of the log entry. 392 * This is exposed to ease the reuse of {@code LogEntry} instances. 393 * 394 * @param time Time of the entry. 395 * @param type Type of entry. 396 * @param tag Tag data of the wake lock. 397 * @param flags Flags used with the wake lock. 398 */ set(long time, int type, TagData tag, int flags)399 public void set(long time, int type, TagData tag, int flags) { 400 this.time = time; 401 this.type = type; 402 this.tag = tag; 403 this.flags = flags; 404 } 405 406 /** 407 * Dumps this entry to the specified {@link PrintWriter}. 408 * 409 * @param pw The print-writer to dump to. 410 * @param dateFormat The date format to use for outputing times. 411 */ dump(PrintWriter pw, SimpleDateFormat dateFormat)412 public void dump(PrintWriter pw, SimpleDateFormat dateFormat) { 413 pw.println(" " + toStringInternal(dateFormat)); 414 } 415 416 /** 417 * Converts the entry to a string. 418 * date - ownerUid - (ACQ|REL) tag [(flags)] 419 * e.g., 1999-01-01 12:01:01.123 - 10012 - ACQ bluetooth_timer (partial) 420 */ 421 @Override toString()422 public String toString() { 423 return toStringInternal(DATE_FORMAT); 424 } 425 426 /** 427 * Converts the entry to a string. 428 * date - ownerUid - (ACQ|REL) tag [(flags)] 429 * e.g., 1999-01-01 12:01:01.123 - 10012 - ACQ bluetooth_timer (partial) 430 * 431 * @param dateFormat The date format to use for outputing times. 432 * @return The string output of this class instance. 433 */ toStringInternal(SimpleDateFormat dateFormat)434 private String toStringInternal(SimpleDateFormat dateFormat) { 435 StringBuilder sb = new StringBuilder(); 436 if (type == TYPE_TIME_RESET) { 437 return dateFormat.format(new Date(time)) + " - RESET"; 438 } 439 sb.append(dateFormat.format(new Date(time))) 440 .append(" - ") 441 .append(tag == null ? "---" : tag.ownerUid) 442 .append(" - ") 443 .append(type == TYPE_ACQUIRE ? "ACQ" : "REL") 444 .append(" ") 445 .append(tag == null ? "UNKNOWN" : tag.tag); 446 if (type == TYPE_ACQUIRE) { 447 sb.append(" ("); 448 flagsToString(sb); 449 sb.append(")"); 450 } 451 return sb.toString(); 452 } 453 flagsToString(StringBuilder sb)454 private void flagsToString(StringBuilder sb) { 455 sb.append(LEVEL_TO_STRING[flags & 0x7]); 456 if ((flags & FLAG_ON_AFTER_RELEASE) == FLAG_ON_AFTER_RELEASE) { 457 sb.append(",on-after-release"); 458 } 459 if ((flags & FLAG_ACQUIRE_CAUSES_WAKEUP) == FLAG_ACQUIRE_CAUSES_WAKEUP) { 460 sb.append(",acq-causes-wake"); 461 } 462 if ((flags & FLAG_SYSTEM_WAKELOCK) == FLAG_SYSTEM_WAKELOCK) { 463 sb.append(",system-wakelock"); 464 } 465 } 466 } 467 468 /** 469 * Converts between a {@link LogEntry} instance and a byte sequence. 470 * 471 * This is used to convert {@link LogEntry}s to a series of bytes before being written into 472 * the log, and vice-versa when reading from the log. 473 * 474 * This method employs the compression techniques that are mentioned in the header of 475 * {@link WakeLockLog}: Relative-time and Tag-indexing. Please see the header for the 476 * description of both. 477 * 478 * The specific byte formats used are explained more thoroughly in the method {@link #toBytes}. 479 */ 480 static class EntryByteTranslator { 481 482 // Error codes that can be returned when converting to bytes. 483 static final int ERROR_TIME_IS_NEGATIVE = -1; // Relative time is negative 484 static final int ERROR_TIME_TOO_LARGE = -2; // Relative time is out of valid range (0-255) 485 486 private final TagDatabase mTagDatabase; 487 EntryByteTranslator(TagDatabase tagDatabase)488 EntryByteTranslator(TagDatabase tagDatabase) { 489 mTagDatabase = tagDatabase; 490 } 491 492 /** 493 * Translates the specified bytes into a LogEntry instance, if possible. 494 * 495 * See {@link #toBytes} for an explanation of the byte formats. 496 * 497 * @param bytes The bytes to read. 498 * @param timeReference The reference time to use when reading the relative time from the 499 * bytes buffer. 500 * @param entryToReuse The entry instance to write to. If null, this method will create a 501 * new instance. 502 * @return The converted entry, or null if data is corrupt. 503 */ fromBytes(byte[] bytes, long timeReference, LogEntry entryToReuse)504 LogEntry fromBytes(byte[] bytes, long timeReference, LogEntry entryToReuse) { 505 if (bytes == null || bytes.length == 0) { 506 return null; 507 } 508 509 // Create an entry if non if passed in to use 510 LogEntry entry = entryToReuse != null ? entryToReuse : new LogEntry(); 511 512 int type = (bytes[0] >> 6) & 0x3; 513 if ((type & 0x2) == 0x2) { 514 // As long as the highest order bit of the byte is set, it is a release 515 type = TYPE_RELEASE; 516 } 517 switch (type) { 518 case TYPE_ACQUIRE: { 519 if (bytes.length < 3) { 520 break; 521 } 522 523 int flags = bytes[0] & MASK_LOWER_6_BITS; 524 int tagIndex = bytes[1] & MASK_LOWER_7_BITS; 525 TagData tag = mTagDatabase.getTag(tagIndex); 526 long time = (bytes[2] & 0xFF) + timeReference; 527 entry.set(time, TYPE_ACQUIRE, tag, flags); 528 return entry; 529 } 530 case TYPE_RELEASE: { 531 if (bytes.length < 2) { 532 break; 533 } 534 535 int flags = 0; 536 int tagIndex = bytes[0] & MASK_LOWER_7_BITS; 537 TagData tag = mTagDatabase.getTag(tagIndex); 538 long time = (bytes[1] & 0xFF) + timeReference; 539 entry.set(time, TYPE_RELEASE, tag, flags); 540 return entry; 541 } 542 case TYPE_TIME_RESET: { 543 if (bytes.length < 9) { 544 break; 545 } 546 547 long time = ((bytes[1] & 0xFFL) << 56) 548 | ((bytes[2] & 0xFFL) << 48) 549 | ((bytes[3] & 0xFFL) << 40) 550 | ((bytes[4] & 0xFFL) << 32) 551 | ((bytes[5] & 0xFFL) << 24) 552 | ((bytes[6] & 0xFFL) << 16) 553 | ((bytes[7] & 0xFFL) << 8) 554 | (bytes[8] & 0xFFL); 555 entry.set(time, TYPE_TIME_RESET, null, 0); 556 return entry; 557 } 558 default: 559 Slog.w(TAG, "Type not recognized [" + type + "]", new Exception()); 560 break; 561 } 562 return null; 563 } 564 565 /** 566 * Converts and writes the specified entry into the specified byte array. 567 * If the byte array is null or too small, then the method writes nothing, but still returns 568 * the number of bytes necessary to write the entry. 569 * 570 * Byte format used for each type: 571 * 572 * TYPE_RELEASE: 573 * bits 574 * 0 1 2 3 4 5 6 7 575 * bytes 0 [ 1 | 7-bit wake lock tag index ] 576 * 1 [ 8-bit relative time ] 577 * 578 * 579 * TYPE_ACQUIRE: 580 * bits 581 * 0 1 2 3 4 5 6 7 582 * 0 [ 0 1 | wake lock flags ] 583 * bytes 1 [unused| 7-bit wake lock tag index ] 584 * 2 [ 8-bit relative time ] 585 * 586 * 587 * TYPE_TIME_RESET: 588 * bits 589 * 0 1 2 3 4 5 6 7 590 * 0 [ 0 0 | unused ] 591 * bytes 1-9 [ 64-bit reference-time ] 592 * 593 * @param entry The entry to convert/write 594 * @param bytes The buffer to write to, or null to just return the necessary bytes. 595 * @param timeReference The reference-time used to calculate relative time of the entry. 596 * @return The number of bytes written to buffer, or required to write to the buffer. 597 */ toBytes(LogEntry entry, byte[] bytes, long timeReference)598 int toBytes(LogEntry entry, byte[] bytes, long timeReference) { 599 final int sizeNeeded; 600 switch (entry.type) { 601 case TYPE_ACQUIRE: { 602 sizeNeeded = 3; 603 if (bytes != null && bytes.length >= sizeNeeded) { 604 int relativeTime = getRelativeTime(timeReference, entry.time); 605 if (relativeTime < 0) { 606 // Negative relative time indicates error code 607 return relativeTime; 608 } 609 bytes[0] = (byte) ((TYPE_ACQUIRE << 6) 610 | (entry.flags & MASK_LOWER_6_BITS)); 611 bytes[1] = (byte) mTagDatabase.getTagIndex(entry.tag); 612 bytes[2] = (byte) (relativeTime & 0xFF); // Lower 8 bits of the time 613 if (DEBUG) { 614 Slog.d(TAG, "ACQ - Setting bytes: " + Arrays.toString(bytes)); 615 } 616 } 617 break; 618 } 619 case TYPE_RELEASE: { 620 sizeNeeded = 2; 621 if (bytes != null && bytes.length >= sizeNeeded) { 622 int relativeTime = getRelativeTime(timeReference, entry.time); 623 if (relativeTime < 0) { 624 // Negative relative time indicates error code 625 return relativeTime; 626 } 627 bytes[0] = (byte) (0x80 | mTagDatabase.getTagIndex(entry.tag)); 628 bytes[1] = (byte) (relativeTime & 0xFF); // Lower 8 bits of the time 629 if (DEBUG) { 630 Slog.d(TAG, "REL - Setting bytes: " + Arrays.toString(bytes)); 631 } 632 } 633 break; 634 } 635 case TYPE_TIME_RESET: { 636 sizeNeeded = 9; 637 long time = entry.time; 638 if (bytes != null && bytes.length >= sizeNeeded) { 639 bytes[0] = (TYPE_TIME_RESET << 6); 640 bytes[1] = (byte) ((time >> 56) & 0xFF); 641 bytes[2] = (byte) ((time >> 48) & 0xFF); 642 bytes[3] = (byte) ((time >> 40) & 0xFF); 643 bytes[4] = (byte) ((time >> 32) & 0xFF); 644 bytes[5] = (byte) ((time >> 24) & 0xFF); 645 bytes[6] = (byte) ((time >> 16) & 0xFF); 646 bytes[7] = (byte) ((time >> 8) & 0xFF); 647 bytes[8] = (byte) (time & 0xFF); 648 } 649 break; 650 } 651 default: 652 throw new RuntimeException("Unknown type " + entry); 653 } 654 655 return sizeNeeded; 656 } 657 658 /** 659 * Calculates the relative time between the specified time and timeReference. The relative 660 * time is expected to be non-negative and fit within 8-bits (values between 0-255). If the 661 * relative time is outside of that range an error code will be returned instead. 662 * 663 * @param time 664 * @param timeReference 665 * @return The relative time between time and timeReference, or an error code. 666 */ getRelativeTime(long timeReference, long time)667 private int getRelativeTime(long timeReference, long time) { 668 if (time < timeReference) { 669 if (DEBUG) { 670 Slog.w(TAG, "ERROR_TIME_IS_NEGATIVE"); 671 } 672 return ERROR_TIME_IS_NEGATIVE; 673 } 674 long relativeTime = time - timeReference; 675 if (relativeTime > 255) { 676 if (DEBUG) { 677 Slog.w(TAG, "ERROR_TIME_TOO_LARGE"); 678 } 679 return ERROR_TIME_TOO_LARGE; 680 } 681 return (int) relativeTime; 682 } 683 } 684 685 /** 686 * Main implementation of the ring buffer used to store the log entries. This class takes 687 * {@link LogEntry} instances and adds them to the ring buffer, utilizing 688 * {@link EntryByteTranslator} to convert byte {@link LogEntry} to bytes within the buffer. 689 * 690 * This class also implements the logic around TIME_RESET events. Since the LogEntries store 691 * their time (8-bit) relative to the previous event, this class can add 692 * {@link #TYPE_TIME_RESET} LogEntries as necessary to allow a LogEntry's relative time to fit 693 * within that range. 694 */ 695 static class TheLog { 696 private final EntryByteTranslator mTranslator; 697 698 /** 699 * Temporary buffer used when converting a new entry to bytes for writing to the buffer. 700 * Allocating once allows us to avoid allocating a buffer with each write. 701 */ 702 private final byte[] mTempBuffer = new byte[MAX_LOG_ENTRY_BYTE_SIZE]; 703 704 /** 705 * Second temporary buffer used when reading and writing bytes from the buffer. 706 * A second temporary buffer is necessary since additional items can be read concurrently 707 * from {@link #mTempBuffer}. E.g., Adding an entry to a full buffer requires removing 708 * other entries from the buffer. 709 */ 710 private final byte[] mReadWriteTempBuffer = new byte[MAX_LOG_ENTRY_BYTE_SIZE]; 711 712 /** 713 * Main log buffer. 714 */ 715 private final byte[] mBuffer; 716 717 /** 718 * Start index of the ring buffer. 719 */ 720 private int mStart = 0; 721 722 /** 723 * Current end index of the ring buffer. 724 */ 725 private int mEnd = 0; 726 727 /** 728 * Start time of the entries in the buffer. The first item stores an 8-bit time that is 729 * relative to this value. 730 */ 731 private long mStartTime = 0; 732 733 /** 734 * The time of the last entry in the buffer. Reading the time from the last entry to 735 * calculate the relative time of a new one is sufficiently hard to prefer saving the value 736 * here instead. 737 */ 738 private long mLatestTime = 0; 739 740 /** 741 * Counter for number of changes (adds or removes) that have been done to the buffer. 742 */ 743 private long mChangeCount = 0; 744 745 private final TagDatabase mTagDatabase; 746 TheLog(Injector injector, EntryByteTranslator translator, TagDatabase tagDatabase)747 TheLog(Injector injector, EntryByteTranslator translator, TagDatabase tagDatabase) { 748 final int logSize = Math.max(injector.getLogSize(), LOG_SIZE_MIN); 749 mBuffer = new byte[logSize]; 750 751 mTranslator = translator; 752 mTagDatabase = tagDatabase; 753 754 // Register to be notified when an older tag is removed from the TagDatabase to make 755 // room for a new entry. 756 mTagDatabase.setCallback(new TagDatabase.Callback() { 757 @Override public void onIndexRemoved(int index) { 758 removeTagIndex(index); 759 } 760 }); 761 } 762 763 /** 764 * Returns the amount of space being used in the ring buffer (in bytes). 765 * 766 * @return Used buffer size in bytes. 767 */ getUsedBufferSize()768 int getUsedBufferSize() { 769 return mBuffer.length - getAvailableSpace(); 770 } 771 772 /** 773 * Adds the specified {@link LogEntry} to the log by converting it to bytes and writing 774 * those bytes to the buffer. 775 * 776 * This method can have side effects of removing old values from the ring buffer and 777 * adding an extra TIME_RESET entry if necessary. 778 */ addEntry(LogEntry entry)779 void addEntry(LogEntry entry) { 780 if (isBufferEmpty()) { 781 // First item being added, do initialization. 782 mStartTime = mLatestTime = entry.time; 783 } 784 785 int size = mTranslator.toBytes(entry, mTempBuffer, mLatestTime); 786 if (size == EntryByteTranslator.ERROR_TIME_IS_NEGATIVE) { 787 return; // Wholly unexpected circumstance...just break out now. 788 } else if (size == EntryByteTranslator.ERROR_TIME_TOO_LARGE) { 789 // The relative time between the last entry and this new one is too large 790 // to fit in our byte format...we need to create a new Time-Reset event and add 791 // that to the log first. 792 addEntry(new LogEntry(entry.time, TYPE_TIME_RESET, null, 0)); 793 size = mTranslator.toBytes(entry, mTempBuffer, mLatestTime); 794 } 795 796 if (size > MAX_LOG_ENTRY_BYTE_SIZE || size <= 0) { 797 Slog.w(TAG, "Log entry size is out of expected range: " + size); 798 return; 799 } 800 801 // In case the buffer is full or nearly full, ensure there is a proper amount of space 802 // for the new entry. 803 if (!makeSpace(size)) { 804 return; // Doesn't fit 805 } 806 807 if (DEBUG) { 808 Slog.d(TAG, "Wrote New Entry @(" + mEnd + ") [" + entry + "] as " 809 + Arrays.toString(mTempBuffer)); 810 } 811 // Set the bytes and update our end index & timestamp. 812 writeBytesAt(mEnd, mTempBuffer, size); 813 if (DEBUG) { 814 Slog.d(TAG, "Read written Entry @(" + mEnd + ") [" 815 + readEntryAt(mEnd, mLatestTime, null)); 816 } 817 mEnd = (mEnd + size) % mBuffer.length; 818 mLatestTime = entry.time; 819 820 TagDatabase.updateTagTime(entry.tag, entry.time); 821 mChangeCount++; 822 } 823 824 /** 825 * Returns an {@link Iterator} of {@link LogEntry}s for all the entries in the log. 826 * 827 * If the log is modified while the entries are being read, the iterator will throw a 828 * {@link ConcurrentModificationException}. 829 * 830 * @param tempEntry A temporary {@link LogEntry} instance to use so that new instances 831 * aren't allocated with every call to {@code Iterator.next}. 832 */ getAllItems(final LogEntry tempEntry)833 Iterator<LogEntry> getAllItems(final LogEntry tempEntry) { 834 return new Iterator<LogEntry>() { 835 private int mCurrent = mStart; // Current read position in the log. 836 private long mCurrentTimeReference = mStartTime; // Current time-reference to use. 837 private final long mChangeValue = mChangeCount; // Used to track if buffer changed. 838 839 /** 840 * @return True if there are more elements to iterate through, false otherwise.\ 841 * @throws ConcurrentModificationException if the buffer contents change. 842 */ 843 @Override 844 public boolean hasNext() { 845 checkState(); 846 return mCurrent != mEnd; 847 } 848 849 /** 850 * Returns the next element in the iterator. 851 * 852 * @return The next entry in the iterator 853 * @throws NoSuchElementException if iterator reaches the end. 854 * @throws ConcurrentModificationException if buffer contents change. 855 */ 856 @Override 857 public LogEntry next() { 858 checkState(); 859 860 if (!hasNext()) { 861 throw new NoSuchElementException("No more entries left."); 862 } 863 864 LogEntry entry = readEntryAt(mCurrent, mCurrentTimeReference, tempEntry); 865 int size = mTranslator.toBytes(entry, null, mStartTime); 866 mCurrent = (mCurrent + size) % mBuffer.length; 867 mCurrentTimeReference = entry.time; 868 869 return entry; 870 } 871 872 @Override public String toString() { 873 return "@" + mCurrent; 874 } 875 876 /** 877 * @throws ConcurrentModificationException if the underlying buffer has changed 878 * since this iterator was instantiated. 879 */ 880 private void checkState() { 881 if (mChangeValue != mChangeCount) { 882 throw new ConcurrentModificationException("Buffer modified, old change: " 883 + mChangeValue + ", new change: " + mChangeCount); 884 } 885 } 886 }; 887 } 888 889 /** 890 * Cleans up old tag index references from the entire log. 891 * Called when an older wakelock tag is removed from the tag database. This happens 892 * when the database needed additional room for newer tags. 893 * 894 * This is a fairly expensive operation. Reads all the entries from the buffer, which can 895 * be around 1500 for a 10Kb buffer. It will write back any entries that use the tag as 896 * well, but that's not many of them. Commonly-used tags dont ever make it to this part. 897 * 898 * If necessary, in the future we can keep track of the number of tag-users the same way we 899 * keep track of a tag's last-used-time to stop having to do this for old tags that dont 900 * have entries in the logs any more. Light testing has shown that for a 10Kb 901 * buffer, there are about 5 or fewer (of 1500) entries with "UNKNOWN" tag...which means 902 * this operation does happen, but not very much. 903 * 904 * @param tagIndex The index of the tag, as stored in the log 905 */ removeTagIndex(int tagIndex)906 private void removeTagIndex(int tagIndex) { 907 if (isBufferEmpty()) { 908 return; 909 } 910 911 int readIndex = mStart; 912 long timeReference = mStartTime; 913 final LogEntry reusableEntryInstance = new LogEntry(); 914 while (readIndex != mEnd) { 915 LogEntry entry = readEntryAt(readIndex, timeReference, reusableEntryInstance); 916 if (DEBUG) { 917 Slog.d(TAG, "Searching to remove tags @ " + readIndex + ": " + entry); 918 } 919 if (entry == null) { 920 Slog.w(TAG, "Entry is unreadable - Unexpected @ " + readIndex); 921 break; // cannot continue if entries are now corrupt 922 } 923 if (entry.tag != null && entry.tag.index == tagIndex) { 924 // We found an entry that uses the tag being removed. Re-write the 925 // entry back without a tag. 926 entry.tag = null; // remove the tag, and write it back 927 writeEntryAt(readIndex, entry, timeReference); 928 if (DEBUG) { 929 Slog.d(TAG, "Remove tag index: " + tagIndex + " @ " + readIndex); 930 } 931 } 932 timeReference = entry.time; 933 int entryByteSize = mTranslator.toBytes(entry, null, 0L); 934 readIndex = (readIndex + entryByteSize) % mBuffer.length; 935 } 936 } 937 938 /** 939 * Removes entries from the buffer until the specified amount of space is available for use. 940 * 941 * @param spaceNeeded The number of bytes needed in the buffer. 942 * @return True if there is space enough in the buffer, false otherwise. 943 */ makeSpace(int spaceNeeded)944 private boolean makeSpace(int spaceNeeded) { 945 // Test the size of the buffer can fit it first, so that we dont loop forever in the 946 // following while loop. 947 if (mBuffer.length < spaceNeeded + 1) { 948 return false; 949 } 950 951 // We check spaceNeeded + 1 so that mStart + mEnd aren't equal. We use them being equal 952 // to mean that the buffer is empty...so avoid that. 953 while (getAvailableSpace() < (spaceNeeded + 1)) { 954 removeOldestItem(); 955 } 956 return true; 957 } 958 959 /** 960 * Returns the available space of the ring buffer. 961 */ getAvailableSpace()962 private int getAvailableSpace() { 963 return mEnd > mStart ? mBuffer.length - (mEnd - mStart) : 964 (mEnd < mStart ? mStart - mEnd : 965 mBuffer.length); 966 } 967 968 /** 969 * Removes the oldest item from the buffer if the buffer is not empty. 970 */ removeOldestItem()971 private void removeOldestItem() { 972 if (isBufferEmpty()) { 973 // No items to remove 974 return; 975 } 976 977 // Copy the contents of the start of the buffer to our temporary buffer. 978 LogEntry entry = readEntryAt(mStart, mStartTime, null); 979 if (DEBUG) { 980 Slog.d(TAG, "Removing oldest item at @ " + mStart + ", found: " + entry); 981 } 982 int size = mTranslator.toBytes(entry, null, mStartTime); 983 mStart = (mStart + size) % mBuffer.length; 984 mStartTime = entry.time; // new start time 985 mChangeCount++; 986 } 987 988 /** 989 * Returns true if the buffer is currently unused (contains zero entries). 990 * 991 * @return True if empty, false otherwise. 992 */ isBufferEmpty()993 private boolean isBufferEmpty() { 994 return mStart == mEnd; 995 } 996 997 /** 998 * Reads an entry from the specified index in the buffer. 999 * 1000 * @param index Index into the buffer from which to read. 1001 * @param timeReference Reference time to use when creating the {@link LogEntry}. 1002 * @param entryToSet Temporary entry to use instead of allocating a new one. 1003 * @return the log-entry instance that was read. 1004 */ readEntryAt(int index, long timeReference, LogEntry entryToSet)1005 private LogEntry readEntryAt(int index, long timeReference, LogEntry entryToSet) { 1006 for (int i = 0; i < MAX_LOG_ENTRY_BYTE_SIZE; i++) { 1007 int indexIntoMainBuffer = (index + i) % mBuffer.length; 1008 if (indexIntoMainBuffer == mEnd) { 1009 break; 1010 } 1011 mReadWriteTempBuffer[i] = mBuffer[indexIntoMainBuffer]; 1012 } 1013 return mTranslator.fromBytes(mReadWriteTempBuffer, timeReference, entryToSet); 1014 } 1015 1016 /** 1017 * Write a specified {@link LogEntry} to the buffer at the specified index. 1018 * 1019 * @param index Index in which to write in the buffer. 1020 * @param entry The entry to write into the buffer. 1021 * @param timeReference The reference time to use when calculating the relative time. 1022 */ writeEntryAt(int index, LogEntry entry, long timeReference)1023 private void writeEntryAt(int index, LogEntry entry, long timeReference) { 1024 int size = mTranslator.toBytes(entry, mReadWriteTempBuffer, timeReference); 1025 if (size > 0) { 1026 if (DEBUG) { 1027 Slog.d(TAG, "Writing Entry (" + index + ") [" + entry + "] as " 1028 + Arrays.toString(mReadWriteTempBuffer)); 1029 } 1030 writeBytesAt(index, mReadWriteTempBuffer, size); 1031 } 1032 } 1033 1034 /** 1035 * Write the specified bytes into the buffer at the specified index. 1036 * Handling wrap-around calculation for the ring-buffer. 1037 * 1038 * @param index The index from which to start writing. 1039 * @param buffer The buffer of bytes to be written. 1040 * @param size The amount of bytes to write from {@code buffer} to the log. 1041 */ writeBytesAt(int index, byte[] buffer, int size)1042 private void writeBytesAt(int index, byte[] buffer, int size) { 1043 for (int i = 0; i < size; i++) { 1044 int indexIntoMainBuffer = (index + i) % mBuffer.length; 1045 mBuffer[indexIntoMainBuffer] = buffer[i]; 1046 } 1047 if (DEBUG) { 1048 Slog.d(TAG, "Write Byte: " + Arrays.toString(buffer)); 1049 } 1050 } 1051 } 1052 1053 /** 1054 * An in-memory database of wake lock {@link TagData}. All tags stored in the database are given 1055 * a 7-bit index. This index is then used by {@link TheLog} when translating {@link LogEntry} 1056 * instanced into bytes. 1057 * 1058 * If a new tag is added when the database is full, the oldest tag is removed. The oldest tag 1059 * is calculated using {@link TagData#lastUsedTime}. 1060 */ 1061 static class TagDatabase { 1062 private final int mInvalidIndex; 1063 private final TagData[] mArray; 1064 private Callback mCallback; 1065 TagDatabase(Injector injector)1066 TagDatabase(Injector injector) { 1067 int size = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX); 1068 1069 // Largest possible index used as "INVALID", hence the (size - 1) sizing. 1070 mArray = new TagData[size - 1]; 1071 mInvalidIndex = size - 1; 1072 } 1073 1074 @Override toString()1075 public String toString() { 1076 StringBuilder sb = new StringBuilder(); 1077 sb.append("Tag Database: size(").append(mArray.length).append(")"); 1078 int entries = 0; 1079 int byteEstimate = 0; 1080 int tagSize = 0; 1081 int tags = 0; 1082 for (TagData tagData : mArray) { 1083 byteEstimate += 8; // reference pointer 1084 TagData data = tagData; 1085 if (data != null) { 1086 entries++; 1087 byteEstimate += data.getByteSize(); 1088 if (data.tag != null) { 1089 tags++; 1090 tagSize += data.tag.length(); 1091 } 1092 } 1093 } 1094 sb.append(", entries: ").append(entries); 1095 sb.append(", Bytes used: ").append(byteEstimate); 1096 if (DEBUG) { 1097 sb.append(", Avg tag size: ").append(tagSize / tags); 1098 sb.append("\n ").append(Arrays.toString(mArray)); 1099 } 1100 return sb.toString(); 1101 } 1102 1103 /** 1104 * Sets the callback. 1105 * 1106 * @param callback The callback to set. 1107 */ setCallback(Callback callback)1108 public void setCallback(Callback callback) { 1109 mCallback = callback; 1110 } 1111 1112 /** 1113 * Returns the tag corresponding to the specified index. 1114 * 1115 * @param index The index to search for. 1116 */ getTag(int index)1117 public TagData getTag(int index) { 1118 if (index < 0 || index >= mArray.length || index == mInvalidIndex) { 1119 return null; 1120 } 1121 return mArray[index]; 1122 } 1123 1124 /** 1125 * Returns an existing tag for the specified wake lock tag + ownerUid. 1126 * 1127 * @param tag The wake lock tag. 1128 * @param ownerUid The wake lock's ownerUid. 1129 * @return the TagData instance. 1130 */ getTag(String tag, int ownerUid)1131 public TagData getTag(String tag, int ownerUid) { 1132 return findOrCreateTag(tag, ownerUid, false /* shouldCreate */); 1133 } 1134 1135 /** 1136 * Returns the index for the corresponding tag. 1137 * 1138 * @param tagData The tag-data to search for. 1139 * @return the corresponding index, or mInvalidIndex of none is found. 1140 */ getTagIndex(TagData tagData)1141 public int getTagIndex(TagData tagData) { 1142 return tagData == null ? mInvalidIndex : tagData.index; 1143 } 1144 1145 /** 1146 * Returns a tag instance for the specified wake lock tag and ownerUid. If the data 1147 * does not exist in the database, it will be created if so specified by 1148 * {@code shouldCreate}. 1149 * 1150 * @param tagStr The wake lock's tag. 1151 * @param ownerUid The wake lock's owner Uid. 1152 * @param shouldCreate True when the tag should be created if it doesn't already exist. 1153 * @return The tag-data instance that was found or created. 1154 */ findOrCreateTag(String tagStr, int ownerUid, boolean shouldCreate)1155 public TagData findOrCreateTag(String tagStr, int ownerUid, boolean shouldCreate) { 1156 int firstAvailable = -1; 1157 TagData oldest = null; 1158 int oldestIndex = -1; 1159 1160 // Loop through and find the tag to be used. 1161 TagData tag = new TagData(tagStr, ownerUid); 1162 for (int i = 0; i < mArray.length; i++) { 1163 TagData current = mArray[i]; 1164 if (tag.equals(current)) { 1165 // found it 1166 return current; 1167 } else if (!shouldCreate) { 1168 continue; 1169 } else if (current != null) { 1170 // See if this entry is the oldest entry, in case 1171 // we need to replace it. 1172 if (oldest == null || current.lastUsedTime < oldest.lastUsedTime) { 1173 oldestIndex = i; 1174 oldest = current; 1175 } 1176 } else if (firstAvailable == -1) { 1177 firstAvailable = i; 1178 } 1179 } 1180 1181 // Item not found, and we shouldn't create one. 1182 if (!shouldCreate) { 1183 return null; 1184 } 1185 1186 // If we need to remove an index, report to listeners that we are removing an index. 1187 boolean useOldest = firstAvailable == -1; 1188 if (useOldest && mCallback != null) { 1189 if (DEBUG) { 1190 Slog.d(TAG, "Removing tag index: " + oldestIndex + " = " + oldest); 1191 } 1192 mCallback.onIndexRemoved(oldestIndex); 1193 } 1194 setToIndex(tag, firstAvailable != -1 ? firstAvailable : oldestIndex); 1195 return tag; 1196 } 1197 1198 /** 1199 * Updates the last-used-time of the specified tag. 1200 * 1201 * @param tag The tag to update. 1202 * @param time The new last-used-time for the tag. 1203 */ updateTagTime(TagData tag, long time)1204 public static void updateTagTime(TagData tag, long time) { 1205 if (tag != null) { 1206 tag.lastUsedTime = time; 1207 } 1208 } 1209 1210 /** 1211 * Sets a specified tag to the specified index. 1212 */ setToIndex(TagData tag, int index)1213 private void setToIndex(TagData tag, int index) { 1214 if (index < 0 || index >= mArray.length) { 1215 return; 1216 } 1217 TagData current = mArray[index]; 1218 if (current != null) { 1219 // clean up the reference in the TagData instance first. 1220 current.index = mInvalidIndex; 1221 1222 if (DEBUG) { 1223 Slog.d(TAG, "Replaced tag " + current.tag + " from index " + index + " with tag" 1224 + tag); 1225 } 1226 } 1227 1228 mArray[index] = tag; 1229 tag.index = index; 1230 } 1231 1232 /** 1233 * Callback on which to be notified of changes to {@link TagDatabase}. 1234 */ 1235 interface Callback { 1236 1237 /** 1238 * Handles removals of TagData indexes. 1239 * 1240 * @param index the index being removed. 1241 */ onIndexRemoved(int index)1242 void onIndexRemoved(int index); 1243 } 1244 } 1245 1246 /** 1247 * This class represents unique wake lock tags that are stored in {@link TagDatabase}. 1248 * Contains both the wake lock tag data (tag + ownerUid) as well as index and last-used 1249 * time data as it relates to the tag-database. 1250 */ 1251 static class TagData { 1252 public String tag; // Wake lock tag 1253 public int ownerUid; // Wake lock owner Uid 1254 public int index; // Index of the tag in the tag-database 1255 public long lastUsedTime; // Last time that this entry was used 1256 TagData(String tag, int ownerUid)1257 TagData(String tag, int ownerUid) { 1258 this.tag = tag; 1259 this.ownerUid = ownerUid; 1260 } 1261 1262 @Override equals(Object o)1263 public boolean equals(Object o) { 1264 if (this == o) { 1265 return true; 1266 } 1267 if (o instanceof TagData) { 1268 TagData other = (TagData) o; 1269 return TextUtils.equals(tag, other.tag) && ownerUid == other.ownerUid; 1270 } 1271 return false; 1272 } 1273 1274 @Override toString()1275 public String toString() { 1276 return "[" + ownerUid + " ; " + tag + "]"; 1277 } 1278 1279 /** 1280 * Returns an estimate of the number of bytes used by each instance of this class. 1281 * Used for debug purposes. 1282 * 1283 * @return the size of this tag-data. 1284 */ getByteSize()1285 int getByteSize() { 1286 int bytes = 0; 1287 bytes += 8; // tag reference-pointer; 1288 bytes += tag == null ? 0 : tag.length() * 2; 1289 bytes += 4; // ownerUid 1290 bytes += 4; // index 1291 bytes += 8; // lastUsedTime 1292 return bytes; 1293 } 1294 } 1295 1296 /** 1297 * Injector used by {@link WakeLockLog} for testing purposes. 1298 */ 1299 public static class Injector { getTagDatabaseSize()1300 public int getTagDatabaseSize() { 1301 return TAG_DATABASE_SIZE; 1302 } 1303 getLogSize()1304 public int getLogSize() { 1305 return LOG_SIZE; 1306 } 1307 currentTimeMillis()1308 public long currentTimeMillis() { 1309 return System.currentTimeMillis(); 1310 } 1311 getDateFormat()1312 public SimpleDateFormat getDateFormat() { 1313 return DATE_FORMAT; 1314 } 1315 } 1316 } 1317