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