From 953db7898eed66684ce84052808fa1bd650fbda5 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 20 Jul 2020 15:47:34 +0100 Subject: [PATCH] Make dependency on AndroidX appcompat optional for dialog builder. The dependency is only used to create a dialog in TrackSelectionDialogBuilder that is compatible with newer styling options. This dependendy adds over 500Kb to the apk (even if unused) and we shoudn't force this on an app. Instead make the dependency optional by automatically falling back to the platform version if the AndroidX one doesn't exist. Issue: #7357 PiperOrigin-RevId: 322143005 --- library/ui/build.gradle | 1 - library/ui/proguard-rules.txt | 18 +++++ .../ui/TrackSelectionDialogBuilder.java | 79 +++++++++++++++---- library/ui/src/main/proguard-rules.txt | 1 + 4 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 library/ui/proguard-rules.txt create mode 120000 library/ui/src/main/proguard-rules.txt diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 5b24cfbc62..3825a15d92 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 0000000000..9bfde914b2 --- /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 5c91645a4c..30098054ef 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 final class 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,24 +208,18 @@ public final class TrackSelectionDialogBuilder { } /** 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); - - TrackSelectionView selectionView = dialogView.findViewById(R.id.exo_track_selection_view); - selectionView.setAllowMultipleOverrides(allowMultipleOverrides); - selectionView.setAllowAdaptiveSelections(allowAdaptiveSelections); - selectionView.setShowDisableOption(showDisableOption); - if (trackNameProvider != null) { - selectionView.setTrackNameProvider(trackNameProvider); - } - selectionView.init(mappedTrackInfo, rendererIndex, isDisabled, overrides, /* listener= */ null); - Dialog.OnClickListener okClickListener = - (dialog, which) -> - callback.onTracksSelected(selectionView.getIsDisabled(), selectionView.getOverrides()); + Dialog.OnClickListener okClickListener = setUpDialogView(dialogView); return builder .setTitle(title) @@ -231,4 +228,54 @@ public final class TrackSelectionDialogBuilder { .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); + selectionView.setShowDisableOption(showDisableOption); + if (trackNameProvider != null) { + selectionView.setTrackNameProvider(trackNameProvider); + } + selectionView.init(mappedTrackInfo, rendererIndex, isDisabled, overrides, /* listener= */ null); + 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 0000000000..499fb08b36 --- /dev/null +++ b/library/ui/src/main/proguard-rules.txt @@ -0,0 +1 @@ +../../proguard-rules.txt \ No newline at end of file