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