1 /*
2  * Copyright 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 android.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Bundle;
22 
23 import java.nio.ByteBuffer;
24 import java.nio.ByteOrder;
25 import java.nio.charset.Charset;
26 import java.nio.charset.StandardCharsets;
27 import java.util.Objects;
28 
29 /**
30  * MediaMetrics is the Java interface to the MediaMetrics service.
31  *
32  * This is used to collect media statistics by the framework.
33  * It is not intended for direct application use.
34  *
35  * @hide
36  */
37 public class MediaMetrics {
38     public static final String TAG = "MediaMetrics";
39 
40     public static final String SEPARATOR = ".";
41 
42     /**
43      * A list of established MediaMetrics names that can be used for Items.
44      */
45     public static class Name {
46         public static final String AUDIO = "audio";
47         public static final String AUDIO_BLUETOOTH = AUDIO + SEPARATOR + "bluetooth";
48         public static final String AUDIO_DEVICE = AUDIO + SEPARATOR + "device";
49         public static final String AUDIO_FOCUS = AUDIO + SEPARATOR + "focus";
50         public static final String AUDIO_FORCE_USE = AUDIO + SEPARATOR + "forceUse";
51         public static final String AUDIO_MIC = AUDIO + SEPARATOR + "mic";
52         public static final String AUDIO_MIDI = AUDIO + SEPARATOR + "midi";
53         public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode";
54         public static final String AUDIO_SERVICE = AUDIO + SEPARATOR + "service";
55         public static final String AUDIO_VOLUME = AUDIO + SEPARATOR + "volume";
56         public static final String AUDIO_VOLUME_EVENT = AUDIO_VOLUME + SEPARATOR + "event";
57         public static final String METRICS_MANAGER = "metrics" + SEPARATOR + "manager";
58     }
59 
60     /**
61      * A list of established string values.
62      */
63     public static class Value {
64         public static final String CONNECT = "connect";
65         public static final String CONNECTED = "connected";
66         public static final String DISCONNECT = "disconnect";
67         public static final String DISCONNECTED = "disconnected";
68         public static final String DOWN = "down";
69         public static final String MUTE = "mute";
70         public static final String NO = "no";
71         public static final String OFF = "off";
72         public static final String ON = "on";
73         public static final String UNMUTE = "unmute";
74         public static final String UP = "up";
75         public static final String YES = "yes";
76     }
77 
78     /**
79      * A list of standard property keys for consistent use and type.
80      */
81     public static class Property {
82         // A use for Bluetooth or USB device addresses
83         public static final Key<String> ADDRESS = createKey("address", String.class);
84         // A string representing the Audio Attributes
85         public static final Key<String> ATTRIBUTES = createKey("attributes", String.class);
86 
87         // The calling package responsible for the state change
88         public static final Key<String> CALLING_PACKAGE =
89                 createKey("callingPackage", String.class);
90 
91         // The client name
92         public static final Key<String> CLIENT_NAME = createKey("clientName", String.class);
93 
94         public static final Key<Integer> CLOSED_COUNT =
95                 createKey("closedCount", Integer.class); // MIDI
96 
97         // The device type
98         public static final Key<Integer> DELAY_MS = createKey("delayMs", Integer.class);
99 
100         // The device type
101         public static final Key<String> DEVICE = createKey("device", String.class);
102 
103         // Whether the device is disconnected. This is either "true" or "false"
104         public static final Key<String> DEVICE_DISCONNECTED =
105                 createKey("deviceDisconnected", String.class); // MIDI
106 
107         // The ID of the device
108         public static final Key<Integer> DEVICE_ID =
109                 createKey("deviceId", Integer.class); // MIDI
110 
111         // For volume changes, up or down
112         public static final Key<String> DIRECTION = createKey("direction", String.class);
113         public static final Key<Long> DURATION_NS =
114                 createKey("durationNs", Long.class); // MIDI
115         // A reason for early return or error
116         public static final Key<String> EARLY_RETURN =
117                 createKey("earlyReturn", String.class);
118         // ENCODING_ ... string to match AudioFormat encoding
119         public static final Key<String> ENCODING = createKey("encoding", String.class);
120 
121         public static final Key<String> EVENT = createKey("event#", String.class);
122 
123         // Generally string "true" or "false"
124         public static final Key<String> ENABLED = createKey("enabled", String.class);
125 
126         // event generated is external (yes, no)
127         public static final Key<String> EXTERNAL = createKey("external", String.class);
128 
129         public static final Key<Integer> FLAGS = createKey("flags", Integer.class);
130         public static final Key<String> FOCUS_CHANGE_HINT =
131                 createKey("focusChangeHint", String.class);
132         public static final Key<String> FORCE_USE_DUE_TO =
133                 createKey("forceUseDueTo", String.class);
134         public static final Key<String> FORCE_USE_MODE =
135                 createKey("forceUseMode", String.class);
136         public static final Key<Double> GAIN_DB =
137                 createKey("gainDb", Double.class);
138         public static final Key<String> GROUP =
139                 createKey("group", String.class);
140 
141         // Generally string "true" or "false"
142         public static final Key<String> HAS_HEAD_TRACKER =
143                 createKey("hasHeadTracker", String.class);     // spatializer
144         public static final Key<Integer> HARDWARE_TYPE =
145                 createKey("hardwareType", Integer.class); // MIDI
146         // Generally string "true" or "false"
147         public static final Key<String> HEAD_TRACKER_ENABLED =
148                 createKey("headTrackerEnabled", String.class); // spatializer
149 
150         public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume
151         public static final Key<Integer> INPUT_PORT_COUNT =
152                 createKey("inputPortCount", Integer.class); // MIDI
153         // Either "true" or "false"
154         public static final Key<String> IS_SHARED = createKey("isShared", String.class); // MIDI
155         public static final Key<String> LOG_SESSION_ID = createKey("logSessionId", String.class);
156         public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class); // vol
157         public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class); // vol
158         public static final Key<String> MODE =
159                 createKey("mode", String.class); // audio_mode
160         public static final Key<String> MUTE =
161                 createKey("mute", String.class); // microphone, on or off.
162 
163         // Bluetooth or Usb device name
164         public static final Key<String> NAME =
165                 createKey("name", String.class);
166 
167         // Number of observers
168         public static final Key<Integer> OBSERVERS =
169                 createKey("observers", Integer.class);
170 
171         public static final Key<Integer> OPENED_COUNT =
172                 createKey("openedCount", Integer.class); // MIDI
173         public static final Key<Integer> OUTPUT_PORT_COUNT =
174                 createKey("outputPortCount", Integer.class); // MIDI
175 
176         public static final Key<String> REQUEST =
177                 createKey("request", String.class);
178 
179         // For audio mode
180         public static final Key<String> REQUESTED_MODE =
181                 createKey("requestedMode", String.class); // audio_mode
182 
183         // For Bluetooth
184         public static final Key<String> SCO_AUDIO_MODE =
185                 createKey("scoAudioMode", String.class);
186         public static final Key<Integer> SDK = createKey("sdk", Integer.class);
187         public static final Key<String> STATE = createKey("state", String.class);
188         public static final Key<Integer> STATUS = createKey("status", Integer.class);
189         public static final Key<String> STREAM_TYPE = createKey("streamType", String.class);
190 
191         // The following MIDI string is generally either "true" or "false"
192         public static final Key<String> SUPPORTS_MIDI_UMP =
193                 createKey("supportsMidiUmp", String.class); // Universal MIDI Packets
194 
195         public static final Key<Integer> TOTAL_INPUT_BYTES =
196                 createKey("totalInputBytes", Integer.class); // MIDI
197         public static final Key<Integer> TOTAL_OUTPUT_BYTES =
198                 createKey("totalOutputBytes", Integer.class); // MIDI
199 
200         // The following MIDI string is generally either "true" or "false"
201         public static final Key<String> USING_ALSA = createKey("usingAlsa", String.class);
202     }
203 
204     /**
205      * The TYPE constants below should match those in native MediaMetricsItem.h
206      */
207     private static final int TYPE_NONE = 0;
208     private static final int TYPE_INT32 = 1;     // Java integer
209     private static final int TYPE_INT64 = 2;     // Java long
210     private static final int TYPE_DOUBLE = 3;    // Java double
211     private static final int TYPE_CSTRING = 4;   // Java string
212     private static final int TYPE_RATE = 5;      // Two longs, ignored in Java
213 
214     // The charset used for encoding Strings to bytes.
215     private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8;
216 
217     /**
218      * Key interface.
219      *
220      * The presence of this {@code Key} interface on an object allows
221      * it to be used to set metrics.
222      *
223      * @param <T> type of value associated with {@code Key}.
224      */
225     public interface Key<T> {
226         /**
227          * Returns the internal name of the key.
228          */
229         @NonNull
getName()230         String getName();
231 
232         /**
233          * Returns the class type of the associated value.
234          */
235         @NonNull
getValueClass()236         Class<T> getValueClass();
237     }
238 
239     /**
240      * Returns a Key object with the correct interface for MediaMetrics.
241      *
242      * @param name The name of the key.
243      * @param type The class type of the value represented by the key.
244      * @param <T> The type of value.
245      * @return a new key interface.
246      */
247     @NonNull
createKey(@onNull String name, @NonNull Class<T> type)248     public static <T> Key<T> createKey(@NonNull String name, @NonNull Class<T> type) {
249         // Implementation specific.
250         return new Key<T>() {
251             private final String mName = name;
252             private final Class<T> mType = type;
253 
254             @Override
255             @NonNull
256             public String getName() {
257                 return mName;
258             }
259 
260             @Override
261             @NonNull
262             public Class<T> getValueClass() {
263                 return mType;
264             }
265 
266             /**
267              * Return true if the name and the type of two objects are the same.
268              */
269             @Override
270             public boolean equals(Object obj) {
271                 if (obj == this) {
272                     return true;
273                 }
274                 if (!(obj instanceof Key)) {
275                     return false;
276                 }
277                 Key<?> other = (Key<?>) obj;
278                 return mName.equals(other.getName()) && mType.equals(other.getValueClass());
279             }
280 
281             @Override
282             public int hashCode() {
283                 return Objects.hash(mName, mType);
284             }
285         };
286     }
287 
288     /**
289      * Item records properties and delivers to the MediaMetrics service
290      *
291      */
292     public static class Item {
293 
294         /*
295          * MediaMetrics Item
296          *
297          * Creates a Byte String and sends to the MediaMetrics service.
298          * The Byte String serves as a compact form for logging data
299          * with low overhead for storage.
300          *
301          * The Byte String format is as follows:
302          *
303          * For Java
304          *  int64 corresponds to long
305          *  int32, uint32 corresponds to int
306          *  uint16 corresponds to char
307          *  uint8, int8 corresponds to byte
308          *
309          * For items transmitted from Java, uint8 and uint32 values are limited
310          * to INT8_MAX and INT32_MAX.  This constrains the size of large items
311          * to 2GB, which is consistent with ByteBuffer max size. A native item
312          * can conceivably have size of 4GB.
313          *
314          * Physical layout of integers and doubles within the MediaMetrics byte string
315          * is in Native / host order, which is usually little endian.
316          *
317          * Note that primitive data (ints, doubles) within a Byte String has
318          * no extra padding or alignment requirements, like ByteBuffer.
319          *
320          * -- begin of item
321          * -- begin of header
322          * (uint32) item size: including the item size field
323          * (uint32) header size, including the item size and header size fields.
324          * (uint16) version: exactly 0
325          * (uint16) key size, that is key strlen + 1 for zero termination.
326          * (int8)+ key, a string which is 0 terminated (UTF-8).
327          * (int32) pid
328          * (int32) uid
329          * (int64) timestamp
330          * -- end of header
331          * -- begin body
332          * (uint32) number of properties
333          * -- repeat for number of properties
334          *     (uint16) property size, including property size field itself
335          *     (uint8) type of property
336          *     (int8)+ key string, including 0 termination
337          *      based on type of property (given above), one of:
338          *       (int32)
339          *       (int64)
340          *       (double)
341          *       (int8)+ for TYPE_CSTRING, including 0 termination
342          *       (int64, int64) for rate
343          * -- end body
344          * -- end of item
345          *
346          * To record a MediaMetrics event, one creates a new item with an id,
347          * then use a series of puts to add properties
348          * and then a record() to send to the MediaMetrics service.
349          *
350          * The properties may not be unique, and putting a later property with
351          * the same name as an earlier property will overwrite the value and type
352          * of the prior property.
353          *
354          * The timestamp can only be recorded by a system service (and is ignored otherwise;
355          * the MediaMetrics service will fill in the timestamp as needed).
356          *
357          * The units of time are in SystemClock.elapsedRealtimeNanos().
358          *
359          * A clear() may be called to reset the properties to empty, the time to 0, but keep
360          * the other entries the same. This may be called after record().
361          * Additional properties may be added after calling record().  Changing the same property
362          * repeatedly is discouraged as - for this particular implementation - extra data
363          * is stored per change.
364          *
365          * new MediaMetrics.Item(mSomeId)
366          *     .putString("event", "javaCreate")
367          *     .putInt("value", intValue)
368          *     .record();
369          */
370 
371         /**
372          * Creates an Item with server added uid, time.
373          *
374          * This is the typical way to record a MediaMetrics item.
375          *
376          * @param key the Metrics ID associated with the item.
377          */
Item(String key)378         public Item(String key) {
379             this(key, -1 /* pid */, -1 /* uid */, 0 /* SystemClock.elapsedRealtimeNanos() */,
380                     2048 /* capacity */);
381         }
382 
383         /**
384          * Creates an Item specifying pid, uid, time, and initial Item capacity.
385          *
386          * This might be used by a service to specify a different PID or UID for a client.
387          *
388          * @param key the Metrics ID associated with the item.
389          *        An app may only set properties on an item which has already been
390          *        logged previously by a service.
391          * @param pid the process ID corresponding to the item.
392          *        A value of -1 (or a record() from an app instead of a service) causes
393          *        the MediaMetrics service to fill this in.
394          * @param uid the user ID corresponding to the item.
395          *        A value of -1 (or a record() from an app instead of a service) causes
396          *        the MediaMetrics service to fill this in.
397          * @param timeNs the time when the item occurred (may be in the past).
398          *        A value of 0 (or a record() from an app instead of a service) causes
399          *        the MediaMetrics service to fill it in.
400          *        Should be obtained from SystemClock.elapsedRealtimeNanos().
401          * @param capacity the anticipated size to use for the buffer.
402          *        If the capacity is too small, the buffer will be resized to accommodate.
403          *        This is amortized to copy data no more than twice.
404          */
Item(String key, int pid, int uid, long timeNs, int capacity)405         public Item(String key, int pid, int uid, long timeNs, int capacity) {
406             final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
407             final int keyLength = keyBytes.length;
408             if (keyLength > Character.MAX_VALUE - 1) {
409                 throw new IllegalArgumentException("Key length too large");
410             }
411 
412             // Version 0 - compute the header offsets here.
413             mHeaderSize = 4 + 4 + 2 + 2 + keyLength + 1 + 4 + 4 + 8; // see format above.
414             mPidOffset = mHeaderSize - 16;
415             mUidOffset = mHeaderSize - 12;
416             mTimeNsOffset = mHeaderSize - 8;
417             mPropertyCountOffset = mHeaderSize;
418             mPropertyStartOffset = mHeaderSize + 4;
419 
420             mKey = key;
421             mBuffer = ByteBuffer.allocateDirect(
422                     Math.max(capacity, mHeaderSize + MINIMUM_PAYLOAD_SIZE));
423 
424             // Version 0 - fill the ByteBuffer with the header (some details updated later).
425             mBuffer.order(ByteOrder.nativeOrder())
426                 .putInt((int) 0)                      // total size in bytes (filled in later)
427                 .putInt((int) mHeaderSize)            // size of header
428                 .putChar((char) FORMAT_VERSION)       // version
429                 .putChar((char) (keyLength + 1))      // length, with zero termination
430                 .put(keyBytes).put((byte) 0)
431                 .putInt(pid)
432                 .putInt(uid)
433                 .putLong(timeNs);
434             if (mHeaderSize != mBuffer.position()) {
435                 throw new IllegalStateException("Mismatched sizing");
436             }
437             mBuffer.putInt(0);     // number of properties (to be later filled in by record()).
438         }
439 
440         /**
441          * Sets a metrics typed key
442          * @param key
443          * @param value
444          * @param <T>
445          * @return
446          */
447         @NonNull
set(@onNull Key<T> key, @Nullable T value)448         public <T> Item set(@NonNull Key<T> key, @Nullable T value) {
449             if (value instanceof Integer) {
450                 putInt(key.getName(), (int) value);
451             } else if (value instanceof Long) {
452                 putLong(key.getName(), (long) value);
453             } else if (value instanceof Double) {
454                 putDouble(key.getName(), (double) value);
455             } else if (value instanceof String) {
456                 putString(key.getName(), (String) value);
457             }
458             // if value is null, etc. no error is raised.
459             return this;
460         }
461 
462         /**
463          * Sets the property with key to an integer (32 bit) value.
464          *
465          * @param key
466          * @param value
467          * @return itself
468          */
putInt(String key, int value)469         public Item putInt(String key, int value) {
470             final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
471             final char propSize = (char) reserveProperty(keyBytes, 4 /* payloadSize */);
472             final int estimatedFinalPosition = mBuffer.position() + propSize;
473             mBuffer.putChar(propSize)
474                 .put((byte) TYPE_INT32)
475                 .put(keyBytes).put((byte) 0) // key, zero terminated
476                 .putInt(value);
477             ++mPropertyCount;
478             if (mBuffer.position() != estimatedFinalPosition) {
479                 throw new IllegalStateException("Final position " + mBuffer.position()
480                         + " != estimatedFinalPosition " + estimatedFinalPosition);
481             }
482             return this;
483         }
484 
485         /**
486          * Sets the property with key to a long (64 bit) value.
487          *
488          * @param key
489          * @param value
490          * @return itself
491          */
putLong(String key, long value)492         public Item putLong(String key, long value) {
493             final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
494             final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
495             final int estimatedFinalPosition = mBuffer.position() + propSize;
496             mBuffer.putChar(propSize)
497                 .put((byte) TYPE_INT64)
498                 .put(keyBytes).put((byte) 0) // key, zero terminated
499                 .putLong(value);
500             ++mPropertyCount;
501             if (mBuffer.position() != estimatedFinalPosition) {
502                 throw new IllegalStateException("Final position " + mBuffer.position()
503                     + " != estimatedFinalPosition " + estimatedFinalPosition);
504             }
505             return this;
506         }
507 
508         /**
509          * Sets the property with key to a double value.
510          *
511          * @param key
512          * @param value
513          * @return itself
514          */
putDouble(String key, double value)515         public Item putDouble(String key, double value) {
516             final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
517             final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
518             final int estimatedFinalPosition = mBuffer.position() + propSize;
519             mBuffer.putChar(propSize)
520                 .put((byte) TYPE_DOUBLE)
521                 .put(keyBytes).put((byte) 0) // key, zero terminated
522                 .putDouble(value);
523             ++mPropertyCount;
524             if (mBuffer.position() != estimatedFinalPosition) {
525                 throw new IllegalStateException("Final position " + mBuffer.position()
526                     + " != estimatedFinalPosition " + estimatedFinalPosition);
527             }
528             return this;
529         }
530 
531         /**
532          * Sets the property with key to a String value.
533          *
534          * @param key
535          * @param value
536          * @return itself
537          */
putString(String key, String value)538         public Item putString(String key, String value) {
539             final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
540             final byte[] valueBytes = value.getBytes(MEDIAMETRICS_CHARSET);
541             final char propSize = (char) reserveProperty(keyBytes, valueBytes.length + 1);
542             final int estimatedFinalPosition = mBuffer.position() + propSize;
543             mBuffer.putChar(propSize)
544                 .put((byte) TYPE_CSTRING)
545                 .put(keyBytes).put((byte) 0) // key, zero terminated
546                 .put(valueBytes).put((byte) 0); // value, zero term.
547             ++mPropertyCount;
548             if (mBuffer.position() != estimatedFinalPosition) {
549                 throw new IllegalStateException("Final position " + mBuffer.position()
550                     + " != estimatedFinalPosition " + estimatedFinalPosition);
551             }
552             return this;
553         }
554 
555         /**
556          * Sets the pid to the provided value.
557          *
558          * @param pid which can be -1 if the service is to fill it in from the calling info.
559          * @return itself
560          */
setPid(int pid)561         public Item setPid(int pid) {
562             mBuffer.putInt(mPidOffset, pid); // pid location in byte string.
563             return this;
564         }
565 
566         /**
567          * Sets the uid to the provided value.
568          *
569          * The UID represents the client associated with the property. This must be the UID
570          * of the application if it comes from the application client.
571          *
572          * Trusted services are allowed to set the uid for a client-related item.
573          *
574          * @param uid which can be -1 if the service is to fill it in from calling info.
575          * @return itself
576          */
setUid(int uid)577         public Item setUid(int uid) {
578             mBuffer.putInt(mUidOffset, uid); // uid location in byte string.
579             return this;
580         }
581 
582         /**
583          * Sets the timestamp to the provided value.
584          *
585          * The time is referenced by the Boottime obtained by SystemClock.elapsedRealtimeNanos().
586          * This should be associated with the occurrence of the event.  It is recommended that
587          * the event be registered immediately when it occurs, and no later than 500ms
588          * (and certainly not in the future).
589          *
590          * @param timeNs which can be 0 if the service is to fill it in at the time of call.
591          * @return itself
592          */
setTimestamp(long timeNs)593         public Item setTimestamp(long timeNs) {
594             mBuffer.putLong(mTimeNsOffset, timeNs); // time location in byte string.
595             return this;
596         }
597 
598         /**
599          * Clears the properties and resets the time to 0.
600          *
601          * No other values are changed.
602          *
603          * @return itself
604          */
clear()605         public Item clear() {
606             mBuffer.position(mPropertyStartOffset);
607             mBuffer.limit(mBuffer.capacity());
608             mBuffer.putLong(mTimeNsOffset, 0); // reset time.
609             mPropertyCount = 0;
610             return this;
611         }
612 
613         /**
614          * Sends the item to the MediaMetrics service.
615          *
616          * The item properties are unchanged, hence record() may be called more than once
617          * to send the same item twice. Also, record() may be called without any properties.
618          *
619          * @return true if successful.
620          */
record()621         public boolean record() {
622             updateHeader();
623             return native_submit_bytebuffer(mBuffer, mBuffer.limit()) >= 0;
624         }
625 
626         /**
627          * Converts the Item to a Bundle.
628          *
629          * This is primarily used as a test API for CTS.
630          *
631          * @return a Bundle with the keys set according to data in the Item's buffer.
632          */
toBundle()633         public Bundle toBundle() {
634             updateHeader();
635 
636             final ByteBuffer buffer = mBuffer.duplicate();
637             buffer.order(ByteOrder.nativeOrder()) // restore order property
638                 .flip();                          // convert from write buffer to read buffer
639 
640             return toBundle(buffer);
641         }
642 
643         // The following constants are used for tests to extract
644         // the content of the Bundle for CTS testing.
645         public static final String BUNDLE_TOTAL_SIZE = "_totalSize";
646         public static final String BUNDLE_HEADER_SIZE = "_headerSize";
647         public static final String BUNDLE_VERSION = "_version";
648         public static final String BUNDLE_KEY_SIZE = "_keySize";
649         public static final String BUNDLE_KEY = "_key";
650         public static final String BUNDLE_PID = "_pid";
651         public static final String BUNDLE_UID = "_uid";
652         public static final String BUNDLE_TIMESTAMP = "_timestamp";
653         public static final String BUNDLE_PROPERTY_COUNT = "_propertyCount";
654 
655         /**
656          * Converts a buffer contents to a bundle
657          *
658          * This is primarily used as a test API for CTS.
659          *
660          * @param buffer contains the byte data serialized according to the byte string version.
661          * @return a Bundle with the keys set according to data in the buffer.
662          */
toBundle(ByteBuffer buffer)663         public static Bundle toBundle(ByteBuffer buffer) {
664             final Bundle bundle = new Bundle();
665 
666             final int totalSize = buffer.getInt();
667             final int headerSize = buffer.getInt();
668             final char version = buffer.getChar();
669             final char keySize = buffer.getChar(); // includes zero termination, i.e. keyLength + 1
670 
671             if (totalSize < 0 || headerSize < 0) {
672                 throw new IllegalArgumentException("Item size cannot be > " + Integer.MAX_VALUE);
673             }
674             final String key;
675             if (keySize > 0) {
676                 key = getStringFromBuffer(buffer, keySize);
677             } else {
678                 throw new IllegalArgumentException("Illegal null key");
679             }
680 
681             final int pid = buffer.getInt();
682             final int uid = buffer.getInt();
683             final long timestamp = buffer.getLong();
684 
685             // Verify header size (depending on version).
686             final int headerRead = buffer.position();
687             if (version == 0) {
688                 if (headerRead != headerSize) {
689                     throw new IllegalArgumentException(
690                             "Item key:" + key
691                             + " headerRead:" + headerRead + " != headerSize:" + headerSize);
692                 }
693             } else {
694                 // future versions should only increase header size
695                 // by adding to the end.
696                 if (headerRead > headerSize) {
697                     throw new IllegalArgumentException(
698                             "Item key:" + key
699                             + " headerRead:" + headerRead + " > headerSize:" + headerSize);
700                 } else if (headerRead < headerSize) {
701                     buffer.position(headerSize);
702                 }
703             }
704 
705             // Body always starts with properties.
706             final int propertyCount = buffer.getInt();
707             if (propertyCount < 0) {
708                 throw new IllegalArgumentException(
709                         "Cannot have more than " + Integer.MAX_VALUE + " properties");
710             }
711             bundle.putInt(BUNDLE_TOTAL_SIZE, totalSize);
712             bundle.putInt(BUNDLE_HEADER_SIZE, headerSize);
713             bundle.putChar(BUNDLE_VERSION, version);
714             bundle.putChar(BUNDLE_KEY_SIZE, keySize);
715             bundle.putString(BUNDLE_KEY, key);
716             bundle.putInt(BUNDLE_PID, pid);
717             bundle.putInt(BUNDLE_UID, uid);
718             bundle.putLong(BUNDLE_TIMESTAMP, timestamp);
719             bundle.putInt(BUNDLE_PROPERTY_COUNT, propertyCount);
720 
721             for (int i = 0; i < propertyCount; ++i) {
722                 final int initialBufferPosition = buffer.position();
723                 final char propSize = buffer.getChar();
724                 final byte type = buffer.get();
725 
726                 // Log.d(TAG, "(" + i + ") propSize:" + ((int)propSize) + " type:" + type);
727                 final String propKey = getStringFromBuffer(buffer);
728                 switch (type) {
729                     case TYPE_INT32:
730                         bundle.putInt(propKey, buffer.getInt());
731                         break;
732                     case TYPE_INT64:
733                         bundle.putLong(propKey, buffer.getLong());
734                         break;
735                     case TYPE_DOUBLE:
736                         bundle.putDouble(propKey, buffer.getDouble());
737                         break;
738                     case TYPE_CSTRING:
739                         bundle.putString(propKey, getStringFromBuffer(buffer));
740                         break;
741                     case TYPE_NONE:
742                         break; // ignore on Java side
743                     case TYPE_RATE:
744                         buffer.getLong();  // consume the first int64_t of rate
745                         buffer.getLong();  // consume the second int64_t of rate
746                         break; // ignore on Java side
747                     default:
748                         // These are unsupported types for version 0
749                         // We ignore them if the version is greater than 0.
750                         if (version == 0) {
751                             throw new IllegalArgumentException(
752                                     "Property " + propKey + " has unsupported type " + type);
753                         }
754                         buffer.position(initialBufferPosition + propSize); // advance and skip
755                         break;
756                 }
757                 final int deltaPosition = buffer.position() - initialBufferPosition;
758                 if (deltaPosition != propSize) {
759                     throw new IllegalArgumentException("propSize:" + propSize
760                         + " != deltaPosition:" + deltaPosition);
761                 }
762             }
763 
764             final int finalPosition = buffer.position();
765             if (finalPosition != totalSize) {
766                 throw new IllegalArgumentException("totalSize:" + totalSize
767                     + " != finalPosition:" + finalPosition);
768             }
769             return bundle;
770         }
771 
772         // Version 0 byte offsets for the header.
773         private static final int FORMAT_VERSION = 0;
774         private static final int TOTAL_SIZE_OFFSET = 0;
775         private static final int HEADER_SIZE_OFFSET = 4;
776         private static final int MINIMUM_PAYLOAD_SIZE = 4;
777         private final int mPidOffset;            // computed in constructor
778         private final int mUidOffset;            // computed in constructor
779         private final int mTimeNsOffset;         // computed in constructor
780         private final int mPropertyCountOffset;  // computed in constructor
781         private final int mPropertyStartOffset;  // computed in constructor
782         private final int mHeaderSize;           // computed in constructor
783 
784         private final String mKey;
785 
786         private ByteBuffer mBuffer;     // may be reallocated if capacity is insufficient.
787         private int mPropertyCount = 0; // overflow not checked (mBuffer would overflow first).
788 
reserveProperty(byte[] keyBytes, int payloadSize)789         private int reserveProperty(byte[] keyBytes, int payloadSize) {
790             final int keyLength = keyBytes.length;
791             if (keyLength > Character.MAX_VALUE) {
792                 throw new IllegalStateException("property key too long "
793                         + new String(keyBytes, MEDIAMETRICS_CHARSET));
794             }
795             if (payloadSize > Character.MAX_VALUE) {
796                 throw new IllegalStateException("payload too large " + payloadSize);
797             }
798 
799             // See the byte string property format above.
800             final int size = 2      /* length */
801                     + 1             /* type */
802                     + keyLength + 1 /* key length with zero termination */
803                     + payloadSize;  /* payload size */
804 
805             if (size > Character.MAX_VALUE) {
806                 throw new IllegalStateException("Item property "
807                         + new String(keyBytes, MEDIAMETRICS_CHARSET) + " is too large to send");
808             }
809 
810             if (mBuffer.remaining() < size) {
811                 int newCapacity = mBuffer.position() + size;
812                 if (newCapacity > Integer.MAX_VALUE >> 1) {
813                     throw new IllegalStateException(
814                         "Item memory requirements too large: " + newCapacity);
815                 }
816                 newCapacity <<= 1;
817                 ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity);
818                 buffer.order(ByteOrder.nativeOrder());
819 
820                 // Copy data from old buffer to new buffer.
821                 mBuffer.flip();
822                 buffer.put(mBuffer);
823 
824                 // set buffer to new buffer
825                 mBuffer = buffer;
826             }
827             return size;
828         }
829 
830         // Used for test
getStringFromBuffer(ByteBuffer buffer)831         private static String getStringFromBuffer(ByteBuffer buffer) {
832             return getStringFromBuffer(buffer, Integer.MAX_VALUE);
833         }
834 
835         // Used for test
getStringFromBuffer(ByteBuffer buffer, int size)836         private static String getStringFromBuffer(ByteBuffer buffer, int size) {
837             int i = buffer.position();
838             int limit = buffer.limit();
839             if (size < Integer.MAX_VALUE - i && i + size < limit) {
840                 limit = i + size;
841             }
842             for (; i < limit; ++i) {
843                 if (buffer.get(i) == 0) {
844                     final int newPosition = i + 1;
845                     if (size != Integer.MAX_VALUE && newPosition - buffer.position() != size) {
846                         throw new IllegalArgumentException("chars consumed at " + i + ": "
847                             + (newPosition - buffer.position()) + " != size: " + size);
848                     }
849                     final String found;
850                     if (buffer.hasArray()) {
851                         found = new String(
852                             buffer.array(), buffer.position() + buffer.arrayOffset(),
853                             i - buffer.position(), MEDIAMETRICS_CHARSET);
854                         buffer.position(newPosition);
855                     } else {
856                         final byte[] array = new byte[i - buffer.position()];
857                         buffer.get(array);
858                         found = new String(array, MEDIAMETRICS_CHARSET);
859                         buffer.get(); // remove 0.
860                     }
861                     return found;
862                 }
863             }
864             throw new IllegalArgumentException(
865                     "No zero termination found in string position: "
866                     + buffer.position() + " end: " + i);
867         }
868 
869         /**
870          * May be called multiple times - just makes the header consistent with the current
871          * properties written.
872          */
updateHeader()873         private void updateHeader() {
874             // Buffer sized properly in constructor.
875             mBuffer.putInt(TOTAL_SIZE_OFFSET, mBuffer.position())      // set total length
876                 .putInt(mPropertyCountOffset, (char) mPropertyCount); // set number of properties
877         }
878     }
879 
native_submit_bytebuffer(@onNull ByteBuffer buffer, int length)880     private static native int native_submit_bytebuffer(@NonNull ByteBuffer buffer, int length);
881 }
882