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