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