• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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} &lt; 0, {@code toIndex} &gt; {@code
306    *     list.size()}, or {@code fromIndex} &gt; {@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 &ge; 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 &lt; right, or a positive value if left
1021    *     &gt; 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 (&gt; 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    * &lt;, &gt;, :, ", /, \, |, ?, 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