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