Replace Util.toLowerInvariant() with Ascii.toLowerCase()

Even when fixed to the US locale (and thus avoiding surprising behaviour
in e.g. Turkish locale with "i" and "I") there are unexpected behaviours
when upper and lower casing non-ASCII characters.

For example it's sometimes not symmetric, e.g.:
"ẞ".toLowerCase() -> "ß"
"ß".toUpperCase() -> "SS"

In all the ExoPlayer usages we are either dealing with known-ASCII
strings (e.g. MIME types) or comparing against ASCII constant strings
anyway, so it seems easier to just use Guava's ASCII-only class in these
cases.

This change also includes some null-twiddling, because
Util.toLowerInvariant() is null tolerant, while Ascii.toLowerCase() is
not. Most of the usages were already non-null, and it was easy enough to
change the remaining ones to be so by simple reordering of statements.

I'll make an equivalent change for Util.toUpperInvariant() next.

PiperOrigin-RevId: 368419813
This commit is contained in:
ibaker 2021-04-14 14:44:29 +01:00 committed by Andrew Lewis
parent 40d3e12853
commit c50084e7ba
19 changed files with 55 additions and 51 deletions

View file

@ -35,6 +35,7 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders;
import com.google.common.primitives.Ints;
@ -546,8 +547,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
@Nullable IOException connectionOpenException = exception;
if (connectionOpenException != null) {
@Nullable String message = connectionOpenException.getMessage();
if (message != null
&& Util.toLowerInvariant(message).contains("err_cleartext_not_permitted")) {
if (message != null && Ascii.toLowerCase(message).contains("err_cleartext_not_permitted")) {
throw new CleartextNotPermittedException(connectionOpenException, dataSpec);
}
throw new OpenException(connectionOpenException, dataSpec, getStatus(urlRequest));

View file

@ -32,6 +32,7 @@ import com.google.android.exoplayer2.upstream.HttpUtil;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders;
import java.io.IOException;
@ -289,7 +290,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
} catch (IOException e) {
@Nullable String message = e.getMessage();
if (message != null
&& Util.toLowerInvariant(message).matches("cleartext communication.*not permitted.*")) {
&& Ascii.toLowerCase(message).matches("cleartext communication.*not permitted.*")) {
throw new CleartextNotPermittedException(e, dataSpec);
}
throw new HttpDataSourceException(

View file

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -539,13 +540,13 @@ public final class Id3Decoder extends SimpleMetadataDecoder {
int mimeTypeEndIndex;
if (majorVersion == 2) {
mimeTypeEndIndex = 2;
mimeType = "image/" + Util.toLowerInvariant(new String(data, 0, 3, "ISO-8859-1"));
mimeType = "image/" + Ascii.toLowerCase(new String(data, 0, 3, "ISO-8859-1"));
if ("image/jpg".equals(mimeType)) {
mimeType = "image/jpeg";
}
} else {
mimeTypeEndIndex = indexOfZeroByte(data, 0);
mimeType = Util.toLowerInvariant(new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"));
mimeType = Ascii.toLowerCase(new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"));
if (mimeType.indexOf('/') == -1) {
mimeType = "image/" + mimeType;
}

View file

@ -27,6 +27,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders;
import java.io.IOException;
@ -339,7 +340,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
} catch (IOException e) {
@Nullable String message = e.getMessage();
if (message != null
&& Util.toLowerInvariant(message).matches("cleartext http traffic.*not permitted.*")) {
&& Ascii.toLowerCase(message).matches("cleartext http traffic.*not permitted.*")) {
throw new CleartextNotPermittedException(e, dataSpec);
}
throw new HttpDataSourceException(

View file

@ -19,6 +19,7 @@ import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import com.google.common.base.Predicate;
import java.io.IOException;
import java.lang.annotation.Documented;
@ -182,7 +183,10 @@ public interface HttpDataSource extends DataSource {
/** A {@link Predicate} that rejects content types often used for pay-walls. */
Predicate<String> REJECT_PAYWALL_TYPES =
contentType -> {
contentType = Util.toLowerInvariant(contentType);
if (contentType == null) {
return false;
}
contentType = Ascii.toLowerCase(contentType);
return !TextUtils.isEmpty(contentType)
&& (!contentType.contains("text") || contentType.contains("text/vtt"))
&& !contentType.contains("html")

View file

@ -20,6 +20,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.audio.AacUtil;
import com.google.common.base.Ascii;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -340,7 +341,7 @@ public final class MimeTypes {
if (codec == null) {
return null;
}
codec = Util.toLowerInvariant(codec.trim());
codec = Ascii.toLowerCase(codec.trim());
if (codec.startsWith("avc1") || codec.startsWith("avc3")) {
return MimeTypes.VIDEO_H264;
} else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) {

View file

@ -674,7 +674,7 @@ public final class Util {
// Tag isn't valid, keep using the original.
normalizedTag = language;
}
normalizedTag = Util.toLowerInvariant(normalizedTag);
normalizedTag = Ascii.toLowerCase(normalizedTag);
String mainLanguage = Util.splitAtFirst(normalizedTag, "-")[0];
if (languageTagReplacementMap == null) {
languageTagReplacementMap = createIsoLanguageReplacementMap();
@ -760,16 +760,6 @@ public final class Util {
return c == '\n' || c == '\r';
}
/**
* Converts text to lower case using {@link Locale#US}.
*
* @param text The text to convert.
* @return The lower case text, or null if {@code text} is null.
*/
public static @PolyNull String toLowerInvariant(@PolyNull String text) {
return text == null ? text : text.toLowerCase(Locale.US);
}
/**
* Converts text to upper case using {@link Locale#US}.
*
@ -1784,7 +1774,7 @@ public final class Util {
* @return The derived {@link UUID}, or {@code null} if one could not be derived.
*/
public static @Nullable UUID getDrmUuid(String drmScheme) {
switch (toLowerInvariant(drmScheme)) {
switch (Ascii.toLowerCase(drmScheme)) {
case "widevine":
return C.WIDEVINE_UUID;
case "playready":
@ -1834,7 +1824,7 @@ public final class Util {
*/
@ContentType
public static int inferContentType(String fileName) {
fileName = toLowerInvariant(fileName);
fileName = Ascii.toLowerCase(fileName);
if (fileName.endsWith(".mpd")) {
return C.TYPE_DASH;
} else if (fileName.endsWith(".m3u8")) {
@ -1907,11 +1897,11 @@ public final class Util {
* @return The fixed URI.
*/
public static Uri fixSmoothStreamingIsmManifestUri(Uri uri) {
@Nullable String path = toLowerInvariant(uri.getPath());
@Nullable String path = uri.getPath();
if (path == null) {
return uri;
}
Matcher ismMatcher = ISM_URL_PATTERN.matcher(path);
Matcher ismMatcher = ISM_URL_PATTERN.matcher(Ascii.toLowerCase(path));
if (ismMatcher.matches() && ismMatcher.group(1) == null) {
// Add missing "Manifest" suffix.
return Uri.withAppendedPath(uri, "Manifest");

View file

@ -30,6 +30,7 @@ import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
@ -303,7 +304,7 @@ class AsynchronousMediaCodecBufferEnqueuer {
* buffers (see [Internal: b/149908061]).
*/
private static boolean needsSynchronizationWorkaround() {
String manufacturer = Util.toLowerInvariant(Util.MANUFACTURER);
String manufacturer = Ascii.toLowerCase(Util.MANUFACTURER);
return manufacturer.contains("samsung") || manufacturer.contains("motorola");
}

View file

@ -33,6 +33,7 @@ import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.common.base.Ascii;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -624,7 +625,7 @@ public final class MediaCodecUtil {
if (Util.SDK_INT >= 29) {
return isSoftwareOnlyV29(codecInfo);
}
String codecName = Util.toLowerInvariant(codecInfo.getName());
String codecName = Ascii.toLowerCase(codecInfo.getName());
if (codecName.startsWith("arc.")) { // App Runtime for Chrome (ARC) codecs
return false;
}
@ -650,7 +651,7 @@ public final class MediaCodecUtil {
if (Util.SDK_INT >= 29) {
return isVendorV29(codecInfo);
}
String codecName = Util.toLowerInvariant(codecInfo.getName());
String codecName = Ascii.toLowerCase(codecInfo.getName());
return !codecName.startsWith("omx.google.")
&& !codecName.startsWith("c2.android.")
&& !codecName.startsWith("c2.google.");

View file

@ -19,7 +19,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import com.google.common.base.Charsets;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
@ -57,10 +57,10 @@ public final class IcyDecoder extends SimpleMetadataDecoder {
int index = 0;
Matcher matcher = METADATA_ELEMENT.matcher(icyString);
while (matcher.find(index)) {
@Nullable String key = Util.toLowerInvariant(matcher.group(1));
@Nullable String key = matcher.group(1);
@Nullable String value = matcher.group(2);
if (key != null) {
switch (key) {
switch (Ascii.toLowerCase(key)) {
case STREAM_KEY_NAME:
name = value;
break;

View file

@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@ -154,7 +155,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
if (infoNameAndValue.length != 2) {
continue;
}
switch (Util.toLowerInvariant(infoNameAndValue[0].trim())) {
switch (Ascii.toLowerCase(infoNameAndValue[0].trim())) {
case "playresx":
try {
screenWidth = Float.parseFloat(infoNameAndValue[1].trim());

View file

@ -22,7 +22,7 @@ import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
/**
* Represents a {@code Format:} line from the {@code [Events]} section
@ -61,7 +61,7 @@ import com.google.android.exoplayer2.util.Util;
Assertions.checkArgument(formatLine.startsWith(FORMAT_LINE_PREFIX));
String[] keys = TextUtils.split(formatLine.substring(FORMAT_LINE_PREFIX.length()), ",");
for (int i = 0; i < keys.length; i++) {
switch (Util.toLowerInvariant(keys[i].trim())) {
switch (Ascii.toLowerCase(keys[i].trim())) {
case "start":
startTimeIndex = i;
break;

View file

@ -31,6 +31,7 @@ import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import com.google.common.primitives.Ints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -284,7 +285,7 @@ import java.util.regex.Pattern;
String[] keys =
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
for (int i = 0; i < keys.length; i++) {
switch (Util.toLowerInvariant(keys[i].trim())) {
switch (Ascii.toLowerCase(keys[i].trim())) {
case "name":
nameIndex = i;
break;

View file

@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.span.TextAnnotation;
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@ -131,7 +132,7 @@ import java.util.regex.Pattern;
return null;
}
String parsingValue = value.trim();
String parsingValue = Ascii.toLowerCase(value.trim());
if (parsingValue.isEmpty()) {
return null;
}

View file

@ -31,6 +31,7 @@ import com.google.android.exoplayer2.util.ColorParser;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.util.XmlPullParserUtil;
import com.google.common.base.Ascii;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayDeque;
@ -432,7 +433,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
String displayAlign =
XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_DISPLAY_ALIGN);
if (displayAlign != null) {
switch (Util.toLowerInvariant(displayAlign)) {
switch (Ascii.toLowerCase(displayAlign)) {
case "center":
lineAnchor = Cue.ANCHOR_TYPE_MIDDLE;
line += height / 2;
@ -454,7 +455,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
String writingDirection =
XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_WRITING_MODE);
if (writingDirection != null) {
switch (Util.toLowerInvariant(writingDirection)) {
switch (Ascii.toLowerCase(writingDirection)) {
// TODO: Support horizontal RTL modes.
case TtmlNode.VERTICAL:
case TtmlNode.VERTICAL_LR:
@ -533,7 +534,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
TtmlNode.ITALIC.equalsIgnoreCase(attributeValue));
break;
case TtmlNode.ATTR_TTS_TEXT_ALIGN:
switch (Util.toLowerInvariant(attributeValue)) {
switch (Ascii.toLowerCase(attributeValue)) {
case TtmlNode.LEFT:
case TtmlNode.START:
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL);
@ -551,7 +552,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
break;
case TtmlNode.ATTR_TTS_TEXT_COMBINE:
switch (Util.toLowerInvariant(attributeValue)) {
switch (Ascii.toLowerCase(attributeValue)) {
case TtmlNode.COMBINE_NONE:
style = createIfNull(style).setTextCombine(false);
break;
@ -564,7 +565,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
break;
case TtmlNode.ATTR_TTS_RUBY:
switch (Util.toLowerInvariant(attributeValue)) {
switch (Ascii.toLowerCase(attributeValue)) {
case TtmlNode.RUBY_CONTAINER:
style = createIfNull(style).setRubyType(TtmlStyle.RUBY_TYPE_CONTAINER);
break;
@ -585,7 +586,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
break;
case TtmlNode.ATTR_TTS_RUBY_POSITION:
switch (Util.toLowerInvariant(attributeValue)) {
switch (Ascii.toLowerCase(attributeValue)) {
case TtmlNode.ANNOTATION_POSITION_BEFORE:
style = createIfNull(style).setRubyPosition(TextAnnotation.POSITION_BEFORE);
break;
@ -598,7 +599,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
break;
case TtmlNode.ATTR_TTS_TEXT_DECORATION:
switch (Util.toLowerInvariant(attributeValue)) {
switch (Ascii.toLowerCase(attributeValue)) {
case TtmlNode.LINETHROUGH:
style = createIfNull(style).setLinethrough(true);
break;
@ -614,9 +615,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
break;
case TtmlNode.ATTR_TTS_TEXT_EMPHASIS:
style =
createIfNull(style)
.setTextEmphasis(TextEmphasis.parse(Util.toLowerInvariant(attributeValue)));
style = createIfNull(style).setTextEmphasis(TextEmphasis.parse(attributeValue));
break;
case TtmlNode.ATTR_TTS_SHEAR:
style = createIfNull(style).setShearPercentage(parseShear(attributeValue));

View file

@ -21,7 +21,7 @@ import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.text.span.TextAnnotation;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -216,7 +216,7 @@ public final class WebvttCssStyle {
}
public WebvttCssStyle setFontFamily(@Nullable String fontFamily) {
this.fontFamily = Util.toLowerInvariant(fontFamily);
this.fontFamily = fontFamily == null ? null : Ascii.toLowerCase(fontFamily);
return this;
}

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util;
import android.graphics.Color;
import android.text.TextUtils;
import androidx.annotation.ColorInt;
import com.google.common.base.Ascii;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@ -106,7 +107,7 @@ public final class ColorParser {
}
} else {
// we use our own color map
Integer color = COLOR_MAP.get(Util.toLowerInvariant(colorExpression));
Integer color = COLOR_MAP.get(Ascii.toLowerCase(colorExpression));
if (color != null) {
return color;
}

View file

@ -549,7 +549,7 @@ public class DashManifestParser extends DefaultHandler
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
if (schemeIdUri != null) {
switch (Util.toLowerInvariant(schemeIdUri)) {
switch (Ascii.toLowerCase(schemeIdUri)) {
case "urn:mpeg:dash:mp4protection:2011":
schemeType = xpp.getAttributeValue(null, "value");
String defaultKid = XmlPullParserUtil.getAttributeValueIgnorePrefix(xpp, "default_KID");
@ -1798,11 +1798,11 @@ public class DashManifestParser extends DefaultHandler
* not be parsed.
*/
protected static int parseDolbyChannelConfiguration(XmlPullParser xpp) {
String value = Util.toLowerInvariant(xpp.getAttributeValue(null, "value"));
@Nullable String value = xpp.getAttributeValue(null, "value");
if (value == null) {
return Format.NO_VALUE;
}
switch (value) {
switch (Ascii.toLowerCase(value)) {
case "4000":
return 1;
case "a000":

View file

@ -36,6 +36,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import java.io.EOFException;
import java.io.IOException;
@ -540,7 +541,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private static byte[] getEncryptionIvArray(String ivString) {
String trimmedIv;
if (Util.toLowerInvariant(ivString).startsWith("0x")) {
if (Ascii.toLowerCase(ivString).startsWith("0x")) {
trimmedIv = ivString.substring(2);
} else {
trimmedIv = ivString;