diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 5b24cfbc626..3825a15d927 100644 --- a/library/ui/build.gradle +++ b/library/ui/build.gradle @@ -19,7 +19,6 @@ dependencies { implementation project(modulePrefix + 'library-core') api 'androidx.media:media:' + androidxMediaVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion - implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion implementation 'com.google.guava:guava:' + guavaVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion diff --git a/library/ui/proguard-rules.txt b/library/ui/proguard-rules.txt new file mode 100644 index 00000000000..9bfde914b29 --- /dev/null +++ b/library/ui/proguard-rules.txt @@ -0,0 +1,18 @@ +# Proguard rules specific to the UI module. + +# Constructor method accessed via reflection in TrackSelectionDialogBuilder +-dontnote androidx.appcompat.app.AlertDialog.Builder +-keepclassmembers class androidx.appcompat.app.AlertDialog$Builder { + (android.content.Context); + public android.content.Context getContext(); + public androidx.appcompat.app.AlertDialog$Builder setTitle(java.lang.CharSequence); + public androidx.appcompat.app.AlertDialog$Builder setView(android.view.View); + public androidx.appcompat.app.AlertDialog$Builder setPositiveButton(int, android.content.DialogInterface$OnClickListener); + public androidx.appcompat.app.AlertDialog$Builder setNegativeButton(int, android.content.DialogInterface$OnClickListener); + public androidx.appcompat.app.AlertDialog create(); +} + +# Don't warn about checkerframework and Kotlin annotations +-dontwarn org.checkerframework.** +-dontwarn kotlin.annotations.jvm.** +-dontwarn javax.annotation.** diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java index 5c91645a4ca..30098054ef7 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java @@ -15,18 +15,21 @@ */ package com.google.android.exoplayer2.ui; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + +import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; +import android.content.DialogInterface; import android.view.LayoutInflater; import android.view.View; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelectionUtil; -import com.google.android.exoplayer2.util.Assertions; +import java.lang.reflect.Constructor; import java.util.Collections; import java.util.List; @@ -97,7 +100,7 @@ public TrackSelectionDialogBuilder( Context context, CharSequence title, DefaultTrackSelector trackSelector, int rendererIndex) { this.context = context; this.title = title; - this.mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo()); + this.mappedTrackInfo = checkNotNull(trackSelector.getCurrentMappedTrackInfo()); this.rendererIndex = rendererIndex; TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex); @@ -205,13 +208,65 @@ public TrackSelectionDialogBuilder setTrackNameProvider( } /** Builds the dialog. */ - public AlertDialog build() { + public Dialog build() { + @Nullable Dialog dialog = buildForAndroidX(); + return dialog == null ? buildForPlatform() : dialog; + } + + private Dialog buildForPlatform() { AlertDialog.Builder builder = new AlertDialog.Builder(context); // Inflate with the builder's context to ensure the correct style is used. LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); View dialogView = dialogInflater.inflate(R.layout.exo_track_selection_dialog, /* root= */ null); + Dialog.OnClickListener okClickListener = setUpDialogView(dialogView); + + return builder + .setTitle(title) + .setView(dialogView) + .setPositiveButton(android.R.string.ok, okClickListener) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } + + // Reflection calls can't verify null safety of return values or parameters. + @SuppressWarnings("nullness:argument.type.incompatible") + @Nullable + private Dialog buildForAndroidX() { + try { + // This method uses reflection to avoid a dependency on AndroidX appcompat that adds 800KB to + // the APK size even with shrinking. See https://issuetracker.google.com/161514204. + // LINT.IfChange + Class builderClazz = Class.forName("androidx.appcompat.app.AlertDialog$Builder"); + Constructor builderConstructor = builderClazz.getConstructor(Context.class); + Object builder = builderConstructor.newInstance(context); + // Inflate with the builder's context to ensure the correct style is used. + Context builderContext = (Context) builderClazz.getMethod("getContext").invoke(builder); + LayoutInflater dialogInflater = LayoutInflater.from(builderContext); + View dialogView = + dialogInflater.inflate(R.layout.exo_track_selection_dialog, /* root= */ null); + Dialog.OnClickListener okClickListener = setUpDialogView(dialogView); + + builderClazz.getMethod("setTitle", CharSequence.class).invoke(builder, title); + builderClazz.getMethod("setView", View.class).invoke(builder, dialogView); + builderClazz + .getMethod("setPositiveButton", int.class, DialogInterface.OnClickListener.class) + .invoke(builder, android.R.string.ok, okClickListener); + builderClazz + .getMethod("setNegativeButton", int.class, DialogInterface.OnClickListener.class) + .invoke(builder, android.R.string.cancel, null); + return (Dialog) builderClazz.getMethod("create").invoke(builder); + // LINT.ThenChange(../../../../../../../../proguard-rules.txt) + } catch (ClassNotFoundException e) { + // Expected if the AndroidX compat library is not available. + return null; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private Dialog.OnClickListener setUpDialogView(View dialogView) { TrackSelectionView selectionView = dialogView.findViewById(R.id.exo_track_selection_view); selectionView.setAllowMultipleOverrides(allowMultipleOverrides); selectionView.setAllowAdaptiveSelections(allowAdaptiveSelections); @@ -220,15 +275,7 @@ public AlertDialog build() { selectionView.setTrackNameProvider(trackNameProvider); } selectionView.init(mappedTrackInfo, rendererIndex, isDisabled, overrides, /* listener= */ null); - Dialog.OnClickListener okClickListener = - (dialog, which) -> - callback.onTracksSelected(selectionView.getIsDisabled(), selectionView.getOverrides()); - - return builder - .setTitle(title) - .setView(dialogView) - .setPositiveButton(android.R.string.ok, okClickListener) - .setNegativeButton(android.R.string.cancel, null) - .create(); + return (dialog, which) -> + callback.onTracksSelected(selectionView.getIsDisabled(), selectionView.getOverrides()); } } diff --git a/library/ui/src/main/proguard-rules.txt b/library/ui/src/main/proguard-rules.txt new file mode 120000 index 00000000000..499fb08b364 --- /dev/null +++ b/library/ui/src/main/proguard-rules.txt @@ -0,0 +1 @@ +../../proguard-rules.txt \ No newline at end of file