1 /* 2 * Copyright (C) 2016 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 package com.google.android.exoplayer2.util; 17 18 import static android.content.Context.UI_MODE_SERVICE; 19 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_BACK; 20 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_FORWARD; 21 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM; 22 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; 23 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_MEDIA_ITEM; 24 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT; 25 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; 26 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS; 27 import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM; 28 import static com.google.android.exoplayer2.util.Assertions.checkNotNull; 29 import static java.lang.Math.abs; 30 import static java.lang.Math.max; 31 import static java.lang.Math.min; 32 33 import android.Manifest.permission; 34 import android.annotation.SuppressLint; 35 import android.app.Activity; 36 import android.app.UiModeManager; 37 import android.content.ComponentName; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.content.pm.PackageInfo; 41 import android.content.pm.PackageManager; 42 import android.content.pm.PackageManager.NameNotFoundException; 43 import android.content.res.Configuration; 44 import android.content.res.Resources; 45 import android.database.DatabaseUtils; 46 import android.database.sqlite.SQLiteDatabase; 47 import android.graphics.Point; 48 import android.hardware.display.DisplayManager; 49 import android.media.AudioFormat; 50 import android.media.AudioManager; 51 import android.media.MediaDrm; 52 import android.net.Uri; 53 import android.os.Build; 54 import android.os.Handler; 55 import android.os.Looper; 56 import android.os.Parcel; 57 import android.os.SystemClock; 58 import android.security.NetworkSecurityPolicy; 59 import android.telephony.TelephonyManager; 60 import android.text.TextUtils; 61 import android.util.Base64; 62 import android.util.SparseLongArray; 63 import android.view.Display; 64 import android.view.SurfaceView; 65 import android.view.WindowManager; 66 import androidx.annotation.Nullable; 67 import androidx.annotation.RequiresApi; 68 import com.google.android.exoplayer2.C; 69 import com.google.android.exoplayer2.C.ContentType; 70 import com.google.android.exoplayer2.ExoPlayerLibraryInfo; 71 import com.google.android.exoplayer2.Format; 72 import com.google.android.exoplayer2.MediaItem; 73 import com.google.android.exoplayer2.ParserException; 74 import com.google.android.exoplayer2.PlaybackException; 75 import com.google.android.exoplayer2.Player; 76 import com.google.android.exoplayer2.Player.Commands; 77 import com.google.common.base.Ascii; 78 import com.google.common.base.Charsets; 79 import java.io.ByteArrayOutputStream; 80 import java.io.Closeable; 81 import java.io.File; 82 import java.io.IOException; 83 import java.io.InputStream; 84 import java.lang.reflect.Method; 85 import java.math.BigDecimal; 86 import java.nio.ByteBuffer; 87 import java.nio.ByteOrder; 88 import java.util.ArrayDeque; 89 import java.util.Arrays; 90 import java.util.Calendar; 91 import java.util.Collections; 92 import java.util.Formatter; 93 import java.util.GregorianCalendar; 94 import java.util.HashMap; 95 import java.util.List; 96 import java.util.Locale; 97 import java.util.MissingResourceException; 98 import java.util.NoSuchElementException; 99 import java.util.TimeZone; 100 import java.util.UUID; 101 import java.util.concurrent.ExecutorService; 102 import java.util.concurrent.Executors; 103 import java.util.regex.Matcher; 104 import java.util.regex.Pattern; 105 import java.util.zip.DataFormatException; 106 import java.util.zip.GZIPOutputStream; 107 import java.util.zip.Inflater; 108 import org.checkerframework.checker.initialization.qual.UnknownInitialization; 109 import org.checkerframework.checker.nullness.compatqual.NullableType; 110 import org.checkerframework.checker.nullness.qual.EnsuresNonNull; 111 import org.checkerframework.checker.nullness.qual.PolyNull; 112 113 /** Miscellaneous utility methods. */ 114 public final class Util { 115 116 /** 117 * Like {@link android.os.Build.VERSION#SDK_INT}, but in a place where it can be conveniently 118 * overridden for local testing. 119 */ 120 public static final int SDK_INT = Build.VERSION.SDK_INT; 121 122 /** 123 * Like {@link Build#DEVICE}, but in a place where it can be conveniently overridden for local 124 * testing. 125 */ 126 public static final String DEVICE = Build.DEVICE; 127 128 /** 129 * Like {@link Build#MANUFACTURER}, but in a place where it can be conveniently overridden for 130 * local testing. 131 */ 132 public static final String MANUFACTURER = Build.MANUFACTURER; 133 134 /** 135 * Like {@link Build#MODEL}, but in a place where it can be conveniently overridden for local 136 * testing. 137 */ 138 public static final String MODEL = Build.MODEL; 139 140 /** A concise description of the device that it can be useful to log for debugging purposes. */ 141 public static final String DEVICE_DEBUG_INFO = 142 DEVICE + ", " + MODEL + ", " + MANUFACTURER + ", " + SDK_INT; 143 144 /** An empty byte array. */ 145 public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 146 147 private static final String TAG = "Util"; 148 private static final Pattern XS_DATE_TIME_PATTERN = 149 Pattern.compile( 150 "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" 151 + "(\\d\\d):(\\d\\d):(\\d\\d)([\\.,](\\d+))?" 152 + "([Zz]|((\\+|\\-)(\\d?\\d):?(\\d\\d)))?"); 153 private static final Pattern XS_DURATION_PATTERN = 154 Pattern.compile( 155 "^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?" 156 + "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"); 157 private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})"); 158 159 // https://docs.microsoft.com/en-us/azure/media-services/previous/media-services-deliver-content-overview#URLs. 160 private static final Pattern ISM_URL_PATTERN = Pattern.compile(".*\\.isml?(?:/(manifest(.*))?)?"); 161 private static final String ISM_HLS_FORMAT_EXTENSION = "format=m3u8-aapl"; 162 private static final String ISM_DASH_FORMAT_EXTENSION = "format=mpd-time-csf"; 163 164 // Replacement map of ISO language codes used for normalization. 165 @Nullable private static HashMap<String, String> languageTagReplacementMap; 166 Util()167 private Util() {} 168 169 /** 170 * Converts the entirety of an {@link InputStream} to a byte array. 171 * 172 * @param inputStream the {@link InputStream} to be read. The input stream is not closed by this 173 * method. 174 * @return a byte array containing all of the inputStream's bytes. 175 * @throws IOException if an error occurs reading from the stream. 176 */ toByteArray(InputStream inputStream)177 public static byte[] toByteArray(InputStream inputStream) throws IOException { 178 byte[] buffer = new byte[1024 * 4]; 179 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 180 int bytesRead; 181 while ((bytesRead = inputStream.read(buffer)) != -1) { 182 outputStream.write(buffer, 0, bytesRead); 183 } 184 return outputStream.toByteArray(); 185 } 186 187 /** 188 * Calls {@link Context#startForegroundService(Intent)} if {@link #SDK_INT} is 26 or higher, or 189 * {@link Context#startService(Intent)} otherwise. 190 * 191 * @param context The context to call. 192 * @param intent The intent to pass to the called method. 193 * @return The result of the called method. 194 */ 195 @Nullable startForegroundService(Context context, Intent intent)196 public static ComponentName startForegroundService(Context context, Intent intent) { 197 if (Util.SDK_INT >= 26) { 198 return context.startForegroundService(intent); 199 } else { 200 return context.startService(intent); 201 } 202 } 203 204 /** 205 * Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE} 206 * permission read the specified {@link Uri}s, requesting the permission if necessary. 207 * 208 * @param activity The host activity for checking and requesting the permission. 209 * @param uris {@link Uri}s that may require {@link permission#READ_EXTERNAL_STORAGE} to read. 210 * @return Whether a permission request was made. 211 */ maybeRequestReadExternalStoragePermission(Activity activity, Uri... uris)212 public static boolean maybeRequestReadExternalStoragePermission(Activity activity, Uri... uris) { 213 if (Util.SDK_INT < 23) { 214 return false; 215 } 216 for (Uri uri : uris) { 217 if (isLocalFileUri(uri)) { 218 return requestExternalStoragePermission(activity); 219 } 220 } 221 return false; 222 } 223 224 /** 225 * Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE} 226 * permission for the specified {@link MediaItem media items}, requesting the permission if 227 * necessary. 228 * 229 * @param activity The host activity for checking and requesting the permission. 230 * @param mediaItems {@link MediaItem Media items}s that may require {@link 231 * permission#READ_EXTERNAL_STORAGE} to read. 232 * @return Whether a permission request was made. 233 */ maybeRequestReadExternalStoragePermission( Activity activity, MediaItem... mediaItems)234 public static boolean maybeRequestReadExternalStoragePermission( 235 Activity activity, MediaItem... mediaItems) { 236 if (Util.SDK_INT < 23) { 237 return false; 238 } 239 for (MediaItem mediaItem : mediaItems) { 240 if (mediaItem.localConfiguration == null) { 241 continue; 242 } 243 if (isLocalFileUri(mediaItem.localConfiguration.uri)) { 244 return requestExternalStoragePermission(activity); 245 } 246 for (int i = 0; i < mediaItem.localConfiguration.subtitleConfigurations.size(); i++) { 247 if (isLocalFileUri(mediaItem.localConfiguration.subtitleConfigurations.get(i).uri)) { 248 return requestExternalStoragePermission(activity); 249 } 250 } 251 } 252 return false; 253 } 254 255 /** 256 * Returns whether it may be possible to load the URIs of the given media items based on the 257 * network security policy's cleartext traffic permissions. 258 * 259 * @param mediaItems A list of {@link MediaItem media items}. 260 * @return Whether it may be possible to load the URIs of the given media items. 261 */ checkCleartextTrafficPermitted(MediaItem... mediaItems)262 public static boolean checkCleartextTrafficPermitted(MediaItem... mediaItems) { 263 if (Util.SDK_INT < 24) { 264 // We assume cleartext traffic is permitted. 265 return true; 266 } 267 for (MediaItem mediaItem : mediaItems) { 268 if (mediaItem.localConfiguration == null) { 269 continue; 270 } 271 if (isTrafficRestricted(mediaItem.localConfiguration.uri)) { 272 return false; 273 } 274 for (int i = 0; i < mediaItem.localConfiguration.subtitleConfigurations.size(); i++) { 275 if (isTrafficRestricted(mediaItem.localConfiguration.subtitleConfigurations.get(i).uri)) { 276 return false; 277 } 278 } 279 } 280 return true; 281 } 282 283 /** 284 * Returns true if the URI is a path to a local file or a reference to a local file. 285 * 286 * @param uri The uri to test. 287 */ isLocalFileUri(Uri uri)288 public static boolean isLocalFileUri(Uri uri) { 289 String scheme = uri.getScheme(); 290 return TextUtils.isEmpty(scheme) || "file".equals(scheme); 291 } 292 293 /** 294 * Tests two objects for {@link Object#equals(Object)} equality, handling the case where one or 295 * both may be null. 296 * 297 * @param o1 The first object. 298 * @param o2 The second object. 299 * @return {@code o1 == null ? o2 == null : o1.equals(o2)}. 300 */ areEqual(@ullable Object o1, @Nullable Object o2)301 public static boolean areEqual(@Nullable Object o1, @Nullable Object o2) { 302 return o1 == null ? o2 == null : o1.equals(o2); 303 } 304 305 /** 306 * Tests whether an {@code items} array contains an object equal to {@code item}, according to 307 * {@link Object#equals(Object)}. 308 * 309 * <p>If {@code item} is null then true is returned if and only if {@code items} contains null. 310 * 311 * @param items The array of items to search. 312 * @param item The item to search for. 313 * @return True if the array contains an object equal to the item being searched for. 314 */ contains(@ullableType Object[] items, @Nullable Object item)315 public static boolean contains(@NullableType Object[] items, @Nullable Object item) { 316 for (Object arrayItem : items) { 317 if (areEqual(arrayItem, item)) { 318 return true; 319 } 320 } 321 return false; 322 } 323 324 /** 325 * Removes an indexed range from a List. 326 * 327 * <p>Does nothing if the provided range is valid and {@code fromIndex == toIndex}. 328 * 329 * @param list The List to remove the range from. 330 * @param fromIndex The first index to be removed (inclusive). 331 * @param toIndex The last index to be removed (exclusive). 332 * @throws IllegalArgumentException If {@code fromIndex} < 0, {@code toIndex} > {@code 333 * list.size()}, or {@code fromIndex} > {@code toIndex}. 334 */ removeRange(List<T> list, int fromIndex, int toIndex)335 public static <T> void removeRange(List<T> list, int fromIndex, int toIndex) { 336 if (fromIndex < 0 || toIndex > list.size() || fromIndex > toIndex) { 337 throw new IllegalArgumentException(); 338 } else if (fromIndex != toIndex) { 339 // Checking index inequality prevents an unnecessary allocation. 340 list.subList(fromIndex, toIndex).clear(); 341 } 342 } 343 344 /** 345 * Casts a nullable variable to a non-null variable without runtime null check. 346 * 347 * <p>Use {@link Assertions#checkNotNull(Object)} to throw if the value is null. 348 */ 349 @SuppressWarnings({"nullness:contracts.postcondition", "nullness:return"}) 350 @EnsuresNonNull("#1") castNonNull(@ullable T value)351 public static <T> T castNonNull(@Nullable T value) { 352 return value; 353 } 354 355 /** Casts a nullable type array to a non-null type array without runtime null check. */ 356 @SuppressWarnings({"nullness:contracts.postcondition", "nullness:return"}) 357 @EnsuresNonNull("#1") castNonNullTypeArray(@ullableType T[] value)358 public static <T> T[] castNonNullTypeArray(@NullableType T[] value) { 359 return value; 360 } 361 362 /** 363 * Copies and optionally truncates an array. Prevents null array elements created by {@link 364 * Arrays#copyOf(Object[], int)} by ensuring the new length does not exceed the current length. 365 * 366 * @param input The input array. 367 * @param length The output array length. Must be less or equal to the length of the input array. 368 * @return The copied array. 369 */ 370 @SuppressWarnings({"nullness:argument", "nullness:return"}) nullSafeArrayCopy(T[] input, int length)371 public static <T> T[] nullSafeArrayCopy(T[] input, int length) { 372 Assertions.checkArgument(length <= input.length); 373 return Arrays.copyOf(input, length); 374 } 375 376 /** 377 * Copies a subset of an array. 378 * 379 * @param input The input array. 380 * @param from The start the range to be copied, inclusive 381 * @param to The end of the range to be copied, exclusive. 382 * @return The copied array. 383 */ 384 @SuppressWarnings({"nullness:argument", "nullness:return"}) nullSafeArrayCopyOfRange(T[] input, int from, int to)385 public static <T> T[] nullSafeArrayCopyOfRange(T[] input, int from, int to) { 386 Assertions.checkArgument(0 <= from); 387 Assertions.checkArgument(to <= input.length); 388 return Arrays.copyOfRange(input, from, to); 389 } 390 391 /** 392 * Creates a new array containing {@code original} with {@code newElement} appended. 393 * 394 * @param original The input array. 395 * @param newElement The element to append. 396 * @return The new array. 397 */ nullSafeArrayAppend(T[] original, T newElement)398 public static <T> T[] nullSafeArrayAppend(T[] original, T newElement) { 399 @NullableType T[] result = Arrays.copyOf(original, original.length + 1); 400 result[original.length] = newElement; 401 return castNonNullTypeArray(result); 402 } 403 404 /** 405 * Creates a new array containing the concatenation of two non-null type arrays. 406 * 407 * @param first The first array. 408 * @param second The second array. 409 * @return The concatenated result. 410 */ 411 @SuppressWarnings("nullness:assignment") nullSafeArrayConcatenation(T[] first, T[] second)412 public static <T> T[] nullSafeArrayConcatenation(T[] first, T[] second) { 413 T[] concatenation = Arrays.copyOf(first, first.length + second.length); 414 System.arraycopy( 415 /* src= */ second, 416 /* srcPos= */ 0, 417 /* dest= */ concatenation, 418 /* destPos= */ first.length, 419 /* length= */ second.length); 420 return concatenation; 421 } 422 423 /** 424 * Copies the contents of {@code list} into {@code array}. 425 * 426 * <p>{@code list.size()} must be the same as {@code array.length} to ensure the contents can be 427 * copied into {@code array} without leaving any nulls at the end. 428 * 429 * @param list The list to copy items from. 430 * @param array The array to copy items to. 431 */ 432 @SuppressWarnings("nullness:toArray.nullable.elements.not.newarray") nullSafeListToArray(List<T> list, T[] array)433 public static <T> void nullSafeListToArray(List<T> list, T[] array) { 434 Assertions.checkState(list.size() == array.length); 435 list.toArray(array); 436 } 437 438 /** 439 * Creates a {@link Handler} on the current {@link Looper} thread. 440 * 441 * @throws IllegalStateException If the current thread doesn't have a {@link Looper}. 442 */ createHandlerForCurrentLooper()443 public static Handler createHandlerForCurrentLooper() { 444 return createHandlerForCurrentLooper(/* callback= */ null); 445 } 446 447 /** 448 * Creates a {@link Handler} with the specified {@link Handler.Callback} on the current {@link 449 * Looper} thread. 450 * 451 * <p>The method accepts partially initialized objects as callback under the assumption that the 452 * Handler won't be used to send messages until the callback is fully initialized. 453 * 454 * @param callback A {@link Handler.Callback}. May be a partially initialized class, or null if no 455 * callback is required. 456 * @return A {@link Handler} with the specified callback on the current {@link Looper} thread. 457 * @throws IllegalStateException If the current thread doesn't have a {@link Looper}. 458 */ createHandlerForCurrentLooper( @ullable Handler.@nknownInitialization Callback callback)459 public static Handler createHandlerForCurrentLooper( 460 @Nullable Handler.@UnknownInitialization Callback callback) { 461 return createHandler(Assertions.checkStateNotNull(Looper.myLooper()), callback); 462 } 463 464 /** 465 * Creates a {@link Handler} on the current {@link Looper} thread. 466 * 467 * <p>If the current thread doesn't have a {@link Looper}, the application's main thread {@link 468 * Looper} is used. 469 */ createHandlerForCurrentOrMainLooper()470 public static Handler createHandlerForCurrentOrMainLooper() { 471 return createHandlerForCurrentOrMainLooper(/* callback= */ null); 472 } 473 474 /** 475 * Creates a {@link Handler} with the specified {@link Handler.Callback} on the current {@link 476 * Looper} thread. 477 * 478 * <p>The method accepts partially initialized objects as callback under the assumption that the 479 * Handler won't be used to send messages until the callback is fully initialized. 480 * 481 * <p>If the current thread doesn't have a {@link Looper}, the application's main thread {@link 482 * Looper} is used. 483 * 484 * @param callback A {@link Handler.Callback}. May be a partially initialized class, or null if no 485 * callback is required. 486 * @return A {@link Handler} with the specified callback on the current {@link Looper} thread. 487 */ createHandlerForCurrentOrMainLooper( @ullable Handler.@nknownInitialization Callback callback)488 public static Handler createHandlerForCurrentOrMainLooper( 489 @Nullable Handler.@UnknownInitialization Callback callback) { 490 return createHandler(getCurrentOrMainLooper(), callback); 491 } 492 493 /** 494 * Creates a {@link Handler} with the specified {@link Handler.Callback} on the specified {@link 495 * Looper} thread. 496 * 497 * <p>The method accepts partially initialized objects as callback under the assumption that the 498 * Handler won't be used to send messages until the callback is fully initialized. 499 * 500 * @param looper A {@link Looper} to run the callback on. 501 * @param callback A {@link Handler.Callback}. May be a partially initialized class, or null if no 502 * callback is required. 503 * @return A {@link Handler} with the specified callback on the current {@link Looper} thread. 504 */ 505 @SuppressWarnings({"nullness:argument", "nullness:return"}) createHandler( Looper looper, @Nullable Handler.@UnknownInitialization Callback callback)506 public static Handler createHandler( 507 Looper looper, @Nullable Handler.@UnknownInitialization Callback callback) { 508 return new Handler(looper, callback); 509 } 510 511 /** 512 * Posts the {@link Runnable} if the calling thread differs with the {@link Looper} of the {@link 513 * Handler}. Otherwise, runs the {@link Runnable} directly. 514 * 515 * @param handler The handler to which the {@link Runnable} will be posted. 516 * @param runnable The runnable to either post or run. 517 * @return {@code true} if the {@link Runnable} was successfully posted to the {@link Handler} or 518 * run. {@code false} otherwise. 519 */ postOrRun(Handler handler, Runnable runnable)520 public static boolean postOrRun(Handler handler, Runnable runnable) { 521 Looper looper = handler.getLooper(); 522 if (!looper.getThread().isAlive()) { 523 return false; 524 } 525 if (handler.getLooper() == Looper.myLooper()) { 526 runnable.run(); 527 return true; 528 } else { 529 return handler.post(runnable); 530 } 531 } 532 533 /** 534 * Returns the {@link Looper} associated with the current thread, or the {@link Looper} of the 535 * application's main thread if the current thread doesn't have a {@link Looper}. 536 */ getCurrentOrMainLooper()537 public static Looper getCurrentOrMainLooper() { 538 @Nullable Looper myLooper = Looper.myLooper(); 539 return myLooper != null ? myLooper : Looper.getMainLooper(); 540 } 541 542 /** 543 * Instantiates a new single threaded executor whose thread has the specified name. 544 * 545 * @param threadName The name of the thread. 546 * @return The executor. 547 */ newSingleThreadExecutor(String threadName)548 public static ExecutorService newSingleThreadExecutor(String threadName) { 549 return Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, threadName)); 550 } 551 552 /** 553 * Closes a {@link Closeable}, suppressing any {@link IOException} that may occur. Both {@link 554 * java.io.OutputStream} and {@link InputStream} are {@code Closeable}. 555 * 556 * @param closeable The {@link Closeable} to close. 557 */ closeQuietly(@ullable Closeable closeable)558 public static void closeQuietly(@Nullable Closeable closeable) { 559 try { 560 if (closeable != null) { 561 closeable.close(); 562 } 563 } catch (IOException e) { 564 // Ignore. 565 } 566 } 567 568 /** 569 * Reads an integer from a {@link Parcel} and interprets it as a boolean, with 0 mapping to false 570 * and all other values mapping to true. 571 * 572 * @param parcel The {@link Parcel} to read from. 573 * @return The read value. 574 */ readBoolean(Parcel parcel)575 public static boolean readBoolean(Parcel parcel) { 576 return parcel.readInt() != 0; 577 } 578 579 /** 580 * Writes a boolean to a {@link Parcel}. The boolean is written as an integer with value 1 (true) 581 * or 0 (false). 582 * 583 * @param parcel The {@link Parcel} to write to. 584 * @param value The value to write. 585 */ writeBoolean(Parcel parcel, boolean value)586 public static void writeBoolean(Parcel parcel, boolean value) { 587 parcel.writeInt(value ? 1 : 0); 588 } 589 590 /** 591 * Returns the language tag for a {@link Locale}. 592 * 593 * <p>For API levels ≥ 21, this tag is IETF BCP 47 compliant. Use {@link 594 * #normalizeLanguageCode(String)} to retrieve a normalized IETF BCP 47 language tag for all API 595 * levels if needed. 596 * 597 * @param locale A {@link Locale}. 598 * @return The language tag. 599 */ getLocaleLanguageTag(Locale locale)600 public static String getLocaleLanguageTag(Locale locale) { 601 return SDK_INT >= 21 ? getLocaleLanguageTagV21(locale) : locale.toString(); 602 } 603 604 /** 605 * Returns a normalized IETF BCP 47 language tag for {@code language}. 606 * 607 * @param language A case-insensitive language code supported by {@link 608 * Locale#forLanguageTag(String)}. 609 * @return The all-lowercase normalized code, or null if the input was null, or {@code 610 * language.toLowerCase()} if the language could not be normalized. 611 */ normalizeLanguageCode(@olyNull String language)612 public static @PolyNull String normalizeLanguageCode(@PolyNull String language) { 613 if (language == null) { 614 return null; 615 } 616 // Locale data (especially for API < 21) may produce tags with '_' instead of the 617 // standard-conformant '-'. 618 String normalizedTag = language.replace('_', '-'); 619 if (normalizedTag.isEmpty() || normalizedTag.equals(C.LANGUAGE_UNDETERMINED)) { 620 // Tag isn't valid, keep using the original. 621 normalizedTag = language; 622 } 623 normalizedTag = Ascii.toLowerCase(normalizedTag); 624 String mainLanguage = Util.splitAtFirst(normalizedTag, "-")[0]; 625 if (languageTagReplacementMap == null) { 626 languageTagReplacementMap = createIsoLanguageReplacementMap(); 627 } 628 @Nullable String replacedLanguage = languageTagReplacementMap.get(mainLanguage); 629 if (replacedLanguage != null) { 630 normalizedTag = 631 replacedLanguage + normalizedTag.substring(/* beginIndex= */ mainLanguage.length()); 632 mainLanguage = replacedLanguage; 633 } 634 if ("no".equals(mainLanguage) || "i".equals(mainLanguage) || "zh".equals(mainLanguage)) { 635 normalizedTag = maybeReplaceLegacyLanguageTags(normalizedTag); 636 } 637 return normalizedTag; 638 } 639 640 /** 641 * Returns a new {@link String} constructed by decoding UTF-8 encoded bytes. 642 * 643 * @param bytes The UTF-8 encoded bytes to decode. 644 * @return The string. 645 */ fromUtf8Bytes(byte[] bytes)646 public static String fromUtf8Bytes(byte[] bytes) { 647 return new String(bytes, Charsets.UTF_8); 648 } 649 650 /** 651 * Returns a new {@link String} constructed by decoding UTF-8 encoded bytes in a subarray. 652 * 653 * @param bytes The UTF-8 encoded bytes to decode. 654 * @param offset The index of the first byte to decode. 655 * @param length The number of bytes to decode. 656 * @return The string. 657 */ fromUtf8Bytes(byte[] bytes, int offset, int length)658 public static String fromUtf8Bytes(byte[] bytes, int offset, int length) { 659 return new String(bytes, offset, length, Charsets.UTF_8); 660 } 661 662 /** 663 * Returns a new byte array containing the code points of a {@link String} encoded using UTF-8. 664 * 665 * @param value The {@link String} whose bytes should be obtained. 666 * @return The code points encoding using UTF-8. 667 */ getUtf8Bytes(String value)668 public static byte[] getUtf8Bytes(String value) { 669 return value.getBytes(Charsets.UTF_8); 670 } 671 672 /** 673 * Splits a string using {@code value.split(regex, -1}). Note: this is is similar to {@link 674 * String#split(String)} but empty matches at the end of the string will not be omitted from the 675 * returned array. 676 * 677 * @param value The string to split. 678 * @param regex A delimiting regular expression. 679 * @return The array of strings resulting from splitting the string. 680 */ split(String value, String regex)681 public static String[] split(String value, String regex) { 682 return value.split(regex, /* limit= */ -1); 683 } 684 685 /** 686 * Splits the string at the first occurrence of the delimiter {@code regex}. If the delimiter does 687 * not match, returns an array with one element which is the input string. If the delimiter does 688 * match, returns an array with the portion of the string before the delimiter and the rest of the 689 * string. 690 * 691 * @param value The string. 692 * @param regex A delimiting regular expression. 693 * @return The string split by the first occurrence of the delimiter. 694 */ splitAtFirst(String value, String regex)695 public static String[] splitAtFirst(String value, String regex) { 696 return value.split(regex, /* limit= */ 2); 697 } 698 699 /** 700 * Returns whether the given character is a carriage return ('\r') or a line feed ('\n'). 701 * 702 * @param c The character. 703 * @return Whether the given character is a linebreak. 704 */ isLinebreak(int c)705 public static boolean isLinebreak(int c) { 706 return c == '\n' || c == '\r'; 707 } 708 709 /** 710 * Formats a string using {@link Locale#US}. 711 * 712 * @see String#format(String, Object...) 713 */ formatInvariant(String format, Object... args)714 public static String formatInvariant(String format, Object... args) { 715 return String.format(Locale.US, format, args); 716 } 717 718 /** 719 * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. 720 * 721 * @param numerator The numerator to divide. 722 * @param denominator The denominator to divide by. 723 * @return The ceiled result of the division. 724 */ ceilDivide(int numerator, int denominator)725 public static int ceilDivide(int numerator, int denominator) { 726 return (numerator + denominator - 1) / denominator; 727 } 728 729 /** 730 * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. 731 * 732 * @param numerator The numerator to divide. 733 * @param denominator The denominator to divide by. 734 * @return The ceiled result of the division. 735 */ ceilDivide(long numerator, long denominator)736 public static long ceilDivide(long numerator, long denominator) { 737 return (numerator + denominator - 1) / denominator; 738 } 739 740 /** 741 * Constrains a value to the specified bounds. 742 * 743 * @param value The value to constrain. 744 * @param min The lower bound. 745 * @param max The upper bound. 746 * @return The constrained value {@code Math.max(min, Math.min(value, max))}. 747 */ constrainValue(int value, int min, int max)748 public static int constrainValue(int value, int min, int max) { 749 return max(min, min(value, max)); 750 } 751 752 /** 753 * Constrains a value to the specified bounds. 754 * 755 * @param value The value to constrain. 756 * @param min The lower bound. 757 * @param max The upper bound. 758 * @return The constrained value {@code Math.max(min, Math.min(value, max))}. 759 */ constrainValue(long value, long min, long max)760 public static long constrainValue(long value, long min, long max) { 761 return max(min, min(value, max)); 762 } 763 764 /** 765 * Constrains a value to the specified bounds. 766 * 767 * @param value The value to constrain. 768 * @param min The lower bound. 769 * @param max The upper bound. 770 * @return The constrained value {@code Math.max(min, Math.min(value, max))}. 771 */ constrainValue(float value, float min, float max)772 public static float constrainValue(float value, float min, float max) { 773 return max(min, min(value, max)); 774 } 775 776 /** 777 * Returns the sum of two arguments, or a third argument if the result overflows. 778 * 779 * @param x The first value. 780 * @param y The second value. 781 * @param overflowResult The return value if {@code x + y} overflows. 782 * @return {@code x + y}, or {@code overflowResult} if the result overflows. 783 */ addWithOverflowDefault(long x, long y, long overflowResult)784 public static long addWithOverflowDefault(long x, long y, long overflowResult) { 785 long result = x + y; 786 // See Hacker's Delight 2-13 (H. Warren Jr). 787 if (((x ^ result) & (y ^ result)) < 0) { 788 return overflowResult; 789 } 790 return result; 791 } 792 793 /** 794 * Returns the difference between two arguments, or a third argument if the result overflows. 795 * 796 * @param x The first value. 797 * @param y The second value. 798 * @param overflowResult The return value if {@code x - y} overflows. 799 * @return {@code x - y}, or {@code overflowResult} if the result overflows. 800 */ subtractWithOverflowDefault(long x, long y, long overflowResult)801 public static long subtractWithOverflowDefault(long x, long y, long overflowResult) { 802 long result = x - y; 803 // See Hacker's Delight 2-13 (H. Warren Jr). 804 if (((x ^ y) & (x ^ result)) < 0) { 805 return overflowResult; 806 } 807 return result; 808 } 809 810 /** 811 * Returns the index of the first occurrence of {@code value} in {@code array}, or {@link 812 * C#INDEX_UNSET} if {@code value} is not contained in {@code array}. 813 * 814 * @param array The array to search. 815 * @param value The value to search for. 816 * @return The index of the first occurrence of value in {@code array}, or {@link C#INDEX_UNSET} 817 * if {@code value} is not contained in {@code array}. 818 */ linearSearch(int[] array, int value)819 public static int linearSearch(int[] array, int value) { 820 for (int i = 0; i < array.length; i++) { 821 if (array[i] == value) { 822 return i; 823 } 824 } 825 return C.INDEX_UNSET; 826 } 827 828 /** 829 * Returns the index of the first occurrence of {@code value} in {@code array}, or {@link 830 * C#INDEX_UNSET} if {@code value} is not contained in {@code array}. 831 * 832 * @param array The array to search. 833 * @param value The value to search for. 834 * @return The index of the first occurrence of value in {@code array}, or {@link C#INDEX_UNSET} 835 * if {@code value} is not contained in {@code array}. 836 */ linearSearch(long[] array, long value)837 public static int linearSearch(long[] array, long value) { 838 for (int i = 0; i < array.length; i++) { 839 if (array[i] == value) { 840 return i; 841 } 842 } 843 return C.INDEX_UNSET; 844 } 845 846 /** 847 * Returns the index of the largest element in {@code array} that is less than (or optionally 848 * equal to) a specified {@code value}. 849 * 850 * <p>The search is performed using a binary search algorithm, so the array must be sorted. If the 851 * array contains multiple elements equal to {@code value} and {@code inclusive} is true, the 852 * index of the first one will be returned. 853 * 854 * @param array The array to search. 855 * @param value The value being searched for. 856 * @param inclusive If the value is present in the array, whether to return the corresponding 857 * index. If false then the returned index corresponds to the largest element strictly less 858 * than the value. 859 * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than 860 * the smallest element in the array. If false then -1 will be returned. 861 * @return The index of the largest element in {@code array} that is less than (or optionally 862 * equal to) {@code value}. 863 */ binarySearchFloor( int[] array, int value, boolean inclusive, boolean stayInBounds)864 public static int binarySearchFloor( 865 int[] array, int value, boolean inclusive, boolean stayInBounds) { 866 int index = Arrays.binarySearch(array, value); 867 if (index < 0) { 868 index = -(index + 2); 869 } else { 870 while (--index >= 0 && array[index] == value) {} 871 if (inclusive) { 872 index++; 873 } 874 } 875 return stayInBounds ? max(0, index) : index; 876 } 877 878 /** 879 * Returns the index of the largest element in {@code array} that is less than (or optionally 880 * equal to) a specified {@code value}. 881 * 882 * <p>The search is performed using a binary search algorithm, so the array must be sorted. If the 883 * array contains multiple elements equal to {@code value} and {@code inclusive} is true, the 884 * index of the first one will be returned. 885 * 886 * @param array The array to search. 887 * @param value The value being searched for. 888 * @param inclusive If the value is present in the array, whether to return the corresponding 889 * index. If false then the returned index corresponds to the largest element strictly less 890 * than the value. 891 * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than 892 * the smallest element in the array. If false then -1 will be returned. 893 * @return The index of the largest element in {@code array} that is less than (or optionally 894 * equal to) {@code value}. 895 */ binarySearchFloor( long[] array, long value, boolean inclusive, boolean stayInBounds)896 public static int binarySearchFloor( 897 long[] array, long value, boolean inclusive, boolean stayInBounds) { 898 int index = Arrays.binarySearch(array, value); 899 if (index < 0) { 900 index = -(index + 2); 901 } else { 902 while (--index >= 0 && array[index] == value) {} 903 if (inclusive) { 904 index++; 905 } 906 } 907 return stayInBounds ? max(0, index) : index; 908 } 909 910 /** 911 * Returns the index of the largest element in {@code list} that is less than (or optionally equal 912 * to) a specified {@code value}. 913 * 914 * <p>The search is performed using a binary search algorithm, so the list must be sorted. If the 915 * list contains multiple elements equal to {@code value} and {@code inclusive} is true, the index 916 * of the first one will be returned. 917 * 918 * @param <T> The type of values being searched. 919 * @param list The list to search. 920 * @param value The value being searched for. 921 * @param inclusive If the value is present in the list, whether to return the corresponding 922 * index. If false then the returned index corresponds to the largest element strictly less 923 * than the value. 924 * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than 925 * the smallest element in the list. If false then -1 will be returned. 926 * @return The index of the largest element in {@code list} that is less than (or optionally equal 927 * to) {@code value}. 928 */ binarySearchFloor( List<? extends Comparable<? super T>> list, T value, boolean inclusive, boolean stayInBounds)929 public static <T extends Comparable<? super T>> int binarySearchFloor( 930 List<? extends Comparable<? super T>> list, 931 T value, 932 boolean inclusive, 933 boolean stayInBounds) { 934 int index = Collections.binarySearch(list, value); 935 if (index < 0) { 936 index = -(index + 2); 937 } else { 938 while (--index >= 0 && list.get(index).compareTo(value) == 0) {} 939 if (inclusive) { 940 index++; 941 } 942 } 943 return stayInBounds ? max(0, index) : index; 944 } 945 946 /** 947 * Returns the index of the largest element in {@code longArray} that is less than (or optionally 948 * equal to) a specified {@code value}. 949 * 950 * <p>The search is performed using a binary search algorithm, so the array must be sorted. If the 951 * array contains multiple elements equal to {@code value} and {@code inclusive} is true, the 952 * index of the first one will be returned. 953 * 954 * @param longArray The array to search. 955 * @param value The value being searched for. 956 * @param inclusive If the value is present in the array, whether to return the corresponding 957 * index. If false then the returned index corresponds to the largest element strictly less 958 * than the value. 959 * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than 960 * the smallest element in the array. If false then -1 will be returned. 961 * @return The index of the largest element in {@code array} that is less than (or optionally 962 * equal to) {@code value}. 963 */ binarySearchFloor( LongArray longArray, long value, boolean inclusive, boolean stayInBounds)964 public static int binarySearchFloor( 965 LongArray longArray, long value, boolean inclusive, boolean stayInBounds) { 966 int lowIndex = 0; 967 int highIndex = longArray.size() - 1; 968 969 while (lowIndex <= highIndex) { 970 int midIndex = (lowIndex + highIndex) >>> 1; 971 if (longArray.get(midIndex) < value) { 972 lowIndex = midIndex + 1; 973 } else { 974 highIndex = midIndex - 1; 975 } 976 } 977 978 if (inclusive && highIndex + 1 < longArray.size() && longArray.get(highIndex + 1) == value) { 979 highIndex++; 980 } else if (stayInBounds && highIndex == -1) { 981 highIndex = 0; 982 } 983 984 return highIndex; 985 } 986 987 /** 988 * Returns the index of the smallest element in {@code array} that is greater than (or optionally 989 * equal to) a specified {@code value}. 990 * 991 * <p>The search is performed using a binary search algorithm, so the array must be sorted. If the 992 * array contains multiple elements equal to {@code value} and {@code inclusive} is true, the 993 * index of the last one will be returned. 994 * 995 * @param array The array to search. 996 * @param value The value being searched for. 997 * @param inclusive If the value is present in the array, whether to return the corresponding 998 * index. If false then the returned index corresponds to the smallest element strictly 999 * greater than the value. 1000 * @param stayInBounds If true, then {@code (a.length - 1)} will be returned in the case that the 1001 * value is greater than the largest element in the array. If false then {@code a.length} will 1002 * be returned. 1003 * @return The index of the smallest element in {@code array} that is greater than (or optionally 1004 * equal to) {@code value}. 1005 */ binarySearchCeil( int[] array, int value, boolean inclusive, boolean stayInBounds)1006 public static int binarySearchCeil( 1007 int[] array, int value, boolean inclusive, boolean stayInBounds) { 1008 int index = Arrays.binarySearch(array, value); 1009 if (index < 0) { 1010 index = ~index; 1011 } else { 1012 while (++index < array.length && array[index] == value) {} 1013 if (inclusive) { 1014 index--; 1015 } 1016 } 1017 return stayInBounds ? min(array.length - 1, index) : index; 1018 } 1019 1020 /** 1021 * Returns the index of the smallest element in {@code array} that is greater than (or optionally 1022 * equal to) a specified {@code value}. 1023 * 1024 * <p>The search is performed using a binary search algorithm, so the array must be sorted. If the 1025 * array contains multiple elements equal to {@code value} and {@code inclusive} is true, the 1026 * index of the last one will be returned. 1027 * 1028 * @param array The array to search. 1029 * @param value The value being searched for. 1030 * @param inclusive If the value is present in the array, whether to return the corresponding 1031 * index. If false then the returned index corresponds to the smallest element strictly 1032 * greater than the value. 1033 * @param stayInBounds If true, then {@code (a.length - 1)} will be returned in the case that the 1034 * value is greater than the largest element in the array. If false then {@code a.length} will 1035 * be returned. 1036 * @return The index of the smallest element in {@code array} that is greater than (or optionally 1037 * equal to) {@code value}. 1038 */ binarySearchCeil( long[] array, long value, boolean inclusive, boolean stayInBounds)1039 public static int binarySearchCeil( 1040 long[] array, long value, boolean inclusive, boolean stayInBounds) { 1041 int index = Arrays.binarySearch(array, value); 1042 if (index < 0) { 1043 index = ~index; 1044 } else { 1045 while (++index < array.length && array[index] == value) {} 1046 if (inclusive) { 1047 index--; 1048 } 1049 } 1050 return stayInBounds ? min(array.length - 1, index) : index; 1051 } 1052 1053 /** 1054 * Returns the index of the smallest element in {@code list} that is greater than (or optionally 1055 * equal to) a specified value. 1056 * 1057 * <p>The search is performed using a binary search algorithm, so the list must be sorted. If the 1058 * list contains multiple elements equal to {@code value} and {@code inclusive} is true, the index 1059 * of the last one will be returned. 1060 * 1061 * @param <T> The type of values being searched. 1062 * @param list The list to search. 1063 * @param value The value being searched for. 1064 * @param inclusive If the value is present in the list, whether to return the corresponding 1065 * index. If false then the returned index corresponds to the smallest element strictly 1066 * greater than the value. 1067 * @param stayInBounds If true, then {@code (list.size() - 1)} will be returned in the case that 1068 * the value is greater than the largest element in the list. If false then {@code 1069 * list.size()} will be returned. 1070 * @return The index of the smallest element in {@code list} that is greater than (or optionally 1071 * equal to) {@code value}. 1072 */ binarySearchCeil( List<? extends Comparable<? super T>> list, T value, boolean inclusive, boolean stayInBounds)1073 public static <T extends Comparable<? super T>> int binarySearchCeil( 1074 List<? extends Comparable<? super T>> list, 1075 T value, 1076 boolean inclusive, 1077 boolean stayInBounds) { 1078 int index = Collections.binarySearch(list, value); 1079 if (index < 0) { 1080 index = ~index; 1081 } else { 1082 int listSize = list.size(); 1083 while (++index < listSize && list.get(index).compareTo(value) == 0) {} 1084 if (inclusive) { 1085 index--; 1086 } 1087 } 1088 return stayInBounds ? min(list.size() - 1, index) : index; 1089 } 1090 1091 /** 1092 * Compares two long values and returns the same value as {@code Long.compare(long, long)}. 1093 * 1094 * @param left The left operand. 1095 * @param right The right operand. 1096 * @return 0, if left == right, a negative value if left < right, or a positive value if left 1097 * > right. 1098 */ compareLong(long left, long right)1099 public static int compareLong(long left, long right) { 1100 return left < right ? -1 : left == right ? 0 : 1; 1101 } 1102 1103 /** 1104 * Returns the minimum value in the given {@link SparseLongArray}. 1105 * 1106 * @param sparseLongArray The {@link SparseLongArray}. 1107 * @return The minimum value. 1108 * @throws NoSuchElementException If the array is empty. 1109 */ 1110 @RequiresApi(18) minValue(SparseLongArray sparseLongArray)1111 public static long minValue(SparseLongArray sparseLongArray) { 1112 if (sparseLongArray.size() == 0) { 1113 throw new NoSuchElementException(); 1114 } 1115 long min = Long.MAX_VALUE; 1116 for (int i = 0; i < sparseLongArray.size(); i++) { 1117 min = min(min, sparseLongArray.valueAt(i)); 1118 } 1119 return min; 1120 } 1121 1122 /** 1123 * Returns the maximum value in the given {@link SparseLongArray}. 1124 * 1125 * @param sparseLongArray The {@link SparseLongArray}. 1126 * @return The maximum value. 1127 * @throws NoSuchElementException If the array is empty. 1128 */ 1129 @RequiresApi(18) maxValue(SparseLongArray sparseLongArray)1130 public static long maxValue(SparseLongArray sparseLongArray) { 1131 if (sparseLongArray.size() == 0) { 1132 throw new NoSuchElementException(); 1133 } 1134 long max = Long.MIN_VALUE; 1135 for (int i = 0; i < sparseLongArray.size(); i++) { 1136 max = max(max, sparseLongArray.valueAt(i)); 1137 } 1138 return max; 1139 } 1140 1141 /** 1142 * Converts a time in microseconds to the corresponding time in milliseconds, preserving {@link 1143 * C#TIME_UNSET} and {@link C#TIME_END_OF_SOURCE} values. 1144 * 1145 * @param timeUs The time in microseconds. 1146 * @return The corresponding time in milliseconds. 1147 */ usToMs(long timeUs)1148 public static long usToMs(long timeUs) { 1149 return (timeUs == C.TIME_UNSET || timeUs == C.TIME_END_OF_SOURCE) ? timeUs : (timeUs / 1000); 1150 } 1151 1152 /** 1153 * Converts a time in milliseconds to the corresponding time in microseconds, preserving {@link 1154 * C#TIME_UNSET} values and {@link C#TIME_END_OF_SOURCE} values. 1155 * 1156 * @param timeMs The time in milliseconds. 1157 * @return The corresponding time in microseconds. 1158 */ msToUs(long timeMs)1159 public static long msToUs(long timeMs) { 1160 return (timeMs == C.TIME_UNSET || timeMs == C.TIME_END_OF_SOURCE) ? timeMs : (timeMs * 1000); 1161 } 1162 1163 /** 1164 * Parses an xs:duration attribute value, returning the parsed duration in milliseconds. 1165 * 1166 * @param value The attribute value to decode. 1167 * @return The parsed duration in milliseconds. 1168 */ parseXsDuration(String value)1169 public static long parseXsDuration(String value) { 1170 Matcher matcher = XS_DURATION_PATTERN.matcher(value); 1171 if (matcher.matches()) { 1172 boolean negated = !TextUtils.isEmpty(matcher.group(1)); 1173 // Durations containing years and months aren't completely defined. We assume there are 1174 // 30.4368 days in a month, and 365.242 days in a year. 1175 String years = matcher.group(3); 1176 double durationSeconds = (years != null) ? Double.parseDouble(years) * 31556908 : 0; 1177 String months = matcher.group(5); 1178 durationSeconds += (months != null) ? Double.parseDouble(months) * 2629739 : 0; 1179 String days = matcher.group(7); 1180 durationSeconds += (days != null) ? Double.parseDouble(days) * 86400 : 0; 1181 String hours = matcher.group(10); 1182 durationSeconds += (hours != null) ? Double.parseDouble(hours) * 3600 : 0; 1183 String minutes = matcher.group(12); 1184 durationSeconds += (minutes != null) ? Double.parseDouble(minutes) * 60 : 0; 1185 String seconds = matcher.group(14); 1186 durationSeconds += (seconds != null) ? Double.parseDouble(seconds) : 0; 1187 long durationMillis = (long) (durationSeconds * 1000); 1188 return negated ? -durationMillis : durationMillis; 1189 } else { 1190 return (long) (Double.parseDouble(value) * 3600 * 1000); 1191 } 1192 } 1193 1194 /** 1195 * Parses an xs:dateTime attribute value, returning the parsed timestamp in milliseconds since the 1196 * epoch. 1197 * 1198 * @param value The attribute value to decode. 1199 * @return The parsed timestamp in milliseconds since the epoch. 1200 * @throws ParserException if an error occurs parsing the dateTime attribute value. 1201 */ 1202 // incompatible types in argument. 1203 // dereference of possibly-null reference matcher.group(9) 1204 @SuppressWarnings({"nullness:argument", "nullness:dereference.of.nullable"}) parseXsDateTime(String value)1205 public static long parseXsDateTime(String value) throws ParserException { 1206 Matcher matcher = XS_DATE_TIME_PATTERN.matcher(value); 1207 if (!matcher.matches()) { 1208 throw ParserException.createForMalformedContainer( 1209 "Invalid date/time format: " + value, /* cause= */ null); 1210 } 1211 1212 int timezoneShift; 1213 if (matcher.group(9) == null) { 1214 // No time zone specified. 1215 timezoneShift = 0; 1216 } else if (matcher.group(9).equalsIgnoreCase("Z")) { 1217 timezoneShift = 0; 1218 } else { 1219 timezoneShift = 1220 ((Integer.parseInt(matcher.group(12)) * 60 + Integer.parseInt(matcher.group(13)))); 1221 if ("-".equals(matcher.group(11))) { 1222 timezoneShift *= -1; 1223 } 1224 } 1225 1226 Calendar dateTime = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 1227 1228 dateTime.clear(); 1229 // Note: The month value is 0-based, hence the -1 on group(2) 1230 dateTime.set( 1231 Integer.parseInt(matcher.group(1)), 1232 Integer.parseInt(matcher.group(2)) - 1, 1233 Integer.parseInt(matcher.group(3)), 1234 Integer.parseInt(matcher.group(4)), 1235 Integer.parseInt(matcher.group(5)), 1236 Integer.parseInt(matcher.group(6))); 1237 if (!TextUtils.isEmpty(matcher.group(8))) { 1238 final BigDecimal bd = new BigDecimal("0." + matcher.group(8)); 1239 // we care only for milliseconds, so movePointRight(3) 1240 dateTime.set(Calendar.MILLISECOND, bd.movePointRight(3).intValue()); 1241 } 1242 1243 long time = dateTime.getTimeInMillis(); 1244 if (timezoneShift != 0) { 1245 time -= timezoneShift * 60000; 1246 } 1247 1248 return time; 1249 } 1250 1251 /** 1252 * Scales a large timestamp. 1253 * 1254 * <p>Logically, scaling consists of a multiplication followed by a division. The actual 1255 * operations performed are designed to minimize the probability of overflow. 1256 * 1257 * @param timestamp The timestamp to scale. 1258 * @param multiplier The multiplier. 1259 * @param divisor The divisor. 1260 * @return The scaled timestamp. 1261 */ scaleLargeTimestamp(long timestamp, long multiplier, long divisor)1262 public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) { 1263 if (divisor >= multiplier && (divisor % multiplier) == 0) { 1264 long divisionFactor = divisor / multiplier; 1265 return timestamp / divisionFactor; 1266 } else if (divisor < multiplier && (multiplier % divisor) == 0) { 1267 long multiplicationFactor = multiplier / divisor; 1268 return timestamp * multiplicationFactor; 1269 } else { 1270 double multiplicationFactor = (double) multiplier / divisor; 1271 return (long) (timestamp * multiplicationFactor); 1272 } 1273 } 1274 1275 /** 1276 * Applies {@link #scaleLargeTimestamp(long, long, long)} to a list of unscaled timestamps. 1277 * 1278 * @param timestamps The timestamps to scale. 1279 * @param multiplier The multiplier. 1280 * @param divisor The divisor. 1281 * @return The scaled timestamps. 1282 */ scaleLargeTimestamps(List<Long> timestamps, long multiplier, long divisor)1283 public static long[] scaleLargeTimestamps(List<Long> timestamps, long multiplier, long divisor) { 1284 long[] scaledTimestamps = new long[timestamps.size()]; 1285 if (divisor >= multiplier && (divisor % multiplier) == 0) { 1286 long divisionFactor = divisor / multiplier; 1287 for (int i = 0; i < scaledTimestamps.length; i++) { 1288 scaledTimestamps[i] = timestamps.get(i) / divisionFactor; 1289 } 1290 } else if (divisor < multiplier && (multiplier % divisor) == 0) { 1291 long multiplicationFactor = multiplier / divisor; 1292 for (int i = 0; i < scaledTimestamps.length; i++) { 1293 scaledTimestamps[i] = timestamps.get(i) * multiplicationFactor; 1294 } 1295 } else { 1296 double multiplicationFactor = (double) multiplier / divisor; 1297 for (int i = 0; i < scaledTimestamps.length; i++) { 1298 scaledTimestamps[i] = (long) (timestamps.get(i) * multiplicationFactor); 1299 } 1300 } 1301 return scaledTimestamps; 1302 } 1303 1304 /** 1305 * Applies {@link #scaleLargeTimestamp(long, long, long)} to an array of unscaled timestamps. 1306 * 1307 * @param timestamps The timestamps to scale. 1308 * @param multiplier The multiplier. 1309 * @param divisor The divisor. 1310 */ scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor)1311 public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) { 1312 if (divisor >= multiplier && (divisor % multiplier) == 0) { 1313 long divisionFactor = divisor / multiplier; 1314 for (int i = 0; i < timestamps.length; i++) { 1315 timestamps[i] /= divisionFactor; 1316 } 1317 } else if (divisor < multiplier && (multiplier % divisor) == 0) { 1318 long multiplicationFactor = multiplier / divisor; 1319 for (int i = 0; i < timestamps.length; i++) { 1320 timestamps[i] *= multiplicationFactor; 1321 } 1322 } else { 1323 double multiplicationFactor = (double) multiplier / divisor; 1324 for (int i = 0; i < timestamps.length; i++) { 1325 timestamps[i] = (long) (timestamps[i] * multiplicationFactor); 1326 } 1327 } 1328 } 1329 1330 /** 1331 * Returns the duration of media that will elapse in {@code playoutDuration}. 1332 * 1333 * @param playoutDuration The duration to scale. 1334 * @param speed The factor by which playback is sped up. 1335 * @return The scaled duration, in the same units as {@code playoutDuration}. 1336 */ getMediaDurationForPlayoutDuration(long playoutDuration, float speed)1337 public static long getMediaDurationForPlayoutDuration(long playoutDuration, float speed) { 1338 if (speed == 1f) { 1339 return playoutDuration; 1340 } 1341 return Math.round((double) playoutDuration * speed); 1342 } 1343 1344 /** 1345 * Returns the playout duration of {@code mediaDuration} of media. 1346 * 1347 * @param mediaDuration The duration to scale. 1348 * @return The scaled duration, in the same units as {@code mediaDuration}. 1349 */ getPlayoutDurationForMediaDuration(long mediaDuration, float speed)1350 public static long getPlayoutDurationForMediaDuration(long mediaDuration, float speed) { 1351 if (speed == 1f) { 1352 return mediaDuration; 1353 } 1354 return Math.round((double) mediaDuration / speed); 1355 } 1356 1357 /** 1358 * Returns the integer equal to the big-endian concatenation of the characters in {@code string} 1359 * as bytes. The string must be no more than four characters long. 1360 * 1361 * @param string A string no more than four characters long. 1362 */ getIntegerCodeForString(String string)1363 public static int getIntegerCodeForString(String string) { 1364 int length = string.length(); 1365 Assertions.checkArgument(length <= 4); 1366 int result = 0; 1367 for (int i = 0; i < length; i++) { 1368 result <<= 8; 1369 result |= string.charAt(i); 1370 } 1371 return result; 1372 } 1373 1374 /** 1375 * Converts an integer to a long by unsigned conversion. 1376 * 1377 * <p>This method is equivalent to {@link Integer#toUnsignedLong(int)} for API 26+. 1378 */ toUnsignedLong(int x)1379 public static long toUnsignedLong(int x) { 1380 // x is implicitly casted to a long before the bit operation is executed but this does not 1381 // impact the method correctness. 1382 return x & 0xFFFFFFFFL; 1383 } 1384 1385 /** 1386 * Returns the long that is composed of the bits of the 2 specified integers. 1387 * 1388 * @param mostSignificantBits The 32 most significant bits of the long to return. 1389 * @param leastSignificantBits The 32 least significant bits of the long to return. 1390 * @return a long where its 32 most significant bits are {@code mostSignificantBits} bits and its 1391 * 32 least significant bits are {@code leastSignificantBits}. 1392 */ toLong(int mostSignificantBits, int leastSignificantBits)1393 public static long toLong(int mostSignificantBits, int leastSignificantBits) { 1394 return (toUnsignedLong(mostSignificantBits) << 32) | toUnsignedLong(leastSignificantBits); 1395 } 1396 1397 /** 1398 * Truncates a sequence of ASCII characters to a maximum length. 1399 * 1400 * <p>This preserves span styling in the {@link CharSequence}. If that's not important, use {@link 1401 * Ascii#truncate(CharSequence, int, String)}. 1402 * 1403 * <p><b>Note:</b> This is not safe to use in general on Unicode text because it may separate 1404 * characters from combining characters or split up surrogate pairs. 1405 * 1406 * @param sequence The character sequence to truncate. 1407 * @param maxLength The max length to truncate to. 1408 * @return {@code sequence} directly if {@code sequence.length() <= maxLength}, otherwise {@code 1409 * sequence.subsequence(0, maxLength}. 1410 */ truncateAscii(CharSequence sequence, int maxLength)1411 public static CharSequence truncateAscii(CharSequence sequence, int maxLength) { 1412 return sequence.length() <= maxLength ? sequence : sequence.subSequence(0, maxLength); 1413 } 1414 1415 /** 1416 * Returns a byte array containing values parsed from the hex string provided. 1417 * 1418 * @param hexString The hex string to convert to bytes. 1419 * @return A byte array containing values parsed from the hex string provided. 1420 */ getBytesFromHexString(String hexString)1421 public static byte[] getBytesFromHexString(String hexString) { 1422 byte[] data = new byte[hexString.length() / 2]; 1423 for (int i = 0; i < data.length; i++) { 1424 int stringOffset = i * 2; 1425 data[i] = 1426 (byte) 1427 ((Character.digit(hexString.charAt(stringOffset), 16) << 4) 1428 + Character.digit(hexString.charAt(stringOffset + 1), 16)); 1429 } 1430 return data; 1431 } 1432 1433 /** 1434 * Returns a string containing a lower-case hex representation of the bytes provided. 1435 * 1436 * @param bytes The byte data to convert to hex. 1437 * @return A String containing the hex representation of {@code bytes}. 1438 */ toHexString(byte[] bytes)1439 public static String toHexString(byte[] bytes) { 1440 StringBuilder result = new StringBuilder(bytes.length * 2); 1441 for (int i = 0; i < bytes.length; i++) { 1442 result 1443 .append(Character.forDigit((bytes[i] >> 4) & 0xF, 16)) 1444 .append(Character.forDigit(bytes[i] & 0xF, 16)); 1445 } 1446 return result.toString(); 1447 } 1448 1449 /** 1450 * Returns a string with comma delimited simple names of each object's class. 1451 * 1452 * @param objects The objects whose simple class names should be comma delimited and returned. 1453 * @return A string with comma delimited simple names of each object's class. 1454 */ getCommaDelimitedSimpleClassNames(Object[] objects)1455 public static String getCommaDelimitedSimpleClassNames(Object[] objects) { 1456 StringBuilder stringBuilder = new StringBuilder(); 1457 for (int i = 0; i < objects.length; i++) { 1458 stringBuilder.append(objects[i].getClass().getSimpleName()); 1459 if (i < objects.length - 1) { 1460 stringBuilder.append(", "); 1461 } 1462 } 1463 return stringBuilder.toString(); 1464 } 1465 1466 /** 1467 * Returns a user agent string based on the given application name and the library version. 1468 * 1469 * @param context A valid context of the calling application. 1470 * @param applicationName String that will be prefix'ed to the generated user agent. 1471 * @return A user agent string generated using the applicationName and the library version. 1472 */ getUserAgent(Context context, String applicationName)1473 public static String getUserAgent(Context context, String applicationName) { 1474 String versionName; 1475 try { 1476 String packageName = context.getPackageName(); 1477 PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); 1478 versionName = info.versionName; 1479 } catch (NameNotFoundException e) { 1480 versionName = "?"; 1481 } 1482 return applicationName 1483 + "/" 1484 + versionName 1485 + " (Linux;Android " 1486 + Build.VERSION.RELEASE 1487 + ") " 1488 + ExoPlayerLibraryInfo.VERSION_SLASHY; 1489 } 1490 1491 /** Returns the number of codec strings in {@code codecs} whose type matches {@code trackType}. */ getCodecCountOfType(@ullable String codecs, @C.TrackType int trackType)1492 public static int getCodecCountOfType(@Nullable String codecs, @C.TrackType int trackType) { 1493 String[] codecArray = splitCodecs(codecs); 1494 int count = 0; 1495 for (String codec : codecArray) { 1496 if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { 1497 count++; 1498 } 1499 } 1500 return count; 1501 } 1502 1503 /** 1504 * Returns a copy of {@code codecs} without the codecs whose track type doesn't match {@code 1505 * trackType}. 1506 * 1507 * @param codecs A codec sequence string, as defined in RFC 6381. 1508 * @param trackType The {@link C.TrackType track type}. 1509 * @return A copy of {@code codecs} without the codecs whose track type doesn't match {@code 1510 * trackType}. If this ends up empty, or {@code codecs} is null, returns null. 1511 */ 1512 @Nullable getCodecsOfType(@ullable String codecs, @C.TrackType int trackType)1513 public static String getCodecsOfType(@Nullable String codecs, @C.TrackType int trackType) { 1514 String[] codecArray = splitCodecs(codecs); 1515 if (codecArray.length == 0) { 1516 return null; 1517 } 1518 StringBuilder builder = new StringBuilder(); 1519 for (String codec : codecArray) { 1520 if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { 1521 if (builder.length() > 0) { 1522 builder.append(","); 1523 } 1524 builder.append(codec); 1525 } 1526 } 1527 return builder.length() > 0 ? builder.toString() : null; 1528 } 1529 1530 /** 1531 * Splits a codecs sequence string, as defined in RFC 6381, into individual codec strings. 1532 * 1533 * @param codecs A codec sequence string, as defined in RFC 6381. 1534 * @return The split codecs, or an array of length zero if the input was empty or null. 1535 */ splitCodecs(@ullable String codecs)1536 public static String[] splitCodecs(@Nullable String codecs) { 1537 if (TextUtils.isEmpty(codecs)) { 1538 return new String[0]; 1539 } 1540 return split(codecs.trim(), "(\\s*,\\s*)"); 1541 } 1542 1543 /** 1544 * Gets a PCM {@link Format} with the specified parameters. 1545 * 1546 * @param pcmEncoding The {@link C.PcmEncoding}. 1547 * @param channels The number of channels, or {@link Format#NO_VALUE} if unknown. 1548 * @param sampleRate The sample rate in Hz, or {@link Format#NO_VALUE} if unknown. 1549 * @return The PCM format. 1550 */ getPcmFormat(@.PcmEncoding int pcmEncoding, int channels, int sampleRate)1551 public static Format getPcmFormat(@C.PcmEncoding int pcmEncoding, int channels, int sampleRate) { 1552 return new Format.Builder() 1553 .setSampleMimeType(MimeTypes.AUDIO_RAW) 1554 .setChannelCount(channels) 1555 .setSampleRate(sampleRate) 1556 .setPcmEncoding(pcmEncoding) 1557 .build(); 1558 } 1559 1560 /** 1561 * Converts a sample bit depth to a corresponding PCM encoding constant. 1562 * 1563 * @param bitDepth The bit depth. Supported values are 8, 16, 24 and 32. 1564 * @return The corresponding encoding. One of {@link C#ENCODING_PCM_8BIT}, {@link 1565 * C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. If 1566 * the bit depth is unsupported then {@link C#ENCODING_INVALID} is returned. 1567 */ getPcmEncoding(int bitDepth)1568 public static @C.PcmEncoding int getPcmEncoding(int bitDepth) { 1569 switch (bitDepth) { 1570 case 8: 1571 return C.ENCODING_PCM_8BIT; 1572 case 16: 1573 return C.ENCODING_PCM_16BIT; 1574 case 24: 1575 return C.ENCODING_PCM_24BIT; 1576 case 32: 1577 return C.ENCODING_PCM_32BIT; 1578 default: 1579 return C.ENCODING_INVALID; 1580 } 1581 } 1582 1583 /** 1584 * Returns whether {@code encoding} is one of the linear PCM encodings. 1585 * 1586 * @param encoding The encoding of the audio data. 1587 * @return Whether the encoding is one of the PCM encodings. 1588 */ isEncodingLinearPcm(@.Encoding int encoding)1589 public static boolean isEncodingLinearPcm(@C.Encoding int encoding) { 1590 return encoding == C.ENCODING_PCM_8BIT 1591 || encoding == C.ENCODING_PCM_16BIT 1592 || encoding == C.ENCODING_PCM_16BIT_BIG_ENDIAN 1593 || encoding == C.ENCODING_PCM_24BIT 1594 || encoding == C.ENCODING_PCM_32BIT 1595 || encoding == C.ENCODING_PCM_FLOAT; 1596 } 1597 1598 /** 1599 * Returns whether {@code encoding} is high resolution (> 16-bit) PCM. 1600 * 1601 * @param encoding The encoding of the audio data. 1602 * @return Whether the encoding is high resolution PCM. 1603 */ isEncodingHighResolutionPcm(@.PcmEncoding int encoding)1604 public static boolean isEncodingHighResolutionPcm(@C.PcmEncoding int encoding) { 1605 return encoding == C.ENCODING_PCM_24BIT 1606 || encoding == C.ENCODING_PCM_32BIT 1607 || encoding == C.ENCODING_PCM_FLOAT; 1608 } 1609 1610 /** 1611 * Returns the audio track channel configuration for the given channel count, or {@link 1612 * AudioFormat#CHANNEL_INVALID} if output is not possible. 1613 * 1614 * @param channelCount The number of channels in the input audio. 1615 * @return The channel configuration or {@link AudioFormat#CHANNEL_INVALID} if output is not 1616 * possible. 1617 */ getAudioTrackChannelConfig(int channelCount)1618 public static int getAudioTrackChannelConfig(int channelCount) { 1619 switch (channelCount) { 1620 case 1: 1621 return AudioFormat.CHANNEL_OUT_MONO; 1622 case 2: 1623 return AudioFormat.CHANNEL_OUT_STEREO; 1624 case 3: 1625 return AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER; 1626 case 4: 1627 return AudioFormat.CHANNEL_OUT_QUAD; 1628 case 5: 1629 return AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER; 1630 case 6: 1631 return AudioFormat.CHANNEL_OUT_5POINT1; 1632 case 7: 1633 return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; 1634 case 8: 1635 if (Util.SDK_INT >= 23) { 1636 return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; 1637 } else if (Util.SDK_INT >= 21) { 1638 // Equal to AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, which is hidden before Android M. 1639 return AudioFormat.CHANNEL_OUT_5POINT1 1640 | AudioFormat.CHANNEL_OUT_SIDE_LEFT 1641 | AudioFormat.CHANNEL_OUT_SIDE_RIGHT; 1642 } else { 1643 // 8 ch output is not supported before Android L. 1644 return AudioFormat.CHANNEL_INVALID; 1645 } 1646 default: 1647 return AudioFormat.CHANNEL_INVALID; 1648 } 1649 } 1650 1651 /** 1652 * Returns the frame size for audio with {@code channelCount} channels in the specified encoding. 1653 * 1654 * @param pcmEncoding The encoding of the audio data. 1655 * @param channelCount The channel count. 1656 * @return The size of one audio frame in bytes. 1657 */ getPcmFrameSize(@.PcmEncoding int pcmEncoding, int channelCount)1658 public static int getPcmFrameSize(@C.PcmEncoding int pcmEncoding, int channelCount) { 1659 switch (pcmEncoding) { 1660 case C.ENCODING_PCM_8BIT: 1661 return channelCount; 1662 case C.ENCODING_PCM_16BIT: 1663 case C.ENCODING_PCM_16BIT_BIG_ENDIAN: 1664 return channelCount * 2; 1665 case C.ENCODING_PCM_24BIT: 1666 return channelCount * 3; 1667 case C.ENCODING_PCM_32BIT: 1668 case C.ENCODING_PCM_FLOAT: 1669 return channelCount * 4; 1670 case C.ENCODING_INVALID: 1671 case Format.NO_VALUE: 1672 default: 1673 throw new IllegalArgumentException(); 1674 } 1675 } 1676 1677 /** Returns the {@link C.AudioUsage} corresponding to the specified {@link C.StreamType}. */ getAudioUsageForStreamType(@.StreamType int streamType)1678 public static @C.AudioUsage int getAudioUsageForStreamType(@C.StreamType int streamType) { 1679 switch (streamType) { 1680 case C.STREAM_TYPE_ALARM: 1681 return C.USAGE_ALARM; 1682 case C.STREAM_TYPE_DTMF: 1683 return C.USAGE_VOICE_COMMUNICATION_SIGNALLING; 1684 case C.STREAM_TYPE_NOTIFICATION: 1685 return C.USAGE_NOTIFICATION; 1686 case C.STREAM_TYPE_RING: 1687 return C.USAGE_NOTIFICATION_RINGTONE; 1688 case C.STREAM_TYPE_SYSTEM: 1689 return C.USAGE_ASSISTANCE_SONIFICATION; 1690 case C.STREAM_TYPE_VOICE_CALL: 1691 return C.USAGE_VOICE_COMMUNICATION; 1692 case C.STREAM_TYPE_MUSIC: 1693 default: 1694 return C.USAGE_MEDIA; 1695 } 1696 } 1697 1698 /** Returns the {@link C.AudioContentType} corresponding to the specified {@link C.StreamType}. */ getAudioContentTypeForStreamType( @.StreamType int streamType)1699 public static @C.AudioContentType int getAudioContentTypeForStreamType( 1700 @C.StreamType int streamType) { 1701 switch (streamType) { 1702 case C.STREAM_TYPE_ALARM: 1703 case C.STREAM_TYPE_DTMF: 1704 case C.STREAM_TYPE_NOTIFICATION: 1705 case C.STREAM_TYPE_RING: 1706 case C.STREAM_TYPE_SYSTEM: 1707 return C.CONTENT_TYPE_SONIFICATION; 1708 case C.STREAM_TYPE_VOICE_CALL: 1709 return C.CONTENT_TYPE_SPEECH; 1710 case C.STREAM_TYPE_MUSIC: 1711 default: 1712 return C.CONTENT_TYPE_MUSIC; 1713 } 1714 } 1715 1716 /** Returns the {@link C.StreamType} corresponding to the specified {@link C.AudioUsage}. */ getStreamTypeForAudioUsage(@.AudioUsage int usage)1717 public static @C.StreamType int getStreamTypeForAudioUsage(@C.AudioUsage int usage) { 1718 switch (usage) { 1719 case C.USAGE_MEDIA: 1720 case C.USAGE_GAME: 1721 case C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: 1722 return C.STREAM_TYPE_MUSIC; 1723 case C.USAGE_ASSISTANCE_SONIFICATION: 1724 return C.STREAM_TYPE_SYSTEM; 1725 case C.USAGE_VOICE_COMMUNICATION: 1726 return C.STREAM_TYPE_VOICE_CALL; 1727 case C.USAGE_VOICE_COMMUNICATION_SIGNALLING: 1728 return C.STREAM_TYPE_DTMF; 1729 case C.USAGE_ALARM: 1730 return C.STREAM_TYPE_ALARM; 1731 case C.USAGE_NOTIFICATION_RINGTONE: 1732 return C.STREAM_TYPE_RING; 1733 case C.USAGE_NOTIFICATION: 1734 case C.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: 1735 case C.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: 1736 case C.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: 1737 case C.USAGE_NOTIFICATION_EVENT: 1738 return C.STREAM_TYPE_NOTIFICATION; 1739 case C.USAGE_ASSISTANCE_ACCESSIBILITY: 1740 case C.USAGE_ASSISTANT: 1741 case C.USAGE_UNKNOWN: 1742 default: 1743 return C.STREAM_TYPE_DEFAULT; 1744 } 1745 } 1746 1747 /** 1748 * Returns a newly generated audio session identifier, or {@link AudioManager#ERROR} if an error 1749 * occurred in which case audio playback may fail. 1750 * 1751 * @see AudioManager#generateAudioSessionId() 1752 */ 1753 @RequiresApi(21) generateAudioSessionIdV21(Context context)1754 public static int generateAudioSessionIdV21(Context context) { 1755 @Nullable 1756 AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); 1757 return audioManager == null ? AudioManager.ERROR : audioManager.generateAudioSessionId(); 1758 } 1759 1760 /** 1761 * Derives a DRM {@link UUID} from {@code drmScheme}. 1762 * 1763 * @param drmScheme A UUID string, or {@code "widevine"}, {@code "playready"} or {@code 1764 * "clearkey"}. 1765 * @return The derived {@link UUID}, or {@code null} if one could not be derived. 1766 */ 1767 @Nullable getDrmUuid(String drmScheme)1768 public static UUID getDrmUuid(String drmScheme) { 1769 switch (Ascii.toLowerCase(drmScheme)) { 1770 case "widevine": 1771 return C.WIDEVINE_UUID; 1772 case "playready": 1773 return C.PLAYREADY_UUID; 1774 case "clearkey": 1775 return C.CLEARKEY_UUID; 1776 default: 1777 try { 1778 return UUID.fromString(drmScheme); 1779 } catch (RuntimeException e) { 1780 return null; 1781 } 1782 } 1783 } 1784 1785 /** 1786 * Returns a {@link PlaybackException.ErrorCode} value that corresponds to the provided {@link 1787 * MediaDrm.ErrorCodes} value. Returns {@link PlaybackException#ERROR_CODE_DRM_SYSTEM_ERROR} if 1788 * the provided error code isn't recognised. 1789 */ getErrorCodeForMediaDrmErrorCode( int mediaDrmErrorCode)1790 public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmErrorCode( 1791 int mediaDrmErrorCode) { 1792 switch (mediaDrmErrorCode) { 1793 case MediaDrm.ErrorCodes.ERROR_PROVISIONING_CONFIG: 1794 case MediaDrm.ErrorCodes.ERROR_PROVISIONING_PARSE: 1795 case MediaDrm.ErrorCodes.ERROR_PROVISIONING_REQUEST_REJECTED: 1796 case MediaDrm.ErrorCodes.ERROR_PROVISIONING_CERTIFICATE: 1797 case MediaDrm.ErrorCodes.ERROR_PROVISIONING_RETRY: 1798 return PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED; 1799 case MediaDrm.ErrorCodes.ERROR_LICENSE_PARSE: 1800 case MediaDrm.ErrorCodes.ERROR_LICENSE_RELEASE: 1801 case MediaDrm.ErrorCodes.ERROR_LICENSE_REQUEST_REJECTED: 1802 case MediaDrm.ErrorCodes.ERROR_LICENSE_RESTORE: 1803 case MediaDrm.ErrorCodes.ERROR_LICENSE_STATE: 1804 case MediaDrm.ErrorCodes.ERROR_CERTIFICATE_MALFORMED: 1805 return PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED; 1806 case MediaDrm.ErrorCodes.ERROR_LICENSE_POLICY: 1807 case MediaDrm.ErrorCodes.ERROR_INSUFFICIENT_OUTPUT_PROTECTION: 1808 case MediaDrm.ErrorCodes.ERROR_INSUFFICIENT_SECURITY: 1809 case MediaDrm.ErrorCodes.ERROR_KEY_EXPIRED: 1810 case MediaDrm.ErrorCodes.ERROR_KEY_NOT_LOADED: 1811 return PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION; 1812 case MediaDrm.ErrorCodes.ERROR_INIT_DATA: 1813 case MediaDrm.ErrorCodes.ERROR_FRAME_TOO_LARGE: 1814 return PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR; 1815 default: 1816 return PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR; 1817 } 1818 } 1819 1820 /** 1821 * Makes a best guess to infer the {@link ContentType} from a {@link Uri}. 1822 * 1823 * @param uri The {@link Uri}. 1824 * @param overrideExtension If not null, used to infer the type. 1825 * @return The content type. 1826 */ inferContentType(Uri uri, @Nullable String overrideExtension)1827 public static @ContentType int inferContentType(Uri uri, @Nullable String overrideExtension) { 1828 return TextUtils.isEmpty(overrideExtension) 1829 ? inferContentType(uri) 1830 : inferContentType("." + overrideExtension); 1831 } 1832 1833 /** 1834 * Makes a best guess to infer the {@link ContentType} from a {@link Uri}. 1835 * 1836 * @param uri The {@link Uri}. 1837 * @return The content type. 1838 */ inferContentType(Uri uri)1839 public static @ContentType int inferContentType(Uri uri) { 1840 @Nullable String scheme = uri.getScheme(); 1841 if (scheme != null && Ascii.equalsIgnoreCase("rtsp", scheme)) { 1842 return C.TYPE_RTSP; 1843 } 1844 1845 @Nullable String path = uri.getPath(); 1846 return path == null ? C.TYPE_OTHER : inferContentType(path); 1847 } 1848 1849 /** 1850 * Makes a best guess to infer the {@link ContentType} from a file name. 1851 * 1852 * @param fileName Name of the file. It can include the path of the file. 1853 * @return The content type. 1854 */ inferContentType(String fileName)1855 public static @ContentType int inferContentType(String fileName) { 1856 fileName = Ascii.toLowerCase(fileName); 1857 if (fileName.endsWith(".mpd")) { 1858 return C.TYPE_DASH; 1859 } else if (fileName.endsWith(".m3u8")) { 1860 return C.TYPE_HLS; 1861 } 1862 Matcher ismMatcher = ISM_URL_PATTERN.matcher(fileName); 1863 if (ismMatcher.matches()) { 1864 @Nullable String extensions = ismMatcher.group(2); 1865 if (extensions != null) { 1866 if (extensions.contains(ISM_DASH_FORMAT_EXTENSION)) { 1867 return C.TYPE_DASH; 1868 } else if (extensions.contains(ISM_HLS_FORMAT_EXTENSION)) { 1869 return C.TYPE_HLS; 1870 } 1871 } 1872 return C.TYPE_SS; 1873 } 1874 return C.TYPE_OTHER; 1875 } 1876 1877 /** 1878 * Makes a best guess to infer the {@link ContentType} from a {@link Uri} and optional MIME type. 1879 * 1880 * @param uri The {@link Uri}. 1881 * @param mimeType If MIME type, or {@code null}. 1882 * @return The content type. 1883 */ inferContentTypeForUriAndMimeType( Uri uri, @Nullable String mimeType)1884 public static @ContentType int inferContentTypeForUriAndMimeType( 1885 Uri uri, @Nullable String mimeType) { 1886 if (mimeType == null) { 1887 return Util.inferContentType(uri); 1888 } 1889 switch (mimeType) { 1890 case MimeTypes.APPLICATION_MPD: 1891 return C.TYPE_DASH; 1892 case MimeTypes.APPLICATION_M3U8: 1893 return C.TYPE_HLS; 1894 case MimeTypes.APPLICATION_SS: 1895 return C.TYPE_SS; 1896 case MimeTypes.APPLICATION_RTSP: 1897 return C.TYPE_RTSP; 1898 default: 1899 return C.TYPE_OTHER; 1900 } 1901 } 1902 1903 /** 1904 * Returns the MIME type corresponding to the given adaptive {@link ContentType}, or {@code null} 1905 * if the content type is not adaptive. 1906 */ 1907 @Nullable getAdaptiveMimeTypeForContentType(@ontentType int contentType)1908 public static String getAdaptiveMimeTypeForContentType(@ContentType int contentType) { 1909 switch (contentType) { 1910 case C.TYPE_DASH: 1911 return MimeTypes.APPLICATION_MPD; 1912 case C.TYPE_HLS: 1913 return MimeTypes.APPLICATION_M3U8; 1914 case C.TYPE_SS: 1915 return MimeTypes.APPLICATION_SS; 1916 case C.TYPE_RTSP: 1917 case C.TYPE_OTHER: 1918 default: 1919 return null; 1920 } 1921 } 1922 1923 /** 1924 * If the provided URI is an ISM Presentation URI, returns the URI with "Manifest" appended to its 1925 * path (i.e., the corresponding default manifest URI). Else returns the provided URI without 1926 * modification. See [MS-SSTR] v20180912, section 2.2.1. 1927 * 1928 * @param uri The original URI. 1929 * @return The fixed URI. 1930 */ fixSmoothStreamingIsmManifestUri(Uri uri)1931 public static Uri fixSmoothStreamingIsmManifestUri(Uri uri) { 1932 @Nullable String path = uri.getPath(); 1933 if (path == null) { 1934 return uri; 1935 } 1936 Matcher ismMatcher = ISM_URL_PATTERN.matcher(Ascii.toLowerCase(path)); 1937 if (ismMatcher.matches() && ismMatcher.group(1) == null) { 1938 // Add missing "Manifest" suffix. 1939 return Uri.withAppendedPath(uri, "Manifest"); 1940 } 1941 return uri; 1942 } 1943 1944 /** 1945 * Returns the specified millisecond time formatted as a string. 1946 * 1947 * @param builder The builder that {@code formatter} will write to. 1948 * @param formatter The formatter. 1949 * @param timeMs The time to format as a string, in milliseconds. 1950 * @return The time formatted as a string. 1951 */ getStringForTime(StringBuilder builder, Formatter formatter, long timeMs)1952 public static String getStringForTime(StringBuilder builder, Formatter formatter, long timeMs) { 1953 if (timeMs == C.TIME_UNSET) { 1954 timeMs = 0; 1955 } 1956 String prefix = timeMs < 0 ? "-" : ""; 1957 timeMs = abs(timeMs); 1958 long totalSeconds = (timeMs + 500) / 1000; 1959 long seconds = totalSeconds % 60; 1960 long minutes = (totalSeconds / 60) % 60; 1961 long hours = totalSeconds / 3600; 1962 builder.setLength(0); 1963 return hours > 0 1964 ? formatter.format("%s%d:%02d:%02d", prefix, hours, minutes, seconds).toString() 1965 : formatter.format("%s%02d:%02d", prefix, minutes, seconds).toString(); 1966 } 1967 1968 /** 1969 * Escapes a string so that it's safe for use as a file or directory name on at least FAT32 1970 * filesystems. FAT32 is the most restrictive of all filesystems still commonly used today. 1971 * 1972 * <p>For simplicity, this only handles common characters known to be illegal on FAT32: <, 1973 * >, :, ", /, \, |, ?, and *. % is also escaped since it is used as the escape character. 1974 * Escaping is performed in a consistent way so that no collisions occur and {@link 1975 * #unescapeFileName(String)} can be used to retrieve the original file name. 1976 * 1977 * @param fileName File name to be escaped. 1978 * @return An escaped file name which will be safe for use on at least FAT32 filesystems. 1979 */ escapeFileName(String fileName)1980 public static String escapeFileName(String fileName) { 1981 int length = fileName.length(); 1982 int charactersToEscapeCount = 0; 1983 for (int i = 0; i < length; i++) { 1984 if (shouldEscapeCharacter(fileName.charAt(i))) { 1985 charactersToEscapeCount++; 1986 } 1987 } 1988 if (charactersToEscapeCount == 0) { 1989 return fileName; 1990 } 1991 1992 int i = 0; 1993 StringBuilder builder = new StringBuilder(length + charactersToEscapeCount * 2); 1994 while (charactersToEscapeCount > 0) { 1995 char c = fileName.charAt(i++); 1996 if (shouldEscapeCharacter(c)) { 1997 builder.append('%').append(Integer.toHexString(c)); 1998 charactersToEscapeCount--; 1999 } else { 2000 builder.append(c); 2001 } 2002 } 2003 if (i < length) { 2004 builder.append(fileName, i, length); 2005 } 2006 return builder.toString(); 2007 } 2008 shouldEscapeCharacter(char c)2009 private static boolean shouldEscapeCharacter(char c) { 2010 switch (c) { 2011 case '<': 2012 case '>': 2013 case ':': 2014 case '"': 2015 case '/': 2016 case '\\': 2017 case '|': 2018 case '?': 2019 case '*': 2020 case '%': 2021 return true; 2022 default: 2023 return false; 2024 } 2025 } 2026 2027 /** 2028 * Unescapes an escaped file or directory name back to its original value. 2029 * 2030 * <p>See {@link #escapeFileName(String)} for more information. 2031 * 2032 * @param fileName File name to be unescaped. 2033 * @return The original value of the file name before it was escaped, or null if the escaped 2034 * fileName seems invalid. 2035 */ 2036 @Nullable unescapeFileName(String fileName)2037 public static String unescapeFileName(String fileName) { 2038 int length = fileName.length(); 2039 int percentCharacterCount = 0; 2040 for (int i = 0; i < length; i++) { 2041 if (fileName.charAt(i) == '%') { 2042 percentCharacterCount++; 2043 } 2044 } 2045 if (percentCharacterCount == 0) { 2046 return fileName; 2047 } 2048 2049 int expectedLength = length - percentCharacterCount * 2; 2050 StringBuilder builder = new StringBuilder(expectedLength); 2051 Matcher matcher = ESCAPED_CHARACTER_PATTERN.matcher(fileName); 2052 int startOfNotEscaped = 0; 2053 while (percentCharacterCount > 0 && matcher.find()) { 2054 char unescapedCharacter = (char) Integer.parseInt(checkNotNull(matcher.group(1)), 16); 2055 builder.append(fileName, startOfNotEscaped, matcher.start()).append(unescapedCharacter); 2056 startOfNotEscaped = matcher.end(); 2057 percentCharacterCount--; 2058 } 2059 if (startOfNotEscaped < length) { 2060 builder.append(fileName, startOfNotEscaped, length); 2061 } 2062 if (builder.length() != expectedLength) { 2063 return null; 2064 } 2065 return builder.toString(); 2066 } 2067 2068 /** Returns a data URI with the specified MIME type and data. */ getDataUriForString(String mimeType, String data)2069 public static Uri getDataUriForString(String mimeType, String data) { 2070 return Uri.parse( 2071 "data:" + mimeType + ";base64," + Base64.encodeToString(data.getBytes(), Base64.NO_WRAP)); 2072 } 2073 2074 /** 2075 * A hacky method that always throws {@code t} even if {@code t} is a checked exception, and is 2076 * not declared to be thrown. 2077 */ sneakyThrow(Throwable t)2078 public static void sneakyThrow(Throwable t) { 2079 sneakyThrowInternal(t); 2080 } 2081 2082 @SuppressWarnings("unchecked") sneakyThrowInternal(Throwable t)2083 private static <T extends Throwable> void sneakyThrowInternal(Throwable t) throws T { 2084 throw (T) t; 2085 } 2086 2087 /** Recursively deletes a directory and its content. */ recursiveDelete(File fileOrDirectory)2088 public static void recursiveDelete(File fileOrDirectory) { 2089 File[] directoryFiles = fileOrDirectory.listFiles(); 2090 if (directoryFiles != null) { 2091 for (File child : directoryFiles) { 2092 recursiveDelete(child); 2093 } 2094 } 2095 fileOrDirectory.delete(); 2096 } 2097 2098 /** Creates an empty directory in the directory returned by {@link Context#getCacheDir()}. */ createTempDirectory(Context context, String prefix)2099 public static File createTempDirectory(Context context, String prefix) throws IOException { 2100 File tempFile = createTempFile(context, prefix); 2101 tempFile.delete(); // Delete the temp file. 2102 tempFile.mkdir(); // Create a directory with the same name. 2103 return tempFile; 2104 } 2105 2106 /** Creates a new empty file in the directory returned by {@link Context#getCacheDir()}. */ createTempFile(Context context, String prefix)2107 public static File createTempFile(Context context, String prefix) throws IOException { 2108 return File.createTempFile(prefix, null, checkNotNull(context.getCacheDir())); 2109 } 2110 2111 /** 2112 * Returns the result of updating a CRC-32 with the specified bytes in a "most significant bit 2113 * first" order. 2114 * 2115 * @param bytes Array containing the bytes to update the crc value with. 2116 * @param start The index to the first byte in the byte range to update the crc with. 2117 * @param end The index after the last byte in the byte range to update the crc with. 2118 * @param initialValue The initial value for the crc calculation. 2119 * @return The result of updating the initial value with the specified bytes. 2120 */ crc32(byte[] bytes, int start, int end, int initialValue)2121 public static int crc32(byte[] bytes, int start, int end, int initialValue) { 2122 for (int i = start; i < end; i++) { 2123 initialValue = 2124 (initialValue << 8) 2125 ^ CRC32_BYTES_MSBF[((initialValue >>> 24) ^ (bytes[i] & 0xFF)) & 0xFF]; 2126 } 2127 return initialValue; 2128 } 2129 2130 /** 2131 * Returns the result of updating a CRC-8 with the specified bytes in a "most significant bit 2132 * first" order. 2133 * 2134 * @param bytes Array containing the bytes to update the crc value with. 2135 * @param start The index to the first byte in the byte range to update the crc with. 2136 * @param end The index after the last byte in the byte range to update the crc with. 2137 * @param initialValue The initial value for the crc calculation. 2138 * @return The result of updating the initial value with the specified bytes. 2139 */ crc8(byte[] bytes, int start, int end, int initialValue)2140 public static int crc8(byte[] bytes, int start, int end, int initialValue) { 2141 for (int i = start; i < end; i++) { 2142 initialValue = CRC8_BYTES_MSBF[initialValue ^ (bytes[i] & 0xFF)]; 2143 } 2144 return initialValue; 2145 } 2146 2147 /** Compresses {@code input} using gzip and returns the result in a newly allocated byte array. */ gzip(byte[] input)2148 public static byte[] gzip(byte[] input) { 2149 ByteArrayOutputStream output = new ByteArrayOutputStream(); 2150 try (GZIPOutputStream os = new GZIPOutputStream(output)) { 2151 os.write(input); 2152 } catch (IOException e) { 2153 // A ByteArrayOutputStream wrapped in a GZipOutputStream should never throw IOException since 2154 // no I/O is happening. 2155 throw new IllegalStateException(e); 2156 } 2157 return output.toByteArray(); 2158 } 2159 2160 /** 2161 * Absolute <i>get</i> method for reading an int value in {@link ByteOrder#BIG_ENDIAN} in a {@link 2162 * ByteBuffer}. Same as {@link ByteBuffer#getInt(int)} except the buffer's order as returned by 2163 * {@link ByteBuffer#order()} is ignored and {@link ByteOrder#BIG_ENDIAN} is used instead. 2164 * 2165 * @param buffer The buffer from which to read an int in big endian. 2166 * @param index The index from which the bytes will be read. 2167 * @return The int value at the given index with the buffer bytes ordered most significant to 2168 * least significant. 2169 */ getBigEndianInt(ByteBuffer buffer, int index)2170 public static int getBigEndianInt(ByteBuffer buffer, int index) { 2171 int value = buffer.getInt(index); 2172 return buffer.order() == ByteOrder.BIG_ENDIAN ? value : Integer.reverseBytes(value); 2173 } 2174 2175 /** 2176 * Returns the upper-case ISO 3166-1 alpha-2 country code of the current registered operator's MCC 2177 * (Mobile Country Code), or the country code of the default Locale if not available. 2178 * 2179 * @param context A context to access the telephony service. If null, only the Locale can be used. 2180 * @return The upper-case ISO 3166-1 alpha-2 country code, or an empty String if unavailable. 2181 */ getCountryCode(@ullable Context context)2182 public static String getCountryCode(@Nullable Context context) { 2183 if (context != null) { 2184 @Nullable 2185 TelephonyManager telephonyManager = 2186 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 2187 if (telephonyManager != null) { 2188 String countryCode = telephonyManager.getNetworkCountryIso(); 2189 if (!TextUtils.isEmpty(countryCode)) { 2190 return Ascii.toUpperCase(countryCode); 2191 } 2192 } 2193 } 2194 return Ascii.toUpperCase(Locale.getDefault().getCountry()); 2195 } 2196 2197 /** 2198 * Returns a non-empty array of normalized IETF BCP 47 language tags for the system languages 2199 * ordered by preference. 2200 */ getSystemLanguageCodes()2201 public static String[] getSystemLanguageCodes() { 2202 String[] systemLocales = getSystemLocales(); 2203 for (int i = 0; i < systemLocales.length; i++) { 2204 systemLocales[i] = normalizeLanguageCode(systemLocales[i]); 2205 } 2206 return systemLocales; 2207 } 2208 2209 /** Returns the default {@link Locale.Category#DISPLAY DISPLAY} {@link Locale}. */ getDefaultDisplayLocale()2210 public static Locale getDefaultDisplayLocale() { 2211 return Util.SDK_INT >= 24 ? Locale.getDefault(Locale.Category.DISPLAY) : Locale.getDefault(); 2212 } 2213 2214 /** 2215 * Uncompresses the data in {@code input}. 2216 * 2217 * @param input Wraps the compressed input data. 2218 * @param output Wraps an output buffer to be used to store the uncompressed data. If {@code 2219 * output.data} isn't big enough to hold the uncompressed data, a new array is created. If 2220 * {@code true} is returned then the output's position will be set to 0 and its limit will be 2221 * set to the length of the uncompressed data. 2222 * @param inflater If not null, used to uncompressed the input. Otherwise a new {@link Inflater} 2223 * is created. 2224 * @return Whether the input is uncompressed successfully. 2225 */ inflate( ParsableByteArray input, ParsableByteArray output, @Nullable Inflater inflater)2226 public static boolean inflate( 2227 ParsableByteArray input, ParsableByteArray output, @Nullable Inflater inflater) { 2228 if (input.bytesLeft() <= 0) { 2229 return false; 2230 } 2231 if (output.capacity() < input.bytesLeft()) { 2232 output.ensureCapacity(2 * input.bytesLeft()); 2233 } 2234 if (inflater == null) { 2235 inflater = new Inflater(); 2236 } 2237 inflater.setInput(input.getData(), input.getPosition(), input.bytesLeft()); 2238 try { 2239 int outputSize = 0; 2240 while (true) { 2241 outputSize += 2242 inflater.inflate(output.getData(), outputSize, output.capacity() - outputSize); 2243 if (inflater.finished()) { 2244 output.setLimit(outputSize); 2245 return true; 2246 } 2247 if (inflater.needsDictionary() || inflater.needsInput()) { 2248 return false; 2249 } 2250 if (outputSize == output.capacity()) { 2251 output.ensureCapacity(output.capacity() * 2); 2252 } 2253 } 2254 } catch (DataFormatException e) { 2255 return false; 2256 } finally { 2257 inflater.reset(); 2258 } 2259 } 2260 2261 /** 2262 * Returns whether the app is running on a TV device. 2263 * 2264 * @param context Any context. 2265 * @return Whether the app is running on a TV device. 2266 */ isTv(Context context)2267 public static boolean isTv(Context context) { 2268 // See https://developer.android.com/training/tv/start/hardware.html#runtime-check. 2269 @Nullable 2270 UiModeManager uiModeManager = 2271 (UiModeManager) context.getApplicationContext().getSystemService(UI_MODE_SERVICE); 2272 return uiModeManager != null 2273 && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; 2274 } 2275 2276 /** 2277 * Returns whether the app is running on an automotive device. 2278 * 2279 * @param context Any context. 2280 * @return Whether the app is running on an automotive device. 2281 */ isAutomotive(Context context)2282 public static boolean isAutomotive(Context context) { 2283 return Util.SDK_INT >= 23 2284 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 2285 } 2286 2287 /** 2288 * Gets the size of the current mode of the default display, in pixels. 2289 * 2290 * <p>Note that due to application UI scaling, the number of pixels made available to applications 2291 * (as reported by {@link Display#getSize(Point)} may differ from the mode's actual resolution (as 2292 * reported by this function). For example, applications running on a display configured with a 4K 2293 * mode may have their UI laid out and rendered in 1080p and then scaled up. Applications can take 2294 * advantage of the full mode resolution through a {@link SurfaceView} using full size buffers. 2295 * 2296 * @param context Any context. 2297 * @return The size of the current mode, in pixels. 2298 */ getCurrentDisplayModeSize(Context context)2299 public static Point getCurrentDisplayModeSize(Context context) { 2300 @Nullable Display defaultDisplay = null; 2301 if (Util.SDK_INT >= 17) { 2302 @Nullable 2303 DisplayManager displayManager = 2304 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); 2305 // We don't expect displayManager to ever be null, so this check is just precautionary. 2306 // Consider removing it when the library minSdkVersion is increased to 17 or higher. 2307 if (displayManager != null) { 2308 defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 2309 } 2310 } 2311 if (defaultDisplay == null) { 2312 WindowManager windowManager = 2313 checkNotNull((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)); 2314 defaultDisplay = windowManager.getDefaultDisplay(); 2315 } 2316 return getCurrentDisplayModeSize(context, defaultDisplay); 2317 } 2318 2319 /** 2320 * Gets the size of the current mode of the specified display, in pixels. 2321 * 2322 * <p>Note that due to application UI scaling, the number of pixels made available to applications 2323 * (as reported by {@link Display#getSize(Point)} may differ from the mode's actual resolution (as 2324 * reported by this function). For example, applications running on a display configured with a 4K 2325 * mode may have their UI laid out and rendered in 1080p and then scaled up. Applications can take 2326 * advantage of the full mode resolution through a {@link SurfaceView} using full size buffers. 2327 * 2328 * @param context Any context. 2329 * @param display The display whose size is to be returned. 2330 * @return The size of the current mode, in pixels. 2331 */ getCurrentDisplayModeSize(Context context, Display display)2332 public static Point getCurrentDisplayModeSize(Context context, Display display) { 2333 if (display.getDisplayId() == Display.DEFAULT_DISPLAY && isTv(context)) { 2334 // On Android TVs it's common for the UI to be driven at a lower resolution than the physical 2335 // resolution of the display (e.g., driving the UI at 1080p when the display is 4K). 2336 // SurfaceView outputs are still able to use the full physical resolution on such devices. 2337 // 2338 // Prior to API level 26, the Display object did not provide a way to obtain the true physical 2339 // resolution of the display. From API level 26, Display.getMode().getPhysical[Width|Height] 2340 // is expected to return the display's true physical resolution, but we still see devices 2341 // setting their hardware compositor output size incorrectly, which makes this unreliable. 2342 // Hence for TV devices, we try and read the display's true physical resolution from system 2343 // properties. 2344 // 2345 // From API level 28, Treble may prevent the system from writing sys.display-size, so we check 2346 // vendor.display-size instead. 2347 @Nullable 2348 String displaySize = 2349 Util.SDK_INT < 28 2350 ? getSystemProperty("sys.display-size") 2351 : getSystemProperty("vendor.display-size"); 2352 // If we managed to read the display size, attempt to parse it. 2353 if (!TextUtils.isEmpty(displaySize)) { 2354 try { 2355 String[] displaySizeParts = split(displaySize.trim(), "x"); 2356 if (displaySizeParts.length == 2) { 2357 int width = Integer.parseInt(displaySizeParts[0]); 2358 int height = Integer.parseInt(displaySizeParts[1]); 2359 if (width > 0 && height > 0) { 2360 return new Point(width, height); 2361 } 2362 } 2363 } catch (NumberFormatException e) { 2364 // Do nothing. 2365 } 2366 Log.e(TAG, "Invalid display size: " + displaySize); 2367 } 2368 2369 // Sony Android TVs advertise support for 4k output via a system feature. 2370 if ("Sony".equals(Util.MANUFACTURER) 2371 && Util.MODEL.startsWith("BRAVIA") 2372 && context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) { 2373 return new Point(3840, 2160); 2374 } 2375 } 2376 2377 Point displaySize = new Point(); 2378 if (Util.SDK_INT >= 23) { 2379 getDisplaySizeV23(display, displaySize); 2380 } else if (Util.SDK_INT >= 17) { 2381 getDisplaySizeV17(display, displaySize); 2382 } else { 2383 getDisplaySizeV16(display, displaySize); 2384 } 2385 return displaySize; 2386 } 2387 2388 /** 2389 * Returns a string representation of a {@link C.TrackType}. 2390 * 2391 * @param trackType A {@link C.TrackType} constant, 2392 * @return A string representation of this constant. 2393 */ getTrackTypeString(@.TrackType int trackType)2394 public static String getTrackTypeString(@C.TrackType int trackType) { 2395 switch (trackType) { 2396 case C.TRACK_TYPE_DEFAULT: 2397 return "default"; 2398 case C.TRACK_TYPE_AUDIO: 2399 return "audio"; 2400 case C.TRACK_TYPE_VIDEO: 2401 return "video"; 2402 case C.TRACK_TYPE_TEXT: 2403 return "text"; 2404 case C.TRACK_TYPE_IMAGE: 2405 return "image"; 2406 case C.TRACK_TYPE_METADATA: 2407 return "metadata"; 2408 case C.TRACK_TYPE_CAMERA_MOTION: 2409 return "camera motion"; 2410 case C.TRACK_TYPE_NONE: 2411 return "none"; 2412 case C.TRACK_TYPE_UNKNOWN: 2413 return "unknown"; 2414 default: 2415 return trackType >= C.TRACK_TYPE_CUSTOM_BASE ? "custom (" + trackType + ")" : "?"; 2416 } 2417 } 2418 2419 /** 2420 * Returns the current time in milliseconds since the epoch. 2421 * 2422 * @param elapsedRealtimeEpochOffsetMs The offset between {@link SystemClock#elapsedRealtime()} 2423 * and the time since the Unix epoch, or {@link C#TIME_UNSET} if unknown. 2424 * @return The Unix time in milliseconds since the epoch. 2425 */ getNowUnixTimeMs(long elapsedRealtimeEpochOffsetMs)2426 public static long getNowUnixTimeMs(long elapsedRealtimeEpochOffsetMs) { 2427 return elapsedRealtimeEpochOffsetMs == C.TIME_UNSET 2428 ? System.currentTimeMillis() 2429 : SystemClock.elapsedRealtime() + elapsedRealtimeEpochOffsetMs; 2430 } 2431 2432 /** 2433 * Moves the elements starting at {@code fromIndex} to {@code newFromIndex}. 2434 * 2435 * @param items The list of which to move elements. 2436 * @param fromIndex The index at which the items to move start. 2437 * @param toIndex The index up to which elements should be moved (exclusive). 2438 * @param newFromIndex The new from index. 2439 */ moveItems( List<T> items, int fromIndex, int toIndex, int newFromIndex)2440 public static <T extends Object> void moveItems( 2441 List<T> items, int fromIndex, int toIndex, int newFromIndex) { 2442 ArrayDeque<T> removedItems = new ArrayDeque<>(); 2443 int removedItemsLength = toIndex - fromIndex; 2444 for (int i = removedItemsLength - 1; i >= 0; i--) { 2445 removedItems.addFirst(items.remove(fromIndex + i)); 2446 } 2447 items.addAll(min(newFromIndex, items.size()), removedItems); 2448 } 2449 2450 /** Returns whether the table exists in the database. */ tableExists(SQLiteDatabase database, String tableName)2451 public static boolean tableExists(SQLiteDatabase database, String tableName) { 2452 long count = 2453 DatabaseUtils.queryNumEntries( 2454 database, "sqlite_master", "tbl_name = ?", new String[] {tableName}); 2455 return count > 0; 2456 } 2457 2458 /** 2459 * Attempts to parse an error code from a diagnostic string found in framework media exceptions. 2460 * 2461 * <p>For example: android.media.MediaCodec.error_1 or android.media.MediaDrm.error_neg_2. 2462 * 2463 * @param diagnosticsInfo A string from which to parse the error code. 2464 * @return The parser error code, or 0 if an error code could not be parsed. 2465 */ getErrorCodeFromPlatformDiagnosticsInfo(@ullable String diagnosticsInfo)2466 public static int getErrorCodeFromPlatformDiagnosticsInfo(@Nullable String diagnosticsInfo) { 2467 // TODO (internal b/192337376): Change 0 for ERROR_UNKNOWN once available. 2468 if (diagnosticsInfo == null) { 2469 return 0; 2470 } 2471 String[] strings = split(diagnosticsInfo, "_"); 2472 int length = strings.length; 2473 if (length < 2) { 2474 return 0; 2475 } 2476 String digitsSection = strings[length - 1]; 2477 boolean isNegative = length >= 3 && "neg".equals(strings[length - 2]); 2478 try { 2479 int errorCode = Integer.parseInt(Assertions.checkNotNull(digitsSection)); 2480 return isNegative ? -errorCode : errorCode; 2481 } catch (NumberFormatException e) { 2482 return 0; 2483 } 2484 } 2485 2486 /** 2487 * Returns string representation of a {@link C.FormatSupport} flag. 2488 * 2489 * @param formatSupport A {@link C.FormatSupport} flag. 2490 * @return A string representation of the flag. 2491 */ getFormatSupportString(@.FormatSupport int formatSupport)2492 public static String getFormatSupportString(@C.FormatSupport int formatSupport) { 2493 switch (formatSupport) { 2494 case C.FORMAT_HANDLED: 2495 return "YES"; 2496 case C.FORMAT_EXCEEDS_CAPABILITIES: 2497 return "NO_EXCEEDS_CAPABILITIES"; 2498 case C.FORMAT_UNSUPPORTED_DRM: 2499 return "NO_UNSUPPORTED_DRM"; 2500 case C.FORMAT_UNSUPPORTED_SUBTYPE: 2501 return "NO_UNSUPPORTED_TYPE"; 2502 case C.FORMAT_UNSUPPORTED_TYPE: 2503 return "NO"; 2504 default: 2505 throw new IllegalStateException(); 2506 } 2507 } 2508 2509 /** 2510 * Returns the {@link Commands} available in the {@link Player}. 2511 * 2512 * @param player The {@link Player}. 2513 * @param permanentAvailableCommands The commands permanently available in the player. 2514 * @return The available {@link Commands}. 2515 */ getAvailableCommands(Player player, Commands permanentAvailableCommands)2516 public static Commands getAvailableCommands(Player player, Commands permanentAvailableCommands) { 2517 boolean isPlayingAd = player.isPlayingAd(); 2518 boolean isCurrentMediaItemSeekable = player.isCurrentMediaItemSeekable(); 2519 boolean hasPreviousMediaItem = player.hasPreviousMediaItem(); 2520 boolean hasNextMediaItem = player.hasNextMediaItem(); 2521 boolean isCurrentMediaItemLive = player.isCurrentMediaItemLive(); 2522 boolean isCurrentMediaItemDynamic = player.isCurrentMediaItemDynamic(); 2523 boolean isTimelineEmpty = player.getCurrentTimeline().isEmpty(); 2524 return new Commands.Builder() 2525 .addAll(permanentAvailableCommands) 2526 .addIf(COMMAND_SEEK_TO_DEFAULT_POSITION, !isPlayingAd) 2527 .addIf(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, isCurrentMediaItemSeekable && !isPlayingAd) 2528 .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousMediaItem && !isPlayingAd) 2529 .addIf( 2530 COMMAND_SEEK_TO_PREVIOUS, 2531 !isTimelineEmpty 2532 && (hasPreviousMediaItem || !isCurrentMediaItemLive || isCurrentMediaItemSeekable) 2533 && !isPlayingAd) 2534 .addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextMediaItem && !isPlayingAd) 2535 .addIf( 2536 COMMAND_SEEK_TO_NEXT, 2537 !isTimelineEmpty 2538 && (hasNextMediaItem || (isCurrentMediaItemLive && isCurrentMediaItemDynamic)) 2539 && !isPlayingAd) 2540 .addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd) 2541 .addIf(COMMAND_SEEK_BACK, isCurrentMediaItemSeekable && !isPlayingAd) 2542 .addIf(COMMAND_SEEK_FORWARD, isCurrentMediaItemSeekable && !isPlayingAd) 2543 .build(); 2544 } 2545 2546 /** 2547 * Returns the sum of all summands of the given array. 2548 * 2549 * @param summands The summands to calculate the sum from. 2550 * @return The sum of all summands. 2551 */ sum(long... summands)2552 public static long sum(long... summands) { 2553 long sum = 0; 2554 for (long summand : summands) { 2555 sum += summand; 2556 } 2557 return sum; 2558 } 2559 2560 @Nullable getSystemProperty(String name)2561 private static String getSystemProperty(String name) { 2562 try { 2563 @SuppressLint("PrivateApi") 2564 Class<?> systemProperties = Class.forName("android.os.SystemProperties"); 2565 Method getMethod = systemProperties.getMethod("get", String.class); 2566 return (String) getMethod.invoke(systemProperties, name); 2567 } catch (Exception e) { 2568 Log.e(TAG, "Failed to read system property " + name, e); 2569 return null; 2570 } 2571 } 2572 2573 @RequiresApi(23) getDisplaySizeV23(Display display, Point outSize)2574 private static void getDisplaySizeV23(Display display, Point outSize) { 2575 Display.Mode mode = display.getMode(); 2576 outSize.x = mode.getPhysicalWidth(); 2577 outSize.y = mode.getPhysicalHeight(); 2578 } 2579 2580 @RequiresApi(17) getDisplaySizeV17(Display display, Point outSize)2581 private static void getDisplaySizeV17(Display display, Point outSize) { 2582 display.getRealSize(outSize); 2583 } 2584 getDisplaySizeV16(Display display, Point outSize)2585 private static void getDisplaySizeV16(Display display, Point outSize) { 2586 display.getSize(outSize); 2587 } 2588 getSystemLocales()2589 private static String[] getSystemLocales() { 2590 Configuration config = Resources.getSystem().getConfiguration(); 2591 return SDK_INT >= 24 2592 ? getSystemLocalesV24(config) 2593 : new String[] {getLocaleLanguageTag(config.locale)}; 2594 } 2595 2596 @RequiresApi(24) getSystemLocalesV24(Configuration config)2597 private static String[] getSystemLocalesV24(Configuration config) { 2598 return Util.split(config.getLocales().toLanguageTags(), ","); 2599 } 2600 2601 @RequiresApi(21) getLocaleLanguageTagV21(Locale locale)2602 private static String getLocaleLanguageTagV21(Locale locale) { 2603 return locale.toLanguageTag(); 2604 } 2605 createIsoLanguageReplacementMap()2606 private static HashMap<String, String> createIsoLanguageReplacementMap() { 2607 String[] iso2Languages = Locale.getISOLanguages(); 2608 HashMap<String, String> replacedLanguages = 2609 new HashMap<>( 2610 /* initialCapacity= */ iso2Languages.length + additionalIsoLanguageReplacements.length); 2611 for (String iso2 : iso2Languages) { 2612 try { 2613 // This returns the ISO 639-2/T code for the language. 2614 String iso3 = new Locale(iso2).getISO3Language(); 2615 if (!TextUtils.isEmpty(iso3)) { 2616 replacedLanguages.put(iso3, iso2); 2617 } 2618 } catch (MissingResourceException e) { 2619 // Shouldn't happen for list of known languages, but we don't want to throw either. 2620 } 2621 } 2622 // Add additional replacement mappings. 2623 for (int i = 0; i < additionalIsoLanguageReplacements.length; i += 2) { 2624 replacedLanguages.put( 2625 additionalIsoLanguageReplacements[i], additionalIsoLanguageReplacements[i + 1]); 2626 } 2627 return replacedLanguages; 2628 } 2629 2630 @RequiresApi(api = Build.VERSION_CODES.M) requestExternalStoragePermission(Activity activity)2631 private static boolean requestExternalStoragePermission(Activity activity) { 2632 if (activity.checkSelfPermission(permission.READ_EXTERNAL_STORAGE) 2633 != PackageManager.PERMISSION_GRANTED) { 2634 activity.requestPermissions( 2635 new String[] {permission.READ_EXTERNAL_STORAGE}, /* requestCode= */ 0); 2636 return true; 2637 } 2638 return false; 2639 } 2640 2641 @RequiresApi(api = Build.VERSION_CODES.N) isTrafficRestricted(Uri uri)2642 private static boolean isTrafficRestricted(Uri uri) { 2643 return "http".equals(uri.getScheme()) 2644 && !NetworkSecurityPolicy.getInstance() 2645 .isCleartextTrafficPermitted(checkNotNull(uri.getHost())); 2646 } 2647 maybeReplaceLegacyLanguageTags(String languageTag)2648 private static String maybeReplaceLegacyLanguageTags(String languageTag) { 2649 for (int i = 0; i < isoLegacyTagReplacements.length; i += 2) { 2650 if (languageTag.startsWith(isoLegacyTagReplacements[i])) { 2651 return isoLegacyTagReplacements[i + 1] 2652 + languageTag.substring(/* beginIndex= */ isoLegacyTagReplacements[i].length()); 2653 } 2654 } 2655 return languageTag; 2656 } 2657 2658 // Additional mapping from ISO3 to ISO2 language codes. 2659 private static final String[] additionalIsoLanguageReplacements = 2660 new String[] { 2661 // Bibliographical codes defined in ISO 639-2/B, replaced by terminological code defined in 2662 // ISO 639-2/T. See https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes. 2663 "alb", "sq", 2664 "arm", "hy", 2665 "baq", "eu", 2666 "bur", "my", 2667 "tib", "bo", 2668 "chi", "zh", 2669 "cze", "cs", 2670 "dut", "nl", 2671 "ger", "de", 2672 "gre", "el", 2673 "fre", "fr", 2674 "geo", "ka", 2675 "ice", "is", 2676 "mac", "mk", 2677 "mao", "mi", 2678 "may", "ms", 2679 "per", "fa", 2680 "rum", "ro", 2681 "scc", "hbs-srp", 2682 "slo", "sk", 2683 "wel", "cy", 2684 // Deprecated 2-letter codes, replaced by modern equivalent (including macrolanguage) 2685 // See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes, "ISO 639:1988" 2686 "id", "ms-ind", 2687 "iw", "he", 2688 "heb", "he", 2689 "ji", "yi", 2690 // Individual macrolanguage codes mapped back to full macrolanguage code. 2691 // See https://en.wikipedia.org/wiki/ISO_639_macrolanguage 2692 "in", "ms-ind", 2693 "ind", "ms-ind", 2694 "nb", "no-nob", 2695 "nob", "no-nob", 2696 "nn", "no-nno", 2697 "nno", "no-nno", 2698 "tw", "ak-twi", 2699 "twi", "ak-twi", 2700 "bs", "hbs-bos", 2701 "bos", "hbs-bos", 2702 "hr", "hbs-hrv", 2703 "hrv", "hbs-hrv", 2704 "sr", "hbs-srp", 2705 "srp", "hbs-srp", 2706 "cmn", "zh-cmn", 2707 "hak", "zh-hak", 2708 "nan", "zh-nan", 2709 "hsn", "zh-hsn" 2710 }; 2711 2712 // Legacy tags that have been replaced by modern equivalents (including macrolanguage) 2713 // See https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry. 2714 private static final String[] isoLegacyTagReplacements = 2715 new String[] { 2716 "i-lux", "lb", 2717 "i-hak", "zh-hak", 2718 "i-navajo", "nv", 2719 "no-bok", "no-nob", 2720 "no-nyn", "no-nno", 2721 "zh-guoyu", "zh-cmn", 2722 "zh-hakka", "zh-hak", 2723 "zh-min-nan", "zh-nan", 2724 "zh-xiang", "zh-hsn" 2725 }; 2726 2727 /** 2728 * Allows the CRC-32 calculation to be done byte by byte instead of bit per bit in the order "most 2729 * significant bit first". 2730 */ 2731 private static final int[] CRC32_BYTES_MSBF = { 2732 0X00000000, 0X04C11DB7, 0X09823B6E, 0X0D4326D9, 0X130476DC, 0X17C56B6B, 0X1A864DB2, 2733 0X1E475005, 0X2608EDB8, 0X22C9F00F, 0X2F8AD6D6, 0X2B4BCB61, 0X350C9B64, 0X31CD86D3, 2734 0X3C8EA00A, 0X384FBDBD, 0X4C11DB70, 0X48D0C6C7, 0X4593E01E, 0X4152FDA9, 0X5F15ADAC, 2735 0X5BD4B01B, 0X569796C2, 0X52568B75, 0X6A1936C8, 0X6ED82B7F, 0X639B0DA6, 0X675A1011, 2736 0X791D4014, 0X7DDC5DA3, 0X709F7B7A, 0X745E66CD, 0X9823B6E0, 0X9CE2AB57, 0X91A18D8E, 2737 0X95609039, 0X8B27C03C, 0X8FE6DD8B, 0X82A5FB52, 0X8664E6E5, 0XBE2B5B58, 0XBAEA46EF, 2738 0XB7A96036, 0XB3687D81, 0XAD2F2D84, 0XA9EE3033, 0XA4AD16EA, 0XA06C0B5D, 0XD4326D90, 2739 0XD0F37027, 0XDDB056FE, 0XD9714B49, 0XC7361B4C, 0XC3F706FB, 0XCEB42022, 0XCA753D95, 2740 0XF23A8028, 0XF6FB9D9F, 0XFBB8BB46, 0XFF79A6F1, 0XE13EF6F4, 0XE5FFEB43, 0XE8BCCD9A, 2741 0XEC7DD02D, 0X34867077, 0X30476DC0, 0X3D044B19, 0X39C556AE, 0X278206AB, 0X23431B1C, 2742 0X2E003DC5, 0X2AC12072, 0X128E9DCF, 0X164F8078, 0X1B0CA6A1, 0X1FCDBB16, 0X018AEB13, 2743 0X054BF6A4, 0X0808D07D, 0X0CC9CDCA, 0X7897AB07, 0X7C56B6B0, 0X71159069, 0X75D48DDE, 2744 0X6B93DDDB, 0X6F52C06C, 0X6211E6B5, 0X66D0FB02, 0X5E9F46BF, 0X5A5E5B08, 0X571D7DD1, 2745 0X53DC6066, 0X4D9B3063, 0X495A2DD4, 0X44190B0D, 0X40D816BA, 0XACA5C697, 0XA864DB20, 2746 0XA527FDF9, 0XA1E6E04E, 0XBFA1B04B, 0XBB60ADFC, 0XB6238B25, 0XB2E29692, 0X8AAD2B2F, 2747 0X8E6C3698, 0X832F1041, 0X87EE0DF6, 0X99A95DF3, 0X9D684044, 0X902B669D, 0X94EA7B2A, 2748 0XE0B41DE7, 0XE4750050, 0XE9362689, 0XEDF73B3E, 0XF3B06B3B, 0XF771768C, 0XFA325055, 2749 0XFEF34DE2, 0XC6BCF05F, 0XC27DEDE8, 0XCF3ECB31, 0XCBFFD686, 0XD5B88683, 0XD1799B34, 2750 0XDC3ABDED, 0XD8FBA05A, 0X690CE0EE, 0X6DCDFD59, 0X608EDB80, 0X644FC637, 0X7A089632, 2751 0X7EC98B85, 0X738AAD5C, 0X774BB0EB, 0X4F040D56, 0X4BC510E1, 0X46863638, 0X42472B8F, 2752 0X5C007B8A, 0X58C1663D, 0X558240E4, 0X51435D53, 0X251D3B9E, 0X21DC2629, 0X2C9F00F0, 2753 0X285E1D47, 0X36194D42, 0X32D850F5, 0X3F9B762C, 0X3B5A6B9B, 0X0315D626, 0X07D4CB91, 2754 0X0A97ED48, 0X0E56F0FF, 0X1011A0FA, 0X14D0BD4D, 0X19939B94, 0X1D528623, 0XF12F560E, 2755 0XF5EE4BB9, 0XF8AD6D60, 0XFC6C70D7, 0XE22B20D2, 0XE6EA3D65, 0XEBA91BBC, 0XEF68060B, 2756 0XD727BBB6, 0XD3E6A601, 0XDEA580D8, 0XDA649D6F, 0XC423CD6A, 0XC0E2D0DD, 0XCDA1F604, 2757 0XC960EBB3, 0XBD3E8D7E, 0XB9FF90C9, 0XB4BCB610, 0XB07DABA7, 0XAE3AFBA2, 0XAAFBE615, 2758 0XA7B8C0CC, 0XA379DD7B, 0X9B3660C6, 0X9FF77D71, 0X92B45BA8, 0X9675461F, 0X8832161A, 2759 0X8CF30BAD, 0X81B02D74, 0X857130C3, 0X5D8A9099, 0X594B8D2E, 0X5408ABF7, 0X50C9B640, 2760 0X4E8EE645, 0X4A4FFBF2, 0X470CDD2B, 0X43CDC09C, 0X7B827D21, 0X7F436096, 0X7200464F, 2761 0X76C15BF8, 0X68860BFD, 0X6C47164A, 0X61043093, 0X65C52D24, 0X119B4BE9, 0X155A565E, 2762 0X18197087, 0X1CD86D30, 0X029F3D35, 0X065E2082, 0X0B1D065B, 0X0FDC1BEC, 0X3793A651, 2763 0X3352BBE6, 0X3E119D3F, 0X3AD08088, 0X2497D08D, 0X2056CD3A, 0X2D15EBE3, 0X29D4F654, 2764 0XC5A92679, 0XC1683BCE, 0XCC2B1D17, 0XC8EA00A0, 0XD6AD50A5, 0XD26C4D12, 0XDF2F6BCB, 2765 0XDBEE767C, 0XE3A1CBC1, 0XE760D676, 0XEA23F0AF, 0XEEE2ED18, 0XF0A5BD1D, 0XF464A0AA, 2766 0XF9278673, 0XFDE69BC4, 0X89B8FD09, 0X8D79E0BE, 0X803AC667, 0X84FBDBD0, 0X9ABC8BD5, 2767 0X9E7D9662, 0X933EB0BB, 0X97FFAD0C, 0XAFB010B1, 0XAB710D06, 0XA6322BDF, 0XA2F33668, 2768 0XBCB4666D, 0XB8757BDA, 0XB5365D03, 0XB1F740B4 2769 }; 2770 2771 /** 2772 * Allows the CRC-8 calculation to be done byte by byte instead of bit per bit in the order "most 2773 * significant bit first". 2774 */ 2775 private static final int[] CRC8_BYTES_MSBF = { 2776 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 2777 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 2778 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 2779 0xC3, 0xCA, 0xCD, 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 2780 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 2781 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 2782 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 2783 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 2784 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 2785 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 2786 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 2787 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, 2788 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, 0x4E, 0x49, 0x40, 2789 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 2790 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 2791 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 2792 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 2793 0xF3 2794 }; 2795 } 2796