From 992c9aa470669f868cf92057271e43533dbe35f0 Mon Sep 17 00:00:00 2001 From: jbibik Date: Sat, 20 May 2023 18:57:22 +0100 Subject: [PATCH] Make DrmConfiguration Bundleable PiperOrigin-RevId: 533721679 (cherry picked from commit 5008417c8cd12680a3945945e00399cca05915c5) --- .../androidx/media3/common/MediaItem.java | 81 +++++++++++++++- .../media3/common/util/BundleableUtil.java | 44 +++++++++ .../androidx/media3/common/MediaItemTest.java | 19 ++++ .../common/util/BundleableUtilTest.java | 96 +++++++++++++++++++ 4 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 libraries/common/src/test/java/androidx/media3/common/util/BundleableUtilTest.java diff --git a/libraries/common/src/main/java/androidx/media3/common/MediaItem.java b/libraries/common/src/main/java/androidx/media3/common/MediaItem.java index 4db28ca61f..0ffcec8749 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MediaItem.java +++ b/libraries/common/src/main/java/androidx/media3/common/MediaItem.java @@ -23,6 +23,7 @@ import android.os.Bundle; import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.media3.common.util.Assertions; +import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; @@ -598,7 +599,7 @@ public final class MediaItem implements Bundleable { } /** DRM configuration for a media item. */ - public static final class DrmConfiguration { + public static final class DrmConfiguration implements Bundleable { /** Builder for {@link DrmConfiguration}. */ public static final class Builder { @@ -773,7 +774,6 @@ public final class MediaItem implements Bundleable { } public DrmConfiguration build() { - return new DrmConfiguration(this); } } @@ -888,6 +888,83 @@ public final class MediaItem implements Bundleable { result = 31 * result + Arrays.hashCode(keySetId); return result; } + + // Bundleable implementation + + private static final String FIELD_SCHEME = Util.intToStringMaxRadix(0); + private static final String FIELD_LICENSE_URI = Util.intToStringMaxRadix(1); + private static final String FIELD_LICENSE_REQUEST_HEADERS = Util.intToStringMaxRadix(2); + private static final String FIELD_MULTI_SESSION = Util.intToStringMaxRadix(3); + private static final String FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY = Util.intToStringMaxRadix(4); + private static final String FIELD_FORCE_DEFAULT_LICENSE_URI = Util.intToStringMaxRadix(5); + private static final String FIELD_FORCED_SESSION_TRACK_TYPES = Util.intToStringMaxRadix(6); + private static final String FIELD_KEY_SET_ID = Util.intToStringMaxRadix(7); + + /** Object that can restore {@link DrmConfiguration} from a {@link Bundle}. */ + @UnstableApi + public static final Creator CREATOR = DrmConfiguration::fromBundle; + + @UnstableApi + private static DrmConfiguration fromBundle(Bundle bundle) { + UUID scheme = UUID.fromString(checkNotNull(bundle.getString(FIELD_SCHEME))); + @Nullable Uri licenseUri = bundle.getParcelable(FIELD_LICENSE_URI); + Bundle licenseMapAsBundle = + BundleableUtil.getBundleWithDefault(bundle, FIELD_LICENSE_REQUEST_HEADERS, Bundle.EMPTY); + ImmutableMap licenseRequestHeaders = + BundleableUtil.bundleToStringImmutableMap(licenseMapAsBundle); + boolean multiSession = bundle.getBoolean(FIELD_MULTI_SESSION, false); + boolean playClearContentWithoutKey = + bundle.getBoolean(FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY, false); + boolean forceDefaultLicenseUri = bundle.getBoolean(FIELD_FORCE_DEFAULT_LICENSE_URI, false); + ArrayList<@C.TrackType Integer> forcedSessionTrackTypesArray = + BundleableUtil.getIntegerArrayListWithDefault( + bundle, FIELD_FORCED_SESSION_TRACK_TYPES, new ArrayList<>()); + ImmutableList<@C.TrackType Integer> forcedSessionTrackTypes = + ImmutableList.copyOf(forcedSessionTrackTypesArray); + @Nullable byte[] keySetId = bundle.getByteArray(FIELD_KEY_SET_ID); + + Builder builder = new Builder(scheme); + return builder + .setLicenseUri(licenseUri) + .setLicenseRequestHeaders(licenseRequestHeaders) + .setMultiSession(multiSession) + .setForceDefaultLicenseUri(forceDefaultLicenseUri) + .setPlayClearContentWithoutKey(playClearContentWithoutKey) + .setForcedSessionTrackTypes(forcedSessionTrackTypes) + .setKeySetId(keySetId) + .build(); + } + + @UnstableApi + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putString(FIELD_SCHEME, scheme.toString()); + if (licenseUri != null) { + bundle.putParcelable(FIELD_LICENSE_URI, licenseUri); + } + if (!licenseRequestHeaders.isEmpty()) { + bundle.putBundle( + FIELD_LICENSE_REQUEST_HEADERS, BundleableUtil.stringMapToBundle(licenseRequestHeaders)); + } + if (multiSession) { + bundle.putBoolean(FIELD_MULTI_SESSION, multiSession); + } + if (playClearContentWithoutKey) { + bundle.putBoolean(FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY, playClearContentWithoutKey); + } + if (forceDefaultLicenseUri) { + bundle.putBoolean(FIELD_FORCE_DEFAULT_LICENSE_URI, forceDefaultLicenseUri); + } + if (!forcedSessionTrackTypes.isEmpty()) { + bundle.putIntegerArrayList( + FIELD_FORCED_SESSION_TRACK_TYPES, new ArrayList<>(forcedSessionTrackTypes)); + } + if (keySetId != null) { + bundle.putByteArray(FIELD_KEY_SET_ID, keySetId); + } + return bundle; + } } /** Configuration for playing back linear ads with a media item. */ diff --git a/libraries/common/src/main/java/androidx/media3/common/util/BundleableUtil.java b/libraries/common/src/main/java/androidx/media3/common/util/BundleableUtil.java index e3451b78df..fe040458eb 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/BundleableUtil.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/BundleableUtil.java @@ -23,9 +23,12 @@ import android.util.SparseArray; import androidx.annotation.Nullable; import androidx.media3.common.Bundleable; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** Utilities for {@link Bundleable}. */ @UnstableApi @@ -94,6 +97,47 @@ public final class BundleableUtil { return sparseArray; } + public static Bundle stringMapToBundle(Map bundleableMap) { + Bundle bundle = new Bundle(); + for (Map.Entry entry : bundleableMap.entrySet()) { + bundle.putString(entry.getKey(), entry.getValue()); + } + return bundle; + } + + public static HashMap bundleToStringHashMap(Bundle bundle) { + HashMap map = new HashMap<>(); + if (bundle == Bundle.EMPTY) { + return map; + } + for (String key : bundle.keySet()) { + @Nullable String value = bundle.getString(key); + if (value != null) { + map.put(key, value); + } + } + return map; + } + + public static ImmutableMap bundleToStringImmutableMap(Bundle bundle) { + if (bundle == Bundle.EMPTY) { + return ImmutableMap.of(); + } + HashMap map = bundleToStringHashMap(bundle); + return ImmutableMap.copyOf(map); + } + + public static Bundle getBundleWithDefault(Bundle bundle, String field, Bundle defaultValue) { + @Nullable Bundle result = bundle.getBundle(field); + return result != null ? result : defaultValue; + } + + public static ArrayList getIntegerArrayListWithDefault( + Bundle bundle, String field, ArrayList defaultValue) { + @Nullable ArrayList result = bundle.getIntegerArrayList(field); + return result != null ? result : defaultValue; + } + /** * Sets the application class loader to the given {@link Bundle} if no class loader is present. * diff --git a/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java b/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java index 4df2c9d0b7..c8bfd2d11c 100644 --- a/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java @@ -247,6 +247,25 @@ public class MediaItemTest { .build()); } + @Test + public void createDrmConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() { + MediaItem.DrmConfiguration drmConfiguration = + new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) + .setLicenseUri(URI_STRING + "/license") + .setLicenseRequestHeaders(ImmutableMap.of("Referer", "http://www.google.com")) + .setMultiSession(true) + .setForceDefaultLicenseUri(true) + .setPlayClearContentWithoutKey(true) + .setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO)) + .setKeySetId(new byte[] {1, 2, 3}) + .build(); + + MediaItem.DrmConfiguration drmConfigurationFromBundle = + MediaItem.DrmConfiguration.CREATOR.fromBundle(drmConfiguration.toBundle()); + + assertThat(drmConfigurationFromBundle).isEqualTo(drmConfiguration); + } + @Test public void builderSetCustomCacheKey_setsCustomCacheKey() { MediaItem mediaItem = diff --git a/libraries/common/src/test/java/androidx/media3/common/util/BundleableUtilTest.java b/libraries/common/src/test/java/androidx/media3/common/util/BundleableUtilTest.java new file mode 100644 index 0000000000..239485e3c1 --- /dev/null +++ b/libraries/common/src/test/java/androidx/media3/common/util/BundleableUtilTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 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 + * + * https://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 androidx.media3.common.util; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link BundleableUtil}. */ +@RunWith(AndroidJUnit4.class) +public class BundleableUtilTest { + + @Test + public void testStringMapToBundle() { + Map originalMap = new HashMap<>(); + originalMap.put("firstKey", "firstValue"); + originalMap.put("secondKey", "repeatedValue"); + originalMap.put("thirdKey", "repeatedValue"); + originalMap.put("", "valueOfEmptyKey"); + + Bundle mapAsBundle = BundleableUtil.stringMapToBundle(originalMap); + Map restoredMap = BundleableUtil.bundleToStringHashMap(mapAsBundle); + + assertThat(restoredMap).isEqualTo(originalMap); + } + + @Test + public void testGetBundleWithDefault() { + Bundle fullInnerBundle = new Bundle(); + fullInnerBundle.putString("0", "123"); + fullInnerBundle.putBoolean("1", false); + fullInnerBundle.putInt("2", 123); + Bundle defaultBundle = new Bundle(); + defaultBundle.putString("0", "I am default"); + Bundle outerBundle = new Bundle(); + outerBundle.putBundle("0", fullInnerBundle); + outerBundle.putBundle("1", Bundle.EMPTY); + outerBundle.putBundle("2", null); + + Bundle restoredInnerBundle = + BundleableUtil.getBundleWithDefault(outerBundle, "0", defaultBundle); + Bundle restoredEmptyBundle = + BundleableUtil.getBundleWithDefault(outerBundle, "1", defaultBundle); + Bundle restoredNullBundle = + BundleableUtil.getBundleWithDefault(outerBundle, "2", defaultBundle); + + assertThat(restoredInnerBundle).isEqualTo(fullInnerBundle); + assertThat(restoredEmptyBundle).isEqualTo(Bundle.EMPTY); + assertThat(restoredNullBundle).isEqualTo(defaultBundle); + } + + @Test + public void testGetIntegerArrayListWithDefault() { + ArrayList normalArray = new ArrayList<>(); + normalArray.add(4); + normalArray.add(8); + normalArray.add(16); + ArrayList emptyArray = new ArrayList<>(); + ArrayList defaultIntegerArray = new ArrayList<>(); + defaultIntegerArray.add(0); + Bundle bundle = new Bundle(); + bundle.putIntegerArrayList("0", normalArray); + bundle.putIntegerArrayList("1", emptyArray); + bundle.putIntegerArrayList("2", null); + + ArrayList restoredIntegerArray = + BundleableUtil.getIntegerArrayListWithDefault(bundle, "0", defaultIntegerArray); + ArrayList restoredEmptyIntegerArray = + BundleableUtil.getIntegerArrayListWithDefault(bundle, "1", defaultIntegerArray); + ArrayList restoredNullIntegerArray = + BundleableUtil.getIntegerArrayListWithDefault(bundle, "2", defaultIntegerArray); + + assertThat(restoredIntegerArray).isEqualTo(normalArray); + assertThat(restoredEmptyIntegerArray).isEqualTo(emptyArray); + assertThat(restoredNullIntegerArray).isEqualTo(defaultIntegerArray); + } +}