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