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