diff --git a/library/common/build.gradle b/library/common/build.gradle index 01bda6fb9b..d1d0d86f42 100644 --- a/library/common/build.gradle +++ b/library/common/build.gradle @@ -26,7 +26,6 @@ dependencies { exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations' } implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion - implementation 'androidx.core:core:' + androidxCoreVersion compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 6682a33959..477b082673 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -25,9 +25,9 @@ import android.os.SystemClock; import android.util.Pair; import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import androidx.core.app.BundleCompat; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.BundleUtil; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import java.lang.annotation.Documented; @@ -1301,9 +1301,9 @@ public abstract class Timeline implements Bundleable { } Bundle bundle = new Bundle(); - BundleCompat.putBinder( + BundleUtil.putBinder( bundle, keyForField(FIELD_WINDOWS), new BundleListRetriever(windowBundles)); - BundleCompat.putBinder( + BundleUtil.putBinder( bundle, keyForField(FIELD_PERIODS), new BundleListRetriever(periodBundles)); bundle.putIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES), shuffledWindowIndices); return bundle; @@ -1321,10 +1321,10 @@ public abstract class Timeline implements Bundleable { private static Timeline fromBundle(Bundle bundle) { ImmutableList windows = fromBundleListRetriever( - Window.CREATOR, BundleCompat.getBinder(bundle, keyForField(FIELD_WINDOWS))); + Window.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_WINDOWS))); ImmutableList periods = fromBundleListRetriever( - Period.CREATOR, BundleCompat.getBinder(bundle, keyForField(FIELD_PERIODS))); + Period.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_PERIODS))); @Nullable int[] shuffledWindowIndices = bundle.getIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES)); return new RemotableTimeline( diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/BundleUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/BundleUtil.java new file mode 100644 index 0000000000..1c1e139d80 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/BundleUtil.java @@ -0,0 +1,111 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import android.os.Bundle; +import android.os.IBinder; +import androidx.annotation.Nullable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** Utilities for {@link Bundle}. */ +public final class BundleUtil { + + private static final String TAG = "BundleUtil"; + + @Nullable private static Method getIBinderMethod; + @Nullable private static Method putIBinderMethod; + + /** + * Gets an {@link IBinder} inside a {@link Bundle} for all Android versions. + * + * @param bundle The bundle to get the {@link IBinder}. + * @param key The key to use while getting the {@link IBinder}. + * @return The {@link IBinder} that was obtained. + */ + @Nullable + public static IBinder getBinder(Bundle bundle, @Nullable String key) { + if (Util.SDK_INT >= 18) { + return bundle.getBinder(key); + } else { + return getBinderByReflection(bundle, key); + } + } + + /** + * Puts an {@link IBinder} inside a {@link Bundle} for all Android versions. + * + * @param bundle The bundle to insert the {@link IBinder}. + * @param key The key to use while putting the {@link IBinder}. + * @param binder The {@link IBinder} to put. + */ + public static void putBinder(Bundle bundle, @Nullable String key, @Nullable IBinder binder) { + if (Util.SDK_INT >= 18) { + bundle.putBinder(key, binder); + } else { + putBinderByReflection(bundle, key, binder); + } + } + + // Method.invoke may take null "key". + @SuppressWarnings("nullness:argument.type.incompatible") + @Nullable + private static IBinder getBinderByReflection(Bundle bundle, @Nullable String key) { + @Nullable Method getIBinder = getIBinderMethod; + if (getIBinder == null) { + try { + getIBinderMethod = Bundle.class.getMethod("getIBinder", String.class); + getIBinderMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + Log.i(TAG, "Failed to retrieve getIBinder method", e); + return null; + } + getIBinder = getIBinderMethod; + } + + try { + return (IBinder) getIBinder.invoke(bundle, key); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + Log.i(TAG, "Failed to invoke getIBinder via reflection", e); + return null; + } + } + + // Method.invoke may take null "key" and "binder". + @SuppressWarnings("nullness:argument.type.incompatible") + private static void putBinderByReflection( + Bundle bundle, @Nullable String key, @Nullable IBinder binder) { + @Nullable Method putIBinder = putIBinderMethod; + if (putIBinder == null) { + try { + putIBinderMethod = Bundle.class.getMethod("putIBinder", String.class, IBinder.class); + putIBinderMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + Log.i(TAG, "Failed to retrieve putIBinder method", e); + return; + } + putIBinder = putIBinderMethod; + } + + try { + putIBinder.invoke(bundle, key, binder); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + Log.i(TAG, "Failed to invoke putIBinder via reflection", e); + } + } + + private BundleUtil() {} +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/BundleUtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/BundleUtilTest.java new file mode 100644 index 0000000000..b7cbe5c77e --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/BundleUtilTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link BundleUtil}. */ +@RunWith(AndroidJUnit4.class) +public class BundleUtilTest { + + @Test + public void getPutBinder() { + String key = "key"; + IBinder binder = new Binder(); + Bundle bundle = new Bundle(); + + BundleUtil.putBinder(bundle, key, binder); + IBinder returnedBinder = BundleUtil.getBinder(bundle, key); + + assertThat(returnedBinder).isSameInstanceAs(binder); + } +}