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