Merge "Add support for playing CEA-608/708 CC tracks" into androidx-master-dev
diff --git a/.gitignore b/.gitignore
index 07ddfed..a19466e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@
buildSrc/build
lifecycle/common/build
jacoco.exec
+studio/
# When Gradle configuration fails with the --profile option enabled it creates this folder.
/reports
diff --git a/activity/api/1.0.0-alpha01.txt b/activity/api/1.0.0-alpha01.txt
index f5bed80..4f1f238 100644
--- a/activity/api/1.0.0-alpha01.txt
+++ b/activity/api/1.0.0-alpha01.txt
@@ -6,7 +6,6 @@
method public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
method public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public androidx.lifecycle.Lifecycle getLifecycle();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
method public final Object? onRetainNonConfigurationInstance();
diff --git a/activity/api/1.0.0-alpha02.txt b/activity/api/1.0.0-alpha02.txt
new file mode 100644
index 0000000..4f1f238
--- /dev/null
+++ b/activity/api/1.0.0-alpha02.txt
@@ -0,0 +1,20 @@
+// Signature format: 2.0
+package androidx.activity {
+
+ public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.lifecycle.ViewModelStoreOwner {
+ ctor public ComponentActivity();
+ method public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+ method public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+ method @Deprecated public Object? getLastCustomNonConfigurationInstance();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
+ method public final Object? onRetainNonConfigurationInstance();
+ method public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+ }
+
+ public interface OnBackPressedCallback {
+ method public boolean handleOnBackPressed();
+ }
+
+}
+
diff --git a/activity/api/current.txt b/activity/api/current.txt
index f5bed80..4f1f238 100644
--- a/activity/api/current.txt
+++ b/activity/api/current.txt
@@ -6,7 +6,6 @@
method public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
method public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public androidx.lifecycle.Lifecycle getLifecycle();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
method public final Object? onRetainNonConfigurationInstance();
diff --git a/activity/ktx/api/1.0.0-alpha02.txt b/activity/ktx/api/1.0.0-alpha02.txt
new file mode 100644
index 0000000..09242be
--- /dev/null
+++ b/activity/ktx/api/1.0.0-alpha02.txt
@@ -0,0 +1,16 @@
+// Signature format: 2.0
+package androidx.activity {
+
+ public final class ActivityViewModelLazy<VM extends androidx.lifecycle.ViewModel> implements kotlin.Lazy<VM> {
+ ctor public ActivityViewModelLazy(androidx.activity.ComponentActivity activity, kotlin.reflect.KClass<VM> viewModelClass, androidx.lifecycle.ViewModelProvider.Factory? factory);
+ method public VM getValue();
+ method public boolean isInitialized();
+ property public VM value;
+ }
+
+ public final class ActivityViewModelLazyKt {
+ ctor public ActivityViewModelLazyKt();
+ }
+
+}
+
diff --git a/activity/src/androidTest/AndroidManifest.xml b/activity/src/androidTest/AndroidManifest.xml
index 40bd4dc..e833bec 100644
--- a/activity/src/androidTest/AndroidManifest.xml
+++ b/activity/src/androidTest/AndroidManifest.xml
@@ -20,6 +20,8 @@
<application>
<activity android:name="androidx.activity.LifecycleComponentActivity"/>
+ <activity android:name="androidx.activity.EagerOverrideLifecycleComponentActivity"/>
+ <activity android:name="androidx.activity.LazyOverrideLifecycleComponentActivity"/>
<activity android:name="androidx.activity.ViewModelActivity"/>
<activity android:name="androidx.activity.OnBackPressedComponentActivity"/>
</application>
diff --git a/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt b/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
new file mode 100644
index 0000000..3c75fc7
--- /dev/null
+++ b/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 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 androidx.activity
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import androidx.test.annotation.UiThreadTest
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ComponentActivityOverrideLifecycleTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(LazyOverrideLifecycleComponentActivity::class.java)
+
+ @UiThreadTest
+ @Test
+ fun testEagerOverride() {
+ try {
+ EagerOverrideLifecycleComponentActivity()
+ fail("Constructor for ComponentActivity using a field initializer should throw")
+ } catch (e: IllegalStateException) {
+ // expected
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun testOverrideLifecycle() {
+ val activity = activityRule.activity
+
+ assertThat(activity.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ }
+}
+
+class EagerOverrideLifecycleComponentActivity : ComponentActivity() {
+
+ val overrideLifecycle = LifecycleRegistry(this)
+
+ override fun getLifecycle(): Lifecycle {
+ return overrideLifecycle
+ }
+}
+
+class LazyOverrideLifecycleComponentActivity : ComponentActivity() {
+ private var overrideLifecycle: LifecycleRegistry? = null
+
+ override fun getLifecycle(): Lifecycle {
+ return overrideLifecycle ?: LifecycleRegistry(this).also {
+ overrideLifecycle = it
+ }
+ }
+}
diff --git a/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/src/main/java/androidx/activity/ComponentActivity.java
index 6821514..dbea8cf 100644
--- a/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -52,7 +52,7 @@
ViewModelStore viewModelStore;
}
- private final LifecycleRegistry mLifecycleRegistry;
+ private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
// Lazily recreated from NonConfigurationInstances by getViewModelStore()
private ViewModelStore mViewModelStore;
@@ -62,9 +62,16 @@
new CopyOnWriteArrayList<>();
public ComponentActivity() {
- mLifecycleRegistry = new LifecycleRegistry(this);
+ Lifecycle lifecycle = getLifecycle();
+ //noinspection ConstantConditions
+ if (lifecycle == null) {
+ throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
+ + "constructor. Please make sure you are lazily constructing your Lifecycle "
+ + "in the first call to getLifecycle() rather than relying on field "
+ + "initialization.");
+ }
if (Build.VERSION.SDK_INT >= 19) {
- mLifecycleRegistry.addObserver(new GenericLifecycleObserver() {
+ getLifecycle().addObserver(new GenericLifecycleObserver() {
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_STOP) {
@@ -77,7 +84,7 @@
}
});
}
- mLifecycleRegistry.addObserver(new GenericLifecycleObserver() {
+ getLifecycle().addObserver(new GenericLifecycleObserver() {
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
@@ -100,7 +107,10 @@
@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
- mLifecycleRegistry.markState(Lifecycle.State.CREATED);
+ Lifecycle lifecycle = getLifecycle();
+ if (lifecycle instanceof LifecycleRegistry) {
+ ((LifecycleRegistry) lifecycle).markState(Lifecycle.State.CREATED);
+ }
super.onSaveInstanceState(outState);
}
@@ -161,6 +171,19 @@
return nc != null ? nc.custom : null;
}
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Overriding this method is no longer supported and this method will be made
+ * <code>final</code> in a future version of ComponentActivity. If you do override
+ * this method, you <code>must</code>:
+ * <ol>
+ * <li>Return an instance of {@link LifecycleRegistry}</li>
+ * <li>Lazily initialize your LifecycleRegistry object when this is first called.
+ * Note that this method will be called in the super classes' constructor, before any
+ * field initialization or object state creation is complete.</li>
+ * </ol>
+ */
@NonNull
@Override
public Lifecycle getLifecycle() {
@@ -261,14 +284,15 @@
*/
public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
- if (owner.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
+ Lifecycle lifecycle = owner.getLifecycle();
+ if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
// Already destroyed, nothing to do
return;
}
// Add new callbacks to the front of the list so that
// the most recently added callbacks get priority
mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
- owner.getLifecycle(), onBackPressedCallback));
+ lifecycle, onBackPressedCallback));
}
/**
diff --git a/animation/testing/build.gradle b/animation/testing/build.gradle
index ae7e3ac9..c251db0 100644
--- a/animation/testing/build.gradle
+++ b/animation/testing/build.gradle
@@ -32,7 +32,7 @@
supportLibrary {
name = "Android Support Animation Testing"
publish = false
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.ANIMATION_TESTING
mavenGroup = LibraryGroups.ANIMATION
inceptionYear = "2018"
description = "This library provides functionalities for testing animations for API 14 and above."
diff --git a/annotations/build.gradle b/annotations/build.gradle
index d55583e..7e20eac 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -53,7 +53,7 @@
supportLibrary {
name = "Android Support Library Annotations"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.ANNOTATION
mavenGroup = LibraryGroups.ANNOTATION
inceptionYear = "2013"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs."
diff --git a/appcompat/build.gradle b/appcompat/build.gradle
index 1c7a7c2..ff3719c 100644
--- a/appcompat/build.gradle
+++ b/appcompat/build.gradle
@@ -26,6 +26,7 @@
androidTestImplementation project(':internal-testutils'), {
exclude group: 'androidx.appcompat', module: 'appcompat'
}
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
android {
diff --git a/appcompat/res/values-v21/styles_base.xml b/appcompat/res/values-v21/styles_base.xml
index 895c850..ccf7acc 100644
--- a/appcompat/res/values-v21/styles_base.xml
+++ b/appcompat/res/values-v21/styles_base.xml
@@ -153,7 +153,7 @@
</style>
<style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="TextAppearance.AppCompat">
- <item name="android:fontFamily">@string/abc_font_family_title_material</item>
+ <item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">@dimen/abc_text_size_menu_header_material</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
diff --git a/appcompat/res/values/donottranslate_material.xml b/appcompat/res/values/donottranslate_material.xml
deleted file mode 100644
index 3f01f7a..0000000
--- a/appcompat/res/values/donottranslate_material.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<resources>
-
- <string name="abc_font_family_display_4_material">sans-serif-light</string>
- <string name="abc_font_family_display_3_material">sans-serif</string>
- <string name="abc_font_family_display_2_material">sans-serif</string>
- <string name="abc_font_family_display_1_material">sans-serif</string>
- <string name="abc_font_family_headline_material">sans-serif</string>
- <string name="abc_font_family_title_material">sans-serif-medium</string>
- <string name="abc_font_family_subhead_material">sans-serif</string>
- <string name="abc_font_family_menu_material">sans-serif</string>
- <string name="abc_font_family_body_2_material">sans-serif-medium</string>
- <string name="abc_font_family_body_1_material">sans-serif</string>
- <string name="abc_font_family_caption_material">sans-serif</string>
- <string name="abc_font_family_button_material">sans-serif-medium</string>
-
-</resources>
\ No newline at end of file
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
index eb02157..c95966f8 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
@@ -40,6 +40,7 @@
import android.text.Layout;
import android.text.PrecomputedText;
import android.view.View;
+import android.view.ViewGroup;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.widget.TextView;
@@ -49,6 +50,7 @@
import androidx.appcompat.test.R;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
+import androidx.core.os.BuildCompat;
import androidx.core.text.PrecomputedTextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.widget.TextViewCompat;
@@ -570,7 +572,7 @@
// setText may wrap the given text with SpannedString. Check the contents by casting
// to String.
assertEquals(SAMPLE_TEXT_1, tv.getText().toString());
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
assertTrue(tv.getText() instanceof PrecomputedText);
}
}
@@ -590,7 +592,7 @@
tv.measure(UNLIMITED_MEASURE_SPEC, UNLIMITED_MEASURE_SPEC);
assertNotEquals(0.0f, tv.getMeasuredWidth());
assertEquals(SAMPLE_TEXT_1, tv.getText().toString());
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
assertTrue(tv.getText() instanceof PrecomputedText);
}
}
@@ -622,7 +624,7 @@
// setText may wrap the given text with SpannedString. Check the contents by casting
// to String.
assertEquals(SAMPLE_TEXT_2, tv.getText().toString());
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
assertTrue(tv.getText() instanceof PrecomputedText);
}
}
@@ -637,7 +639,7 @@
// setText may wrap the given text with SpannedString. Check the contents by casting
// to String.
assertEquals(SAMPLE_TEXT_2, tv.getText().toString());
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
assertTrue(tv.getText() instanceof PrecomputedText);
}
}
@@ -645,6 +647,38 @@
}
@Test
+ public void testSetTextAsync_directionDifference() throws Throwable {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.setContentView(R.layout.appcompat_textview_rtl);
+ final ViewGroup container = mActivity.findViewById(R.id.container);
+ final AppCompatTextView tv = mActivity.findViewById(R.id.text_view_rtl);
+ tv.setTextFuture(PrecomputedTextCompat.getTextFuture(
+ SAMPLE_TEXT_1, tv.getTextMetricsParamsCompat(), null));
+ container.measure(UNLIMITED_MEASURE_SPEC, UNLIMITED_MEASURE_SPEC);
+ }
+ });
+ }
+
+ @Test
+ public void testSetTextAsync_createAndAttach() throws Throwable {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.setContentView(R.layout.appcompat_textview_rtl);
+ final ViewGroup container = mActivity.findViewById(R.id.container);
+
+ final AppCompatTextView tv = new AppCompatTextView(mActivity);
+ tv.setTextFuture(PrecomputedTextCompat.getTextFuture(
+ SAMPLE_TEXT_1, tv.getTextMetricsParamsCompat(), null));
+ container.addView(tv);
+ container.measure(UNLIMITED_MEASURE_SPEC, UNLIMITED_MEASURE_SPEC);
+ }
+ });
+ }
+
+ @Test
public void testSetTextAsync_executionOrder_withNull() throws Throwable {
final ManualExecutor executor = new ManualExecutor();
mActivity.runOnUiThread(new Runnable() {
diff --git a/appcompat/src/androidTest/res/layout/appcompat_textview_rtl.xml b/appcompat/src/androidTest/res/layout/appcompat_textview_rtl.xml
new file mode 100644
index 0000000..5b84a24
--- /dev/null
+++ b/appcompat/src/androidTest/res/layout/appcompat_textview_rtl.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layoutDirection="rtl">
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/text_view_rtl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, World"/>
+</LinearLayout>
diff --git a/asynclayoutinflater/build.gradle b/asynclayoutinflater/build.gradle
index e69a048..1671c98 100644
--- a/asynclayoutinflater/build.gradle
+++ b/asynclayoutinflater/build.gradle
@@ -13,7 +13,7 @@
supportLibrary {
name = "Android Support Library Async Layout Inflater"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.ASYNCLAYOUTINFLATER
mavenGroup = LibraryGroups.ASYNCLAYOUTINFLATER
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.java b/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.java
index 5124566..e6d097a 100644
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.java
+++ b/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.java
@@ -22,7 +22,7 @@
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.java b/benchmark/src/main/java/androidx/benchmark/BenchmarkState.java
index f14482f..ac85f70 100644
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.java
+++ b/benchmark/src/main/java/androidx/benchmark/BenchmarkState.java
@@ -25,7 +25,7 @@
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import java.io.File;
import java.text.NumberFormat;
@@ -69,7 +69,8 @@
private static final int REPEAT_COUNT = 5;
static {
- ApplicationInfo appInfo = InstrumentationRegistry.getTargetContext().getApplicationInfo();
+ ApplicationInfo appInfo = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getApplicationInfo();
IS_DEBUGGABLE = (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
StringBuilder sb = new StringBuilder();
@@ -141,7 +142,9 @@
private void beginBenchmark() {
if (ENABLE_PROFILING && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// TODO: support data dir for old platforms
- File f = new File(InstrumentationRegistry.getContext().getDataDir(), "benchprof");
+ File f = new File(
+ InstrumentationRegistry.getInstrumentation().getContext().getDataDir(),
+ "benchprof");
Log.d(TAG, "Tracing to: " + f.getAbsolutePath());
Debug.startMethodTracingSampling(f.getAbsolutePath(), 16 * 1024 * 1024, 100);
}
diff --git a/browser/build.gradle b/browser/build.gradle
index 1336b63..0e00196 100644
--- a/browser/build.gradle
+++ b/browser/build.gradle
@@ -28,7 +28,7 @@
supportLibrary {
name = "Android Support Custom Tabs"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.BROWSER
mavenGroup = LibraryGroups.BROWSER
inceptionYear = "2015"
description = "Android Support Custom Tabs"
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 7674773..562bd1ec 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -29,7 +29,6 @@
def init = new Properties()
ext.init = init
rootProject.ext.versionChecker = new GMavenVersionChecker(rootProject.logger)
-
apply from: "${supportRoot}/buildSrc/dependencies.gradle"
def setupRepoOutAndBuildNumber() {
@@ -56,7 +55,15 @@
def configureSubProjects() {
subprojects {
repos.addMavenRepositories(repositories)
-
+ }
+ if (!rootProject.hasProperty("android.injected.invoked.from.ide")) {
+ // configure build server tasks only if we are not running in the IDE to speed up
+ // configuration times
+ setupSubprojectsForBuildServer()
+ }
+}
+def setupSubprojectsForBuildServer() {
+ subprojects {
project.plugins.whenPluginAdded { plugin ->
def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin"
.equals(plugin.class.name)
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index b1b6f61..186aa5f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -16,6 +16,7 @@
package androidx.build
+import androidx.build.dokka.Dokka
import androidx.build.SupportConfig.BUILD_TOOLS_VERSION
import androidx.build.SupportConfig.CURRENT_SDK_VERSION
import androidx.build.SupportConfig.DEFAULT_MIN_SDK_VERSION
@@ -112,6 +113,7 @@
tasks.all { task ->
if (task.name.startsWith(Release.DIFF_TASK_PREFIX) ||
"distDocs" == task.name ||
+ Dokka.ARCHIVE_TASK_NAME == task.name ||
"dejetifyArchive" == task.name ||
CheckExternalDependencyLicensesTask.TASK_NAME == task.name) {
buildOnServerTask.dependsOn(task)
@@ -154,6 +156,7 @@
project.createClockLockTasks()
AffectedModuleDetector.configure(gradle, this)
+
}
private fun Project.configureAndroidCommonOptions(extension: BaseExtension) {
diff --git a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
index 560c87c..266eed7 100644
--- a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
@@ -226,7 +226,7 @@
?.forEach { docsProject?.evaluationDependsOn(it.path) }
}
- private fun registerPrebuilts(extension: SupportLibraryExtension) =
+ fun registerPrebuilts(extension: SupportLibraryExtension) =
docsProject?.afterEvaluate { docs ->
val depHandler = docs.dependencies
val root = docs.rootProject
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 31e8087..6ed2006 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -20,29 +20,48 @@
* The list of versions codes of all the libraries in this project.
*/
object LibraryVersions {
- val ACTIVITY = Version("1.0.0-alpha01")
+ val ACTIVITY = Version("1.0.0-alpha02")
val ANIMATION = Version("1.0.0-alpha01")
+ val ANIMATION_TESTING = Version("1.0.0")
+ val ANNOTATION = Version("1.0.0")
val APPCOMPAT = Version("1.1.0-alpha01")
val ARCH_CORE = Version("2.0.0")
val ARCH_CORE_TESTING = ARCH_CORE
val ARCH_RUNTIME = Version("2.0.1-alpha01")
+ val ASYNCLAYOUTINFLATER = Version("1.0.0")
val BENCHMARK = Version("1.0.0-alpha01")
val BIOMETRIC = Version("1.0.0-alpha03")
+ val BROWSER = Version("1.0.0")
val CAR = Version("1.0.0-alpha6")
val CAR_CLUSTER = Version("1.0.0-alpha5")
val CAR_MODERATOR = Version("1.0.0-alpha1")
+ val CARDVIEW = Version("1.0.0")
val COLLECTION = Version("1.1.0-alpha01")
+ val CONTENTPAGER = Version("1.0.0")
val COORDINATORLAYOUT = Version("1.1.0-alpha01")
val CORE = Version("1.1.0-alpha01")
+ val CORE_KTX = Version("1.0.1")
+ val CURSORADAPTER = Version("1.0.0")
+ val CUSTOMVIEW = Version("1.0.0")
+ val DOCUMENTFILE = Version("1.0.0")
+ val DRAWERLAYOUT = Version("1.0.0")
+ val DYNAMICANIMATION = Version("1.0.0")
val DYNAMICANIMATION_KTX = Version("1.0.0-alpha01")
+ val EMOJI = Version("1.0.0")
+ val EXIFINTERFACE = Version("1.0.0")
val FRAGMENT = Version("1.1.0-alpha01")
val FUTURES = Version("1.0.0-alpha02")
val GRIDLAYOUT = Version("1.1.0-alpha01")
+ val HEIFWRITER = Version("1.0.0")
+ val INTERPOLATOR = Version("1.0.0")
val JETIFIER = Version("1.0.0-beta02")
val LEANBACK = Version("1.1.0-alpha01")
val LEANBACK_PREFERENCE = Version("1.1.0-alpha01")
+ val LEGACY = Version("1.0.0")
+ val LOCALBROADCASTMANAGER = Version("1.0.0")
val LIFECYCLE = Version("2.1.0-alpha01")
val LIFECYCLES_SAVEDSTATE = Version("1.0.0-alpha01")
+ val LOADER = Version("1.1.0-alpha01")
val MEDIA = Version("1.1.0-alpha01")
val MEDIA2 = Version("1.0.0-alpha03")
val MEDIA2_EXOPLAYER = Version("1.0.0-alpha01")
@@ -51,19 +70,30 @@
val NAVIGATION = Version("1.0.0-alpha07")
val PAGING = Version("2.2.0-alpha01")
val PALETTE = Version("1.1.0-alpha01")
+ val PALETTE_KTX = Version("1.0.0")
+ val PRINT = Version("1.0.0")
+ val PERCENTLAYOUT = Version("1.0.0")
val PERSISTENCE = Version("2.0.0")
val PREFERENCE = Version("1.1.0-alpha01")
+ val PREFERENCE_KTX = Version("1.0.0")
+ val RECOMMENDATION = Version("1.0.0")
val RECYCLERVIEW = Version("1.1.0-alpha01")
val REMOTECALLBACK = Version("1.0.0-alpha01")
val ROOM = Version("2.1.0-alpha02")
val SLICE = Version("1.1.0-alpha01")
+ val SLICE_BENCHMARK = Version("1.0.0")
val SLICE_BUILDERS_KTX = Version("1.0.0-alpha6")
- val SUPPORT_LIBRARY = Version("1.0.0")
+ val SLIDINGPANELAYOUT = Version("1.0.0")
val SWIPE_REFRESH_LAYOUT = Version("1.1.0-alpha01")
val TEXTCLASSIFIER = Version("1.0.0-alpha01")
val TRANSITION = Version("1.1.0-alpha01")
+ val TVPROVIDER = Version("1.0.0")
+ val VECTORDRAWABLE = Version("1.0.1")
val VECTORDRAWABLE_ANIMATED = Version("1.1.0-alpha01")
val VERSIONED_PARCELABLE = Version("1.1.0-alpha01")
+ val VIEWPAGER = Version("1.1.0-alpha01")
+ val VIEWPAGER2 = Version("1.0.0")
+ val WEAR = Version("1.0.0")
val WEBKIT = Version("1.1.0-alpha01")
val WORKMANAGER = Version("1.0.0-alpha11")
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 5e69e3a..82755d3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -25,8 +25,9 @@
import androidx.build.Strategy.TipOfTree
val RELEASE_RULE = docsRules("public", false) {
+ prebuilts(LibraryGroups.ACTIVITY, "1.0.0-alpha01")
prebuilts(LibraryGroups.ANNOTATION, "1.0.0")
- prebuilts(LibraryGroups.APPCOMPAT, "1.0.0")
+ prebuilts(LibraryGroups.APPCOMPAT, "1.0.2")
prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, "1.0.0")
prebuilts(LibraryGroups.BIOMETRIC, "biometric", "1.0.0-alpha01")
prebuilts(LibraryGroups.BROWSER, "1.0.0")
@@ -35,19 +36,22 @@
prebuilts(LibraryGroups.CAR, "car", "1.0.0-alpha5")
.addStubs("car/stubs/android.car.jar")
prebuilts(LibraryGroups.CARDVIEW, "1.0.0")
+ ignore(LibraryGroups.COLLECTION, "collection-ktx")
prebuilts(LibraryGroups.COLLECTION, "1.0.0")
prebuilts(LibraryGroups.CONCURRENT, "1.0.0-alpha02")
prebuilts(LibraryGroups.CONTENTPAGER, "1.0.0")
prebuilts(LibraryGroups.COORDINATORLAYOUT, "1.0.0")
- prebuilts(LibraryGroups.CORE, "1.0.0")
+ prebuilts(LibraryGroups.CORE, "1.0.1")
+ prebuilts(LibraryGroups.CORE, "core-ktx", "1.0.1")
prebuilts(LibraryGroups.CURSORADAPTER, "1.0.0")
prebuilts(LibraryGroups.CUSTOMVIEW, "1.0.0")
prebuilts(LibraryGroups.DOCUMENTFILE, "1.0.0")
prebuilts(LibraryGroups.DRAWERLAYOUT, "1.0.0")
+ ignore(LibraryGroups.DYNAMICANIMATION, "dynamicanimation-ktx")
prebuilts(LibraryGroups.DYNAMICANIMATION, "1.0.0")
prebuilts(LibraryGroups.EMOJI, "1.0.0")
prebuilts(LibraryGroups.EXIFINTERFACE, "1.0.0")
- prebuilts(LibraryGroups.FRAGMENT, "1.0.0")
+ prebuilts(LibraryGroups.FRAGMENT, "1.1.0-alpha01")
prebuilts(LibraryGroups.GRIDLAYOUT, "1.0.0")
prebuilts(LibraryGroups.HEIFWRITER, "1.0.0")
prebuilts(LibraryGroups.INTERPOLATOR, "1.0.0")
@@ -62,7 +66,8 @@
prebuilts(LibraryGroups.MEDIAROUTER, "1.0.0")
prebuilts(LibraryGroups.PALETTE, "1.0.0")
prebuilts(LibraryGroups.PERCENTLAYOUT, "1.0.0")
- prebuilts(LibraryGroups.PREFERENCE, "1.0.0")
+ prebuilts(LibraryGroups.PREFERENCE, "preference-ktx", "1.0.0")
+ prebuilts(LibraryGroups.PREFERENCE, "1.1.0-alpha01")
prebuilts(LibraryGroups.PRINT, "1.0.0")
prebuilts(LibraryGroups.RECOMMENDATION, "1.0.0")
prebuilts(LibraryGroups.RECYCLERVIEW, "1.0.0")
@@ -75,9 +80,10 @@
prebuilts(LibraryGroups.SLICE, "slice-view", "1.0.0")
prebuilts(LibraryGroups.SLIDINGPANELAYOUT, "1.0.0")
prebuilts(LibraryGroups.SWIPEREFRESHLAYOUT, "1.0.0")
- prebuilts(LibraryGroups.TRANSITION, "1.0.0")
+ prebuilts(LibraryGroups.TRANSITION, "1.0.1")
prebuilts(LibraryGroups.TVPROVIDER, "1.0.0")
prebuilts(LibraryGroups.VECTORDRAWABLE, "1.0.0")
+ prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.0.1")
prebuilts(LibraryGroups.VIEWPAGER, "1.0.0")
prebuilts(LibraryGroups.WEAR, "1.0.0")
.addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
@@ -88,12 +94,13 @@
ignore(LibraryGroups.LIFECYCLE, "lifecycle-savedstate-fragment")
ignore(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-savedstate")
ignore(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-fragment")
+ ignore(LibraryGroups.LIFECYCLE, "lifecycle-livedata-ktx")
+ ignore(LibraryGroups.LIFECYCLE, "lifecycle-livedata-core-ktx")
prebuilts(LibraryGroups.LIFECYCLE, "2.0.0")
prebuilts(LibraryGroups.ARCH_CORE, "2.0.0")
- prebuilts(LibraryGroups.PAGING, "2.1.0-alpha01")
+ prebuilts(LibraryGroups.PAGING, "2.1.0-beta01")
prebuilts(LibraryGroups.NAVIGATION, "1.0.0-alpha07")
- ignore(LibraryGroups.WORKMANAGER, "work-runtime-sync")
- prebuilts(LibraryGroups.WORKMANAGER, "1.0.0-alpha10")
+ prebuilts(LibraryGroups.WORKMANAGER, "1.0.0-alpha11")
default(Ignore)
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 802bc60..539c0f9 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -34,6 +34,8 @@
const val GUAVA = "com.google.guava:guava:23.5-jre"
const val GUAVA_ANDROID = "com.google.guava:guava:23.6-android"
const val GUAVA_LISTENABLE_FUTURE = "com.google.guava:listenablefuture:1.0"
+const val GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT =
+ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava"
const val INTELLIJ_ANNOTATIONS = "com.intellij:annotations:12.0"
const val JAVAPOET = "com.squareup:javapoet:1.8.0"
const val JSR250 = "javax.annotation:javax.annotation-api:1.2"
@@ -47,9 +49,9 @@
const val PLAY_SERVICES = "com.google.android.gms:play-services-base:11.6.0@aar"
const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.0.6"
-const val TEST_CORE = "androidx.test:core:1.0.0-beta01"
-const val TEST_RUNNER = "androidx.test:runner:1.1.0-alpha3"
-const val TEST_RULES = "androidx.test:rules:1.1.0-alpha3"
+const val TEST_CORE = "androidx.test:core:1.0.0"
+const val TEST_RUNNER = "androidx.test:runner:1.1.0"
+const val TEST_RULES = "androidx.test:rules:1.1.0"
const val TRUTH = "com.google.truth:truth:0.34"
/**
* this Xerial version is newer than we want but we need it to fix
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
index 065b862..f011b2a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
@@ -18,19 +18,25 @@
// TODO: after DiffAndDocs and Doclava are fully obsoleted and removed, rename this from Dokka to just Docs
package androidx.build.dokka
+import androidx.build.getBuildId
+import androidx.build.getDistributionDirectory
import androidx.build.java.JavaCompileInputs
import androidx.build.SupportLibraryExtension
+import androidx.build.Release
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getPlugin
import org.jetbrains.dokka.gradle.DokkaPlugin
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.dokka.gradle.PackageOptions
+import androidx.build.DiffAndDocs
object Dokka {
private val RUNNER_TASK_NAME = "dokka"
+ public val ARCHIVE_TASK_NAME: String = "distDokkaDocs"
private val hiddenPackages = listOf(
"androidx.core.internal",
@@ -65,6 +71,14 @@
opts.suppress = true
docsTask.perPackageOptions.add(opts)
}
+ project.tasks.create(ARCHIVE_TASK_NAME, Zip::class.java) { task ->
+ task.dependsOn(docsTask)
+ task.description = "Generates documentation artifact for pushing to developer.android.com"
+ task.from(docsTask.outputDirectory)
+ task.baseName = "android-support-dokka-docs"
+ task.version = getBuildId()
+ task.destinationDir = project.getDistributionDirectory()
+ }
}
return runnerProject.tasks.getByName(Dokka.RUNNER_TASK_NAME) as DokkaTask
}
@@ -79,13 +93,14 @@
return
}
library.libraryVariants.all { variant ->
- if (variant.name == "release") {
+ if (variant.name == Release.DEFAULT_PUBLISH_CONFIG) {
project.afterEvaluate({
val inputs = JavaCompileInputs.fromLibraryVariant(library, variant)
registerInputs(inputs, project)
})
}
}
+ DiffAndDocs.registerPrebuilts(extension)
}
fun registerJavaProject(
@@ -102,6 +117,7 @@
val inputs = JavaCompileInputs.fromSourceSet(mainSourceSet, project)
registerInputs(inputs, project)
})
+ DiffAndDocs.registerPrebuilts(extension)
}
fun registerInputs(inputs: JavaCompileInputs, project: Project) {
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/Metalava.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/Metalava.kt
index 369558f..8618477 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/Metalava.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/Metalava.kt
@@ -24,6 +24,7 @@
import androidx.build.checkapi.hasApiFolder
import androidx.build.checkapi.hasApiTasks
import androidx.build.java.JavaCompileInputs
+import androidx.build.Release
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
@@ -50,7 +51,7 @@
val metalavaConfiguration = project.createMetalavaConfiguration()
library.libraryVariants.all { variant ->
- if (variant.name == "minDepVersionsRelease") {
+ if (variant.name == Release.DEFAULT_PUBLISH_CONFIG) {
if (!project.hasApiFolder()) {
project.logger.info(
"Project ${project.name} doesn't have an api folder, ignoring API tasks.")
diff --git a/car/cluster/api/1.0.0-alpha5.txt b/car/cluster/api/1.0.0-alpha5.txt
index beb43e4..ed909d0 100644
--- a/car/cluster/api/1.0.0-alpha5.txt
+++ b/car/cluster/api/1.0.0-alpha5.txt
@@ -35,6 +35,21 @@
enum_constant public static final androidx.car.cluster.navigation.Distance.Unit YARDS;
}
+ public class ImageReference implements androidx.versionedparcelable.VersionedParcelable {
+ method public android.net.Uri? getContentUri(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int, @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int);
+ method public int getOriginalHeight();
+ method public int getOriginalWidth();
+ method public boolean isTintable();
+ }
+
+ public static final class ImageReference.Builder {
+ ctor public ImageReference.Builder();
+ method public androidx.car.cluster.navigation.ImageReference build();
+ method public androidx.car.cluster.navigation.ImageReference.Builder setContentUri(String);
+ method public androidx.car.cluster.navigation.ImageReference.Builder setIsTintable(boolean);
+ method public androidx.car.cluster.navigation.ImageReference.Builder setOriginalSize(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int, @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int);
+ }
+
public final class Lane implements androidx.versionedparcelable.VersionedParcelable {
method public java.util.List<androidx.car.cluster.navigation.LaneDirection> getDirections();
}
@@ -77,6 +92,7 @@
}
public final class Maneuver implements androidx.versionedparcelable.VersionedParcelable {
+ method public androidx.car.cluster.navigation.ImageReference? getIcon();
method public int getRoundaboutExitNumber();
method public androidx.car.cluster.navigation.Maneuver.Type getType();
}
@@ -84,6 +100,7 @@
public static final class Maneuver.Builder {
ctor public Maneuver.Builder();
method public androidx.car.cluster.navigation.Maneuver build();
+ method public androidx.car.cluster.navigation.Maneuver.Builder setIcon(androidx.car.cluster.navigation.ImageReference?);
method public androidx.car.cluster.navigation.Maneuver.Builder setRoundaboutExitNumber(int);
method public androidx.car.cluster.navigation.Maneuver.Builder setType(androidx.car.cluster.navigation.Maneuver.Type, androidx.car.cluster.navigation.Maneuver.Type...);
}
@@ -168,14 +185,34 @@
enum_constant public static final androidx.car.cluster.navigation.NavigationState.ServiceStatus REROUTING;
}
+ public class RichText implements androidx.versionedparcelable.VersionedParcelable {
+ }
+
+ public static final class RichText.Builder {
+ ctor public RichText.Builder();
+ method public androidx.car.cluster.navigation.RichText.Builder addElement(androidx.car.cluster.navigation.RichTextElement);
+ method public androidx.car.cluster.navigation.RichText build();
+ }
+
+ public class RichTextElement implements androidx.versionedparcelable.VersionedParcelable {
+ }
+
+ public static class RichTextElement.Builder {
+ ctor public RichTextElement.Builder();
+ method public androidx.car.cluster.navigation.RichTextElement! build(String);
+ method public androidx.car.cluster.navigation.RichTextElement.Builder! setImage(androidx.car.cluster.navigation.ImageReference?);
+ }
+
public class Segment implements androidx.versionedparcelable.VersionedParcelable {
ctor public Segment(String);
method public String getName();
}
public final class Step implements androidx.versionedparcelable.VersionedParcelable {
+ method public androidx.car.cluster.navigation.RichText? getCue();
method public androidx.car.cluster.navigation.Distance? getDistance();
method public java.util.List<androidx.car.cluster.navigation.Lane> getLanes();
+ method public androidx.car.cluster.navigation.ImageReference? getLanesImage();
method public androidx.car.cluster.navigation.Maneuver? getManeuver();
}
@@ -183,7 +220,9 @@
ctor public Step.Builder();
method public androidx.car.cluster.navigation.Step.Builder addLane(androidx.car.cluster.navigation.Lane);
method public androidx.car.cluster.navigation.Step build();
+ method public androidx.car.cluster.navigation.Step.Builder setCue(androidx.car.cluster.navigation.RichText?);
method public androidx.car.cluster.navigation.Step.Builder setDistance(androidx.car.cluster.navigation.Distance?);
+ method public androidx.car.cluster.navigation.Step.Builder setLanesImage(androidx.car.cluster.navigation.ImageReference?);
method public androidx.car.cluster.navigation.Step.Builder setManeuver(androidx.car.cluster.navigation.Maneuver?);
}
diff --git a/car/cluster/api/current.txt b/car/cluster/api/current.txt
index beb43e4..ed909d0 100644
--- a/car/cluster/api/current.txt
+++ b/car/cluster/api/current.txt
@@ -35,6 +35,21 @@
enum_constant public static final androidx.car.cluster.navigation.Distance.Unit YARDS;
}
+ public class ImageReference implements androidx.versionedparcelable.VersionedParcelable {
+ method public android.net.Uri? getContentUri(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int, @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int);
+ method public int getOriginalHeight();
+ method public int getOriginalWidth();
+ method public boolean isTintable();
+ }
+
+ public static final class ImageReference.Builder {
+ ctor public ImageReference.Builder();
+ method public androidx.car.cluster.navigation.ImageReference build();
+ method public androidx.car.cluster.navigation.ImageReference.Builder setContentUri(String);
+ method public androidx.car.cluster.navigation.ImageReference.Builder setIsTintable(boolean);
+ method public androidx.car.cluster.navigation.ImageReference.Builder setOriginalSize(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int, @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int);
+ }
+
public final class Lane implements androidx.versionedparcelable.VersionedParcelable {
method public java.util.List<androidx.car.cluster.navigation.LaneDirection> getDirections();
}
@@ -77,6 +92,7 @@
}
public final class Maneuver implements androidx.versionedparcelable.VersionedParcelable {
+ method public androidx.car.cluster.navigation.ImageReference? getIcon();
method public int getRoundaboutExitNumber();
method public androidx.car.cluster.navigation.Maneuver.Type getType();
}
@@ -84,6 +100,7 @@
public static final class Maneuver.Builder {
ctor public Maneuver.Builder();
method public androidx.car.cluster.navigation.Maneuver build();
+ method public androidx.car.cluster.navigation.Maneuver.Builder setIcon(androidx.car.cluster.navigation.ImageReference?);
method public androidx.car.cluster.navigation.Maneuver.Builder setRoundaboutExitNumber(int);
method public androidx.car.cluster.navigation.Maneuver.Builder setType(androidx.car.cluster.navigation.Maneuver.Type, androidx.car.cluster.navigation.Maneuver.Type...);
}
@@ -168,14 +185,34 @@
enum_constant public static final androidx.car.cluster.navigation.NavigationState.ServiceStatus REROUTING;
}
+ public class RichText implements androidx.versionedparcelable.VersionedParcelable {
+ }
+
+ public static final class RichText.Builder {
+ ctor public RichText.Builder();
+ method public androidx.car.cluster.navigation.RichText.Builder addElement(androidx.car.cluster.navigation.RichTextElement);
+ method public androidx.car.cluster.navigation.RichText build();
+ }
+
+ public class RichTextElement implements androidx.versionedparcelable.VersionedParcelable {
+ }
+
+ public static class RichTextElement.Builder {
+ ctor public RichTextElement.Builder();
+ method public androidx.car.cluster.navigation.RichTextElement! build(String);
+ method public androidx.car.cluster.navigation.RichTextElement.Builder! setImage(androidx.car.cluster.navigation.ImageReference?);
+ }
+
public class Segment implements androidx.versionedparcelable.VersionedParcelable {
ctor public Segment(String);
method public String getName();
}
public final class Step implements androidx.versionedparcelable.VersionedParcelable {
+ method public androidx.car.cluster.navigation.RichText? getCue();
method public androidx.car.cluster.navigation.Distance? getDistance();
method public java.util.List<androidx.car.cluster.navigation.Lane> getLanes();
+ method public androidx.car.cluster.navigation.ImageReference? getLanesImage();
method public androidx.car.cluster.navigation.Maneuver? getManeuver();
}
@@ -183,7 +220,9 @@
ctor public Step.Builder();
method public androidx.car.cluster.navigation.Step.Builder addLane(androidx.car.cluster.navigation.Lane);
method public androidx.car.cluster.navigation.Step build();
+ method public androidx.car.cluster.navigation.Step.Builder setCue(androidx.car.cluster.navigation.RichText?);
method public androidx.car.cluster.navigation.Step.Builder setDistance(androidx.car.cluster.navigation.Distance?);
+ method public androidx.car.cluster.navigation.Step.Builder setLanesImage(androidx.car.cluster.navigation.ImageReference?);
method public androidx.car.cluster.navigation.Step.Builder setManeuver(androidx.car.cluster.navigation.Maneuver?);
}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/ImageReferenceTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/ImageReferenceTest.java
new file mode 100644
index 0000000..b00029a
--- /dev/null
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/ImageReferenceTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2018 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 androidx.car.cluster.navigation;
+
+import static androidx.car.cluster.navigation.utils.Assertions.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ImageReference} serialization
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ImageReferenceTest {
+ private static final String TEST_CONTENT_URI = "content://foo";
+ private static final int TEST_WIDTH = 123;
+ private static final int TEST_HEIGHT = 234;
+
+ /**
+ * Test a few equality conditions
+ */
+ @Test
+ public void equality() {
+ ImageReference expected = createSampleImage();
+
+ assertEquals(expected, createSampleImage());
+ assertNotEquals(expected, new ImageReference.Builder()
+ .setContentUri("content://bar")
+ .setIsTintable(true)
+ .setOriginalSize(TEST_WIDTH, TEST_HEIGHT)
+ .build());
+ assertNotEquals(expected, new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setIsTintable(false)
+ .setOriginalSize(TEST_WIDTH, TEST_HEIGHT)
+ .build());
+ assertNotEquals(expected, new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setIsTintable(false)
+ .setOriginalSize(1, TEST_HEIGHT)
+ .build());
+ assertNotEquals(expected, new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setIsTintable(false)
+ .setOriginalSize(TEST_WIDTH, 1)
+ .build());
+
+ assertEquals(expected.hashCode(), new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setIsTintable(true)
+ .setOriginalSize(TEST_WIDTH, TEST_HEIGHT)
+ .build()
+ .hashCode());
+ }
+
+ /**
+ * Tests the output of the {@link ImageReference.Builder}
+ */
+ @Test
+ public void builder_outputShouldMatchExpected() {
+ ImageReference image = createSampleImage();
+
+ assertEquals(TEST_CONTENT_URI, image.getRawContentUri());
+ assertEquals(true, image.isTintable());
+ assertEquals(TEST_WIDTH, image.getOriginalWidth());
+ assertEquals(TEST_HEIGHT, image.getOriginalHeight());
+ }
+
+ /**
+ * Returns a sample {@link ImageReference} instance for testing.
+ */
+ public static ImageReference createSampleImage() {
+ return new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setIsTintable(true)
+ .setOriginalSize(TEST_WIDTH, TEST_HEIGHT)
+ .build();
+ }
+
+ /**
+ * Tests {@link ImageReference.Builder} can be used to produce more than one instance.
+ */
+ @Test
+ public void builder_shouldBeReusable() {
+ final String alternativeContentUri = "content://bar";
+
+ ImageReference.Builder builder = new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setIsTintable(true)
+ .setOriginalSize(TEST_WIDTH, TEST_HEIGHT);
+ ImageReference image1 = builder.build();
+ ImageReference image2 = builder.build();
+ assertEquals(image1, image2);
+
+ builder.setContentUri(alternativeContentUri);
+ ImageReference image3 = builder.build();
+ assertEquals(image3, new ImageReference.Builder()
+ .setContentUri(alternativeContentUri)
+ .setIsTintable(true)
+ .setOriginalSize(TEST_WIDTH, TEST_HEIGHT)
+ .build());
+ }
+
+ /**
+ * {@link ImageReference.Builder#setContentUri(String)} must be called.
+ */
+ @Test(expected = NullPointerException.class)
+ public void builder_contentUriIsMandatory() {
+ new ImageReference.Builder().setOriginalSize(TEST_WIDTH, TEST_HEIGHT).build();
+ }
+
+ /**
+ * Tests that passing strings not starting with 'content://' to
+ * {@link ImageReference.Builder#setContentUri(String)} throws an exception.
+ */
+ @Test
+ public void builder_contentUriOnlyAcceptsContentSchema() {
+ ImageReference.Builder builder = new ImageReference.Builder();
+ assertThrows(NullPointerException.class, () -> builder.setContentUri(null));
+ assertThrows(IllegalArgumentException.class, () -> builder.setContentUri(""));
+ assertThrows(IllegalArgumentException.class, () -> builder.setContentUri("http://foo"));
+ assertThrows(IllegalArgumentException.class, () -> builder.setContentUri("content:/foo"));
+ assertThrows(IllegalArgumentException.class, () -> builder.setContentUri("content//foo"));
+ }
+
+ /**
+ * Even if a content URI was not received, {@link ImageReference#getRawContentUri()} should
+ * return an empty string.
+ */
+ @Test
+ public void contentUri_unsetContentUriReturnsEmptyRawUri() {
+ assertEquals("", new ImageReference().getRawContentUri());
+ }
+
+ /**
+ * If {@link ImageReference.Builder#setContentUri(String)} is not used, then
+ * {@link ImageReference#getContentUri(int, int)} should return null.
+ */
+ @Test
+ public void contentUri_unsetContentUriRetursNullUri() {
+ assertEquals(null, new ImageReference().getContentUri(TEST_WIDTH, TEST_HEIGHT));
+ }
+
+ /**
+ * {@link ImageReference#getContentUri(int, int)} should provide a fully formed URI containing
+ * with and height parameters.
+ */
+ @Test
+ public void contentUri_requestMustContainWidthAndHeightParameters() {
+ ImageReference image = new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setOriginalSize(1, 1)
+ .build();
+
+ assertEquals(Uri.parse("content://foo?w=123&h=234"),
+ image.getContentUri(TEST_WIDTH, TEST_HEIGHT));
+ }
+
+ /**
+ * {@link ImageReference#getContentUri(int, int)} throws an exception if size is not provided.
+ */
+ @Test
+ public void contentUri_widthAndHeightParametersMustBePositive() {
+ ImageReference image = new ImageReference.Builder()
+ .setContentUri(TEST_CONTENT_URI)
+ .setOriginalSize(1, 1)
+ .build();
+
+ assertThrows(IllegalArgumentException.class, () -> image.getContentUri(0, TEST_HEIGHT));
+ assertThrows(IllegalArgumentException.class, () -> image.getContentUri(TEST_WIDTH, 0));
+ }
+}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/LaneTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/LaneTest.java
index cd9c8e2..865d972 100644
--- a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/LaneTest.java
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/LaneTest.java
@@ -16,12 +16,18 @@
package androidx.car.cluster.navigation;
+import static androidx.car.cluster.navigation.utils.Assertions.assertImmutable;
+
+import static org.junit.Assert.assertEquals;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
/**
* Tests for {@link Lane} serialization
*/
@@ -31,9 +37,34 @@
/**
* Tests that lists returned by {@link Lane} are immutable.
*/
- @Test(expected = UnsupportedOperationException.class)
- public void immutableLists() {
- Lane lane = new Lane.Builder().build();
- lane.getDirections().add(new LaneDirection.Builder().build());
+ @Test
+ public void immutability() {
+ assertImmutable(new Lane.Builder().build().getDirections());
+ assertImmutable(new Lane().getDirections());
+ }
+
+ /**
+ * Tests that even if we receive a null list of {@link LaneDirection}s, we return an empty list
+ * to the consumers.
+ */
+ @Test
+ public void nullability_directionsListIsNeverNull() {
+ assertEquals(new ArrayList<>(), new Lane().getDirections());
+ }
+
+ /**
+ * Returns a sample {@link Lane} for testing.
+ */
+ public static Lane createSampleLane() {
+ return new Lane.Builder()
+ .addDirection(new LaneDirection.Builder()
+ .setShape(LaneDirection.Shape.NORMAL_LEFT)
+ .setHighlighted(true)
+ .build())
+ .addDirection(new LaneDirection.Builder()
+ .setShape(LaneDirection.Shape.STRAIGHT)
+ .setHighlighted(true)
+ .build())
+ .build();
}
}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/NavigationStateTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/NavigationStateTest.java
index 601b87f..f657b9b 100644
--- a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/NavigationStateTest.java
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/NavigationStateTest.java
@@ -16,6 +16,8 @@
package androidx.car.cluster.navigation;
+import static androidx.car.cluster.navigation.utils.Assertions.assertImmutable;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -145,28 +147,19 @@
}
/**
- * Tests that {@link NavigationState#mDestinations} is immutable.
+ * Tests that {@link NavigationState} is immutable.
*/
- @Test(expected = UnsupportedOperationException.class)
- public void immutableDestionationsLists() {
- NavigationState state = createEmptyState();
- state.getDestinations().add(new Destination.Builder().build());
- }
-
- /**
- * Tests that {@link NavigationState#mSteps} is immutable.
- */
- @Test(expected = UnsupportedOperationException.class)
- public void immutableStepsLists() {
- NavigationState state = createEmptyState();
- state.getSteps().add(new Step.Builder().build());
+ @Test
+ public void immutability() {
+ assertImmutable(createEmptyState().getDestinations());
+ assertImmutable(createEmptyState().getSteps());
}
/**
* Test a few equality conditions
*/
@Test
- public void equalityTest() {
+ public void equality() {
// Testing empty nav state cases
assertEquals(new NavigationState(), new NavigationState.Builder().build());
assertEquals(new NavigationState(), new NavigationState.Builder()
@@ -234,36 +227,7 @@
private NavigationState createSampleState() {
return new NavigationState.Builder()
- .addStep(new Step.Builder()
- .setManeuver(new Maneuver.Builder()
- .setType(Maneuver.Type.DEPART).build())
- .setDistance(new Distance(10, "10", Distance.Unit.METERS))
- .addLane(new Lane.Builder()
- .addDirection(new LaneDirection.Builder()
- .setShape(LaneDirection.Shape.NORMAL_LEFT)
- .setHighlighted(true)
- .build())
- .addDirection(new LaneDirection.Builder()
- .setShape(LaneDirection.Shape.STRAIGHT)
- .setHighlighted(true)
- .build())
- .build())
- .addLane(new Lane.Builder()
- .addDirection(new LaneDirection.Builder()
- .setShape(LaneDirection.Shape.SHARP_LEFT)
- .build())
- .addDirection(new LaneDirection.Builder()
- .setShape(LaneDirection.Shape.NORMAL_LEFT)
- .build())
- .build())
- .build())
- .addStep(new Step.Builder()
- .setManeuver(new Maneuver.Builder()
- .setType(Maneuver.Type.ROUNDABOUT_EXIT)
- .setRoundaboutExitNumber(2)
- .build())
- .setDistance(new Distance(15, "15", Distance.Unit.METERS))
- .build())
+ .addStep(StepTest.createSampleStep())
.addDestination(new Destination.Builder()
.setTitle("Home")
.setDistance(new Distance(1230, "1.2", Distance.Unit.KILOMETERS))
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextElementTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextElementTest.java
new file mode 100644
index 0000000..fce32e19
--- /dev/null
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextElementTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018 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 androidx.car.cluster.navigation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link RichText} serialization
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RichTextElementTest {
+ private static final String TEST_TEXT = "foo";
+ private static final ImageReference TEST_IMAGE = ImageReferenceTest.createSampleImage();
+
+ /**
+ * Test a few equality conditions
+ */
+ @Test
+ public void equality() {
+ RichTextElement element = createSampleElement();
+
+ assertEquals(element, createSampleElement());
+ assertNotEquals(element, new RichTextElement.Builder()
+ .setImage(TEST_IMAGE)
+ .build(""));
+ assertNotEquals(element, new RichTextElement.Builder()
+ .setImage(null)
+ .build(TEST_TEXT));
+
+ assertEquals(element.hashCode(), createSampleElement().hashCode());
+ }
+
+ /**
+ * Test that a text representation must be provided.
+ */
+ @Test(expected = NullPointerException.class)
+ public void builder_textIsMandatory() {
+ new RichTextElement.Builder().build(null);
+ }
+
+ /**
+ * Test that an empty string will be returned from {@link RichTextElement#getText()} to the
+ * consumer, even if no string was received.
+ */
+ @Test
+ public void text_isEmptyIfNotProvided() {
+ assertEquals("", new RichTextElement().getText());
+ }
+
+ /**
+ * Returns a sample {@link RichTextElement} for testing.
+ */
+ public RichTextElement createSampleElement() {
+ return new RichTextElement.Builder()
+ .setImage(TEST_IMAGE)
+ .build(TEST_TEXT);
+ }
+}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextTest.java
new file mode 100644
index 0000000..488b650
--- /dev/null
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018 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 androidx.car.cluster.navigation;
+
+import static androidx.car.cluster.navigation.utils.Assertions.assertImmutable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for {@link RichText} serialization
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RichTextTest {
+ private static final String TEST_TEXT = "foo";
+
+ /**
+ * Test a few equality conditions
+ */
+ @Test
+ public void equality() {
+ RichText expected = createSampleRichText();
+ RichTextElement element = new RichTextElement.Builder().build(TEST_TEXT);
+
+ assertEquals(expected, createSampleRichText());
+ assertNotEquals(expected, new RichText.Builder().build());
+ assertNotEquals(expected, new RichText.Builder()
+ .addElement(element)
+ .addElement(element)
+ .build());
+
+ assertEquals(expected.hashCode(), createSampleRichText().hashCode());
+ }
+
+ /**
+ * Tests that lists returned by {@link RichText} are immutable.
+ */
+ @Test
+ public void immutability() {
+ assertImmutable(new RichText.Builder().build().getElements());
+ assertImmutable(new RichText().getElements());
+ }
+
+ /**
+ * Tests that even if we receive a null list of {@link RichTextElement}s, we return an empty
+ * list to the consumers.
+ */
+ @Test
+ public void nullability_elementsListIsNeverNull() {
+ assertEquals(new ArrayList<>(), new RichText().getElements());
+ }
+
+ /**
+ * Builder doesn't accept null elements
+ */
+ @Test(expected = NullPointerException.class)
+ public void builder_elementsCantBeNull() {
+ new RichText.Builder().addElement(null);
+ }
+
+ /**
+ * Returns a sample {@link RichText} instance for testing.
+ */
+ public static RichText createSampleRichText() {
+ return new RichText.Builder()
+ .addElement(new RichTextElement.Builder().build(TEST_TEXT))
+ .build();
+ }
+}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/SegmentTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/SegmentTest.java
index a1f61a9..93a1a36 100644
--- a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/SegmentTest.java
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/SegmentTest.java
@@ -35,7 +35,7 @@
* Test a few equality conditions
*/
@Test
- public void equalityTests() {
+ public void equality() {
assertEquals(new Segment(), new Segment(""));
assertEquals(new Segment("foo"), new Segment("foo"));
assertNotEquals(new Segment("foo"), new Segment("bar"));
@@ -48,7 +48,7 @@
* Test null on {@link Segment} constructor
*/
@Test(expected = NullPointerException.class)
- public void nullHandlingTests() {
+ public void nullability() {
new Segment(null);
}
}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/StepTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/StepTest.java
index 12e6ce6..020bcd5 100644
--- a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/StepTest.java
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/StepTest.java
@@ -16,6 +16,11 @@
package androidx.car.cluster.navigation;
+import static androidx.car.cluster.navigation.utils.Assertions.assertImmutable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -29,11 +34,75 @@
@SmallTest
public class StepTest {
/**
- * Tests that lists returned by {@link Step} are immutable.
+ * Test a few equality conditions
*/
- @Test(expected = UnsupportedOperationException.class)
- public void immutableLists() {
- Step step = new Step.Builder().build();
- step.getLanes().add(new Lane.Builder().build());
+ @Test
+ public void equality() {
+ Step expected = createSampleStep();
+
+ assertEquals(expected, createSampleStep());
+ assertNotEquals(expected, new Step.Builder()
+ .setDistance(new Distance(10, "10", Distance.Unit.METERS))
+ .setManeuver(new Maneuver.Builder().setType(Maneuver.Type.DEPART).build())
+ .addLane(LaneTest.createSampleLane())
+ .setLanesImage(ImageReferenceTest.createSampleImage())
+ .build());
+ assertNotEquals(expected, new Step.Builder()
+ .setCue(RichTextTest.createSampleRichText())
+ .setManeuver(new Maneuver.Builder().setType(Maneuver.Type.DEPART).build())
+ .addLane(LaneTest.createSampleLane())
+ .setLanesImage(ImageReferenceTest.createSampleImage())
+ .build());
+ assertNotEquals(expected, new Step.Builder()
+ .setCue(RichTextTest.createSampleRichText())
+ .setDistance(new Distance(10, "10", Distance.Unit.METERS))
+ .addLane(LaneTest.createSampleLane())
+ .setLanesImage(ImageReferenceTest.createSampleImage())
+ .build());
+ assertNotEquals(expected, new Step.Builder()
+ .setCue(RichTextTest.createSampleRichText())
+ .setDistance(new Distance(10, "10", Distance.Unit.METERS))
+ .setManeuver(new Maneuver.Builder().setType(Maneuver.Type.DEPART).build())
+ .build());
+ assertNotEquals(expected, new Step.Builder()
+ .setCue(RichTextTest.createSampleRichText())
+ .setDistance(new Distance(10, "10", Distance.Unit.METERS))
+ .setManeuver(new Maneuver.Builder().setType(Maneuver.Type.DEPART).build())
+ .addLane(LaneTest.createSampleLane())
+ .addLane(LaneTest.createSampleLane())
+ .setLanesImage(ImageReferenceTest.createSampleImage())
+ .build());
+
+ assertEquals(expected.hashCode(), createSampleStep().hashCode());
+ }
+
+ /**
+ * Lists returned by {@link Step} are immutable.
+ */
+ @Test
+ public void immutability_lanesListIsNeverNull() {
+ assertImmutable(new Step.Builder().build().getLanes());
+ assertImmutable(new Step().getLanes());
+ }
+
+ /**
+ * Builder doesn't accept null lanes
+ */
+ @Test(expected = NullPointerException.class)
+ public void builder_lanesCantBeNull() {
+ new Step.Builder().addLane(null);
+ }
+
+ /**
+ * Returns a sample {@link Step} for testing
+ */
+ public static Step createSampleStep() {
+ return new Step.Builder()
+ .setCue(RichTextTest.createSampleRichText())
+ .setDistance(new Distance(10, "10", Distance.Unit.METERS))
+ .setManeuver(new Maneuver.Builder().setType(Maneuver.Type.DEPART).build())
+ .addLane(LaneTest.createSampleLane())
+ .setLanesImage(ImageReferenceTest.createSampleImage())
+ .build();
}
}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/utils/Assertions.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/utils/Assertions.java
new file mode 100644
index 0000000..51410ad
--- /dev/null
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/utils/Assertions.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 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 androidx.car.cluster.navigation.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+
+/**
+ * A collection of utility methods that supports asserting conditions in tests.
+ */
+public class Assertions {
+
+ private Assertions() {
+ // No instantiable class.
+ }
+
+ /**
+ * Asserts the given block throws the given throwable.
+ */
+ public static void assertThrows(Class<? extends Throwable> throwableClass, Runnable runnable) {
+ try {
+ runnable.run();
+ fail();
+ } catch (Throwable e) {
+ assertEquals(e.getClass(), throwableClass);
+ }
+ }
+
+ /**
+ * Asserts the given list can not be mutated.
+ */
+ public static void assertImmutable(List<?> list) {
+ assertThrows(UnsupportedOperationException.class, () -> list.add(null));
+ assertThrows(UnsupportedOperationException.class, () -> list.set(0, null));
+ assertThrows(UnsupportedOperationException.class, () -> list.remove(0));
+ }
+}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/Common.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/Common.java
index 31b47c4..8c83496 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/Common.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/Common.java
@@ -48,14 +48,14 @@
}
/**
- * Returns the given list, or an empty one if the list is null, or if any of its elements
- * is null. The returned list should not be mutated.
+ * Returns an immutable view of the given list, or an empty one if the list is null, or if any
+ * of its elements is null.
*/
@NonNull
- public static <T> List<T> nonNullOrEmpty(@Nullable List<T> list) {
+ public static <T> List<T> immutableOrEmpty(@Nullable List<T> list) {
if (list == null || list.stream().anyMatch(Objects::isNull)) {
return Collections.emptyList();
}
- return list;
+ return Collections.unmodifiableList(list);
}
}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/Destination.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/Destination.java
index bd80c81..246ac94 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/Destination.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/Destination.java
@@ -71,11 +71,11 @@
* Builder for creating a {@link Destination}
*/
public static final class Builder {
- String mTitle;
- String mAddress;
- Distance mDistance;
- Time mEta;
- LatLng mLatLng;
+ private String mTitle;
+ private String mAddress;
+ private Distance mDistance;
+ private Time mEta;
+ private LatLng mLatLng;
/**
* Sets the destination title (formatted for the current user's locale), or empty if there
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/ImageReference.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/ImageReference.java
new file mode 100644
index 0000000..9e2ede7
--- /dev/null
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/ImageReference.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2018 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 androidx.car.cluster.navigation;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.annotation.SuppressLint;
+import android.net.Uri;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Preconditions;
+import androidx.versionedparcelable.ParcelField;
+import androidx.versionedparcelable.VersionedParcelable;
+import androidx.versionedparcelable.VersionedParcelize;
+
+import java.util.Objects;
+
+/**
+ * Reference to an image. This class encapsulates a 'content://' style URI plus metadata that allows
+ * consumers to know the image they will receive and how to handle it.
+ *
+ * <ul>
+ * <li><b>Sizing:</b> Producers will always provide an image "original" size which defines the image
+ * aspect ratio. When requesting these images, consumers must always specify a desired size (width
+ * and height) based on UI available space and the provided aspect ration. Producers can use this
+ * "requested" size to select the best version of the requested image, and producers can optionally
+ * resize the image to exactly match the "requested" size provided, but consumers should not assume
+ * that the received image will match such size. Instead, consumers should always assume that the
+ * image will require additional scaling.
+ * <li><b>Content:</b> Producers should avoid including margins around the image content.
+ * <li><b>Format:</b> Content URI must reference a file with MIME type 'image/png', 'image/jpeg'
+ * or 'image/bmp' (vector images are not supported).
+ * <li><b>Color:</b> Images can be either "tintable" or not. A "tintable" image is such that all its
+ * content is defined in its alpha channel, while its color (all other channels) can be altered
+ * without losing information (e.g.: icons). A non "tintable" images contains information in all its
+ * channels (e.g.: photos).
+ * <li><b>Caching:</b> Given the same image reference and the same requested size, producers must
+ * return the exact same image. This means that it should be safe for the consumer to cache an image
+ * once downloaded and use this image reference plus requested size as key, for as long as they
+ * need. If a producer needs to provide a different version of a certain image, they must provide a
+ * different image reference (e.g. producers can opt to include version information as part of the
+ * content URI).
+ * </ul>
+ */
+@VersionedParcelize
+public class ImageReference implements VersionedParcelable {
+ private static final String SCHEME = "content://";
+ private static final String WIDTH_HINT_PARAMETER = "w";
+ private static final String HEIGHT_HINT_PARAMETER = "h";
+
+ @ParcelField(1)
+ String mContentUri;
+ @ParcelField(2)
+ int mOriginalWidth;
+ @ParcelField(3)
+ int mOriginalHeight;
+ @ParcelField(4)
+ boolean mIsTintable;
+
+ /**
+ * Used by {@link VersionedParcelable}
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ ImageReference() {
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ ImageReference(@NonNull String contentUri,
+ @IntRange(from = 1, to = Integer.MAX_VALUE) int originalWidth,
+ @IntRange(from = 1, to = Integer.MAX_VALUE) int originalHeight,
+ boolean isTintable) {
+ mContentUri = Preconditions.checkNotNull(contentUri);
+ mOriginalWidth = Preconditions.checkArgumentInRange(originalWidth, 1,
+ Integer.MAX_VALUE, "originalWidth");
+ mOriginalHeight = Preconditions.checkArgumentInRange(originalHeight, 1,
+ Integer.MAX_VALUE, "originalHeight");
+ mIsTintable = isTintable;
+ }
+
+ /**
+ * Builder for creating an {@link ImageReference}.
+ */
+ public static final class Builder {
+ private String mContentUri;
+ private int mOriginalWidth;
+ private int mOriginalHeight;
+ private boolean mIsTintable;
+
+ /**
+ * Sets a 'content://' style URI
+ *
+ * @return this object for chaining
+ * @throws NullPointerException if the provided {@code contentUri} is null
+ * @throws IllegalArgumentException if the provided {@code contentUri} doesn't start with
+ * 'content://'.
+ */
+ @NonNull
+ public Builder setContentUri(@NonNull String contentUri) {
+ Preconditions.checkNotNull(contentUri);
+ Preconditions.checkArgument(contentUri.startsWith(SCHEME));
+ mContentUri = contentUri;
+ return this;
+ }
+
+ /**
+ * Sets the aspect ratio of this image, expressed as with and height sizes. Both dimensions
+ * must be greater than 0.
+ *
+ * @return this object for chaining
+ * @throws IllegalArgumentException if any of the dimensions is not positive.
+ */
+ @NonNull
+ public Builder setOriginalSize(@IntRange(from = 1, to = Integer.MAX_VALUE) int width,
+ @IntRange(from = 1, to = Integer.MAX_VALUE) int height) {
+ Preconditions.checkArgumentInRange(width, 1, Integer.MAX_VALUE, "width");
+ Preconditions.checkArgumentInRange(height, 1, Integer.MAX_VALUE, "height");
+ mOriginalWidth = width;
+ mOriginalHeight = height;
+ return this;
+ }
+
+ /**
+ * Sets whether this image is "tintable" or not. An image is "tintable" when all its
+ * content is defined in its alpha-channel, designed to be colorized (e.g. using
+ * {@link android.graphics.PorterDuff.Mode#SRC_ATOP} image composition).
+ * If this method is not used, images will be non "tintable" by default.
+ *
+ * @return this object for chaining
+ */
+ @NonNull
+ public Builder setIsTintable(boolean isTintable) {
+ mIsTintable = isTintable;
+ return this;
+ }
+
+ /**
+ * Returns a {@link ImageReference} built with the provided information. Calling
+ * {@link ImageReference.Builder#setContentUri(String)} and
+ * {@link ImageReference.Builder#setOriginalSize(int, int)} before calling this method is
+ * mandatory.
+ *
+ * @return an {@link ImageReference} instance
+ * @throws NullPointerException if content URI is not provided.
+ * @throws IllegalArgumentException if original size is not set.
+ */
+ @NonNull
+ public ImageReference build() {
+ return new ImageReference(mContentUri, mOriginalWidth, mOriginalHeight, mIsTintable);
+ }
+ }
+
+ /**
+ * Returns a 'content://' style URI that can be used to retrieve the actual image, or an empty
+ * string if the URI provided by the producer doesn't comply with the format requirements. If
+ * this URI is used as-is, the size of the resulting image is undefined.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @NonNull
+ public String getRawContentUri() {
+ String value = Common.nonNullOrEmpty(mContentUri);
+ return value.startsWith(SCHEME) ? value : "";
+ }
+
+ /**
+ * Returns a fully formed {@link Uri} that can be used to retrieve the actual image, including
+ * size constraints, or null if this image reference is not properly formed.
+ * <p>
+ * Producers can optionally use these size constraints to provide an optimized version of the
+ * image, but the resulting image might still not match the requested size.
+ * <p>
+ * Consumers must confirm the size of the received image and scale it proportionally (
+ * maintaining the aspect ratio of the received image) if it doesn't match the desired
+ * dimensions.
+ *
+ * @param width desired maximum width (must be greater than 0)
+ * @param height desired maximum height (must be greater than 0)
+ * @return fully formed {@link Uri}, or null if this image reference can not be used.
+ */
+ @Nullable
+ public Uri getContentUri(@IntRange(from = 1, to = Integer.MAX_VALUE) int width,
+ @IntRange(from = 1, to = Integer.MAX_VALUE) int height) {
+ Preconditions.checkArgumentInRange(width, 1, Integer.MAX_VALUE, "width");
+ Preconditions.checkArgumentInRange(height, 1, Integer.MAX_VALUE, "height");
+ String contentUri = getRawContentUri();
+ if (contentUri.isEmpty()) {
+ // We have an invalid content URI.
+ return null;
+ }
+ return Uri.parse(contentUri).buildUpon()
+ .appendQueryParameter(WIDTH_HINT_PARAMETER, String.valueOf(width))
+ .appendQueryParameter(HEIGHT_HINT_PARAMETER, String.valueOf(height))
+ .build();
+ }
+
+ /**
+ * Returns the image width, which should only be used to determine the image aspect ratio.
+ */
+ public int getOriginalWidth() {
+ return mOriginalWidth;
+ }
+
+ /**
+ * Returns the image height, which should only be used to determine the image aspect ratio.
+ */
+ public int getOriginalHeight() {
+ return mOriginalHeight;
+ }
+
+ /**
+ * Returns whether this image is "tintable" or not. An image is "tintable" when all its
+ * content is defined in its alpha-channel, designed to be colorized (e.g. using
+ * {@link android.graphics.PorterDuff.Mode#SRC_ATOP} image composition).
+ */
+ public boolean isTintable() {
+ return mIsTintable;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ImageReference image = (ImageReference) o;
+ return Objects.equals(getRawContentUri(), image.getRawContentUri())
+ && getOriginalWidth() == image.getOriginalWidth()
+ && getOriginalHeight() == image.getOriginalHeight()
+ && isTintable() == image.isTintable();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getRawContentUri(), getOriginalWidth(), getOriginalHeight(),
+ isTintable());
+ }
+
+ // DefaultLocale suppressed as this method is only offered for debugging purposes.
+ @SuppressLint("DefaultLocale")
+ @Override
+ public String toString() {
+ return String.format("{contentUri: '%s', originalWidth: %d, originalHeight: %d, "
+ + "isTintable: %s}",
+ mContentUri, mOriginalWidth, mOriginalHeight, mIsTintable);
+ }
+}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/Lane.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/Lane.java
index 509a7bd..6d7e734 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/Lane.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/Lane.java
@@ -26,7 +26,6 @@
import androidx.versionedparcelable.VersionedParcelize;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -54,14 +53,14 @@
*/
@RestrictTo(LIBRARY_GROUP)
Lane(@NonNull List<LaneDirection> directions) {
- mDirections = Collections.unmodifiableList(new ArrayList<>(directions));
+ mDirections = new ArrayList<>(directions);
}
/**
* Builder for creating a {@link Lane}
*/
public static final class Builder {
- List<LaneDirection> mDirections = new ArrayList<>();
+ private List<LaneDirection> mDirections = new ArrayList<>();
/**
* Add a possible direction a driver can take from this lane.
@@ -86,7 +85,7 @@
*/
@NonNull
public List<LaneDirection> getDirections() {
- return Common.nonNullOrEmpty(mDirections);
+ return Common.immutableOrEmpty(mDirections);
}
@Override
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/LaneDirection.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/LaneDirection.java
index 85338a0..94c2976 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/LaneDirection.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/LaneDirection.java
@@ -108,8 +108,8 @@
* Builder for creating a {@link LaneDirection}
*/
public static final class Builder {
- EnumWrapper<Shape> mShape;
- boolean mHighlighted;
+ private EnumWrapper<Shape> mShape;
+ private boolean mHighlighted;
/**
* Sets the {@link Shape} of this lane direction, and any fallback values that could be used
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/Maneuver.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/Maneuver.java
index cd6705b..c607ab2 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/Maneuver.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/Maneuver.java
@@ -21,6 +21,7 @@
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelable;
@@ -286,6 +287,8 @@
EnumWrapper<Type> mType;
@ParcelField(2)
int mRoundaboutExitNumber;
+ @ParcelField(3)
+ ImageReference mIcon;
/**
* Used by {@link VersionedParcelable}
@@ -300,17 +303,20 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- Maneuver(@NonNull EnumWrapper<Type> type, int roundaboutExitNumber) {
+ Maneuver(@NonNull EnumWrapper<Type> type, int roundaboutExitNumber,
+ @Nullable ImageReference icon) {
mType = type;
mRoundaboutExitNumber = roundaboutExitNumber;
+ mIcon = icon;
}
/**
* Builder for creating a {@link Maneuver}
*/
public static final class Builder {
- EnumWrapper<Type> mType;
- int mRoundaboutExitNumber;
+ private EnumWrapper<Type> mType;
+ private int mRoundaboutExitNumber;
+ private ImageReference mIcon;
/**
@@ -346,11 +352,21 @@
}
/**
+ * Sets a reference to an image presenting this maneuver. The provided image must be
+ * optimized to be presented in a square canvas (aspect ratio of 1:1).
+ */
+ @NonNull
+ public Builder setIcon(@Nullable ImageReference icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
* Returns a {@link Maneuver} built with the provided information.
*/
@NonNull
public Maneuver build() {
- return new Maneuver(mType, mRoundaboutExitNumber);
+ return new Maneuver(mType, mRoundaboutExitNumber, mIcon);
}
}
@@ -375,6 +391,16 @@
return mRoundaboutExitNumber;
}
+ /**
+ * Returns a reference to an image representing this maneuver, or null if image representation
+ * is not available. This image is optimized to be displayed in a square canvas (aspect ratio of
+ * 1:1).
+ */
+ @Nullable
+ public ImageReference getIcon() {
+ return mIcon;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -385,18 +411,20 @@
}
Maneuver maneuver = (Maneuver) o;
return getRoundaboutExitNumber() == maneuver.getRoundaboutExitNumber()
- && Objects.equals(getType(), maneuver.getType());
+ && Objects.equals(getType(), maneuver.getType())
+ && Objects.equals(getIcon(), maneuver.getIcon());
}
@Override
public int hashCode() {
- return Objects.hash(getType(), getRoundaboutExitNumber());
+ return Objects.hash(getType(), getRoundaboutExitNumber(), getIcon());
}
// DefaultLocale suppressed as this method is only offered for debugging purposes.
@SuppressLint("DefaultLocale")
@Override
public String toString() {
- return String.format("{type: %s, roundaboutExitNumer: %d}", mType, mRoundaboutExitNumber);
+ return String.format("{type: %s, roundaboutExitNumer: %d, icon: %s}", mType,
+ mRoundaboutExitNumber, mIcon);
}
}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/NavigationState.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/NavigationState.java
index 0074fbe..93f3888 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/NavigationState.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/NavigationState.java
@@ -31,7 +31,6 @@
import androidx.versionedparcelable.VersionedParcelize;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -87,8 +86,8 @@
@NonNull List<Destination> destinations,
@Nullable Segment currentSegment,
@NonNull EnumWrapper<ServiceStatus> serviceStatus) {
- mSteps = Collections.unmodifiableList(new ArrayList<>(steps));
- mDestinations = Collections.unmodifiableList(new ArrayList<>(destinations));
+ mSteps = new ArrayList<>(steps);
+ mDestinations = new ArrayList<>(destinations);
mCurrentSegment = currentSegment;
mServiceStatus = Preconditions.checkNotNull(serviceStatus);
}
@@ -97,10 +96,10 @@
* Builder for creating a {@link NavigationState}
*/
public static final class Builder {
- List<Step> mSteps = new ArrayList<>();
- List<Destination> mDestinations = new ArrayList<>();
- Segment mCurrentSegment;
- EnumWrapper<ServiceStatus> mServiceStatus = new EnumWrapper<>();
+ private List<Step> mSteps = new ArrayList<>();
+ private List<Destination> mDestinations = new ArrayList<>();
+ private Segment mCurrentSegment;
+ private EnumWrapper<ServiceStatus> mServiceStatus = new EnumWrapper<>();
/**
* Add a navigation step. Steps should be provided in order of execution. It is up to the
@@ -168,7 +167,7 @@
*/
@NonNull
public List<Step> getSteps() {
- return Common.nonNullOrEmpty(mSteps);
+ return Common.immutableOrEmpty(mSteps);
}
/**
@@ -177,7 +176,7 @@
*/
@NonNull
public List<Destination> getDestinations() {
- return Common.nonNullOrEmpty(mDestinations);
+ return Common.immutableOrEmpty(mDestinations);
}
/**
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/RichText.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichText.java
new file mode 100644
index 0000000..ab488d6
--- /dev/null
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichText.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018 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 androidx.car.cluster.navigation;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Preconditions;
+import androidx.versionedparcelable.ParcelField;
+import androidx.versionedparcelable.VersionedParcelable;
+import androidx.versionedparcelable.VersionedParcelize;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Immutable sequence of graphic elements (e.g.: text, images) to be displayed one after another in
+ * the same way as a {@link CharSequence} would. Elements in this sequence are represented by
+ * {@link RichTextElement} instances.
+ */
+@VersionedParcelize
+public class RichText implements VersionedParcelable {
+ @ParcelField(1)
+ List<RichTextElement> mElements;
+
+ /**
+ * Used by {@link VersionedParcelable}
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ RichText() {
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ RichText(@NonNull List<RichTextElement> elements) {
+ mElements = new ArrayList<>(elements);
+ }
+
+ /**
+ * Builder for creating a {@link RichText}
+ */
+ public static final class Builder {
+ private List<RichTextElement> mElements = new ArrayList<>();
+
+ /**
+ * Adds a graphic element to the rich text sequence.
+ *
+ * @param element a graphic element to add to the sequence.
+ * @return this object for chaining
+ */
+ @NonNull
+ public Builder addElement(@NonNull RichTextElement element) {
+ mElements.add(Preconditions.checkNotNull(element));
+ return this;
+ }
+
+ /**
+ * Returns a {@link RichText} built with the provided information.
+ */
+ @NonNull
+ public RichText build() {
+ return new RichText(mElements);
+ }
+ }
+
+ /**
+ * Returns the sequence of graphic elements
+ */
+ @NonNull
+ List<RichTextElement> getElements() {
+ return Common.immutableOrEmpty(mElements);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RichText richText = (RichText) o;
+ return Objects.equals(getElements(), richText.getElements());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getElements());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{elements: %s}", mElements);
+ }
+}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/RichTextElement.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichTextElement.java
new file mode 100644
index 0000000..1343444
--- /dev/null
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichTextElement.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 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 androidx.car.cluster.navigation;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Preconditions;
+import androidx.versionedparcelable.ParcelField;
+import androidx.versionedparcelable.VersionedParcelable;
+import androidx.versionedparcelable.VersionedParcelize;
+
+import java.util.Objects;
+
+/**
+ * An item in a {@link RichText} sequence, acting as a union of different graphic elements that can
+ * be displayed one after another.
+ * <p>
+ * All {@link RichTextElement} must contain a textual representation of its content, which will be
+ * used by consumers incapable of rendering the desired graphic element. A {@link RichTextElement}
+ * can only contain one other graphic element. Consumers must attempt to render such element and
+ * only fallback to text if needed.
+ * <p>
+ * New graphic element types might be added in the future. If such elements are unknown to the
+ * consumer, they will be delivered to the consumer as just text.
+ */
+@VersionedParcelize
+public class RichTextElement implements VersionedParcelable {
+ @ParcelField(1)
+ String mText;
+ @ParcelField(2)
+ ImageReference mImage;
+
+ /**
+ * Used by {@link VersionedParcelable}
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ RichTextElement() {
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public RichTextElement(@NonNull String text, @Nullable ImageReference image) {
+ mText = Preconditions.checkNotNull(text, "A textual representation of this "
+ + "element must be provided.");
+ mImage = image;
+ }
+
+ /**
+ * Builder for creating a {@link RichTextElement}
+ */
+ public static class Builder {
+ private ImageReference mImage;
+
+ /**
+ * Sets an image to be displayed as part of the {@link RichText} sequence. Images in the
+ * same {@link RichText} sequence are expected to be rendered with equal height but variable
+ * width.
+ *
+ * @param image image reference to be used to represent this element, or null if only the
+ * textual representation should be used.
+ * @return this object for chaining
+ */
+ public Builder setImage(@Nullable ImageReference image) {
+ // Note: if new graphic element types are added in the future, this API should enforce
+ // that no more than one of them is set at each moment.
+ mImage = image;
+ return this;
+ }
+
+ /**
+ * Builds a {@link RichTextElement} with the given textual representation, and any other
+ * optional representation provided to this builder. If no other graphic element is provided
+ * or if such graphic element cannot be rendered by the consumer, this text will be used
+ * instead.
+ *
+ * @param text textual representation to use
+ */
+ public RichTextElement build(@NonNull String text) {
+ return new RichTextElement(Preconditions.checkNotNull(text), mImage);
+ }
+ }
+
+ /**
+ * Returns the textual representation of this element
+ */
+ @NonNull
+ String getText() {
+ return Common.nonNullOrEmpty(mText);
+ }
+
+ /**
+ * Returns an image representing this element, or null if the textual representation should be
+ * used instead.
+ */
+ @Nullable
+ ImageReference getImage() {
+ return mImage;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RichTextElement element = (RichTextElement) o;
+ return Objects.equals(getText(), element.getText())
+ && Objects.equals(getImage(), element.getImage());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getText(), getImage());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{text: '%s', image: %s}", mText, mImage);
+ }
+}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/Step.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/Step.java
index 78c89b7..096f29d 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/Step.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/Step.java
@@ -27,7 +27,6 @@
import androidx.versionedparcelable.VersionedParcelize;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -44,6 +43,10 @@
Maneuver mManeuver;
@ParcelField(3)
List<Lane> mLanes;
+ @ParcelField(4)
+ ImageReference mLanesImage;
+ @ParcelField(5)
+ RichText mCue;
/**
* Used by {@link VersionedParcelable}
@@ -58,19 +61,24 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- Step(@Nullable Distance distance, @Nullable Maneuver maneuver, @NonNull List<Lane> lanes) {
+ Step(@Nullable Distance distance, @Nullable Maneuver maneuver, @NonNull List<Lane> lanes,
+ @Nullable ImageReference lanesImage, @Nullable RichText cue) {
mDistance = distance;
mManeuver = maneuver;
- mLanes = Collections.unmodifiableList(new ArrayList<>(lanes));
+ mLanes = new ArrayList<>(lanes);
+ mLanesImage = lanesImage;
+ mCue = cue;
}
/**
* Builder for creating a {@link Step}
*/
public static final class Builder {
- Distance mDistance;
- Maneuver mManeuver;
- List<Lane> mLanes = new ArrayList<>();
+ private Distance mDistance;
+ private Maneuver mManeuver;
+ private List<Lane> mLanes = new ArrayList<>();
+ private ImageReference mLanesImage;
+ private RichText mCue;
/**
* Sets the distance from the current position to the point where this navigation step
@@ -98,6 +106,11 @@
/**
* Adds a road lane configuration to this step. Lanes should be added from left to right.
+ * <p>
+ * If lanes configuration information is available, producers should provide both image (see
+ * {@link #setLanesImage(ImageReference)}) and metadata (through this method) for maximum
+ * interoperability, as some consumers might use images while others might use metadata, or
+ * both.
*
* @return this object for chaining
*/
@@ -108,11 +121,59 @@
}
/**
+ * Sets a reference to an image that represents a complete lanes configuration at this point
+ * in the navigation. The image, if provided, is expected to contain:
+ *
+ * <ul>
+ * <li>A representation of all lanes, one next to the other in a single row.
+ * <li>For each lane, a set of arrows, representing each possible driving directions
+ * (e.g.: straight, left turn, right turn, etc.) within such lane.
+ * <li>Each of such driving directions that would keep the driver within the navigation
+ * route should be highlighted.
+ * </ul>
+ *
+ * Lane configuration images are expected to be displayed in a canvas with fixed height and
+ * variable width.
+ * <p>
+ * If lanes configuration information is available, producers should provide both metadata
+ * (see {@link #addLane(Lane)}) and image (through this method) for maximum
+ * interoperability, as some consumers might use images while others might use metadata, or
+ * both.
+ *
+ * @return this object for chaining
+ */
+ @NonNull
+ public Builder setLanesImage(@Nullable ImageReference lanesImage) {
+ mLanesImage = lanesImage;
+ return this;
+ }
+
+ /**
+ * Sets auxiliary instructions on how complete this navigation step, described as a
+ * {@link RichText} object containing a sequence of texts (e.g.: "towards", "Wallaby way")
+ * and images (e.g.: road badge of a highway).
+ * <p>
+ * If consumers don't have enough space to display the complete content of this
+ * {@link RichText} instance, it is expected they will truncate these instructions by
+ * cutting its end.
+ * <p>
+ * Because of this, it is expected the most important part of these instructions to be
+ * located at the beginning of the sequence.
+ *
+ * @return this object for chaining
+ */
+ @NonNull
+ public Builder setCue(@Nullable RichText cue) {
+ mCue = cue;
+ return this;
+ }
+
+ /**
* Returns a {@link Step} built with the provided information.
*/
@NonNull
public Step build() {
- return new Step(mDistance, mManeuver, mLanes);
+ return new Step(mDistance, mManeuver, mLanes, mLanesImage, mCue);
}
}
@@ -136,11 +197,47 @@
/**
* Returns an unmodifiable list containing the configuration of road lanes at the point where
- * the driver should execute this step. Lane configurations are listed from left to right.
+ * the driver should execute this step, or an empty list if lane configuration metadata is not
+ * available. Lane configurations are listed from left to right.
*/
@NonNull
public List<Lane> getLanes() {
- return Common.nonNullOrEmpty(mLanes);
+ return Common.immutableOrEmpty(mLanes);
+ }
+
+ /**
+ * Returns an image representing the lanes configuration at this point in the navigation, or
+ * null if the lanes configuration image was not provided. The image, if provided, is expected
+ * to contain:
+ *
+ * <ul>
+ * <li>A representation of all lanes, one next to the other in a single row.
+ * <li>For each lane, a set of arrows, representing each possible driving directions
+ * (e.g.: straight, left turn, right turn, etc.) within such lane.
+ * <li>Each of such driving directions that would keep the driver within the navigation
+ * route should be highlighted.
+ * </ul>
+ *
+ * Lane configuration images are expected to be displayed in a canvas with fixed height and
+ * variable width.
+ */
+ @Nullable
+ public ImageReference getLanesImage() {
+ return mLanesImage;
+ }
+
+ /**
+ * Returns auxiliary instructions on how complete this navigation step, described as a
+ * {@link RichText} object containing a sequence of texts (e.g.: "towards", "Wallaby way")
+ * and images (e.g.: road badge of a highway).
+ * <p>
+ * If space is not enough to display the complete content of this {@link RichText} instance,
+ * consumers must display the beginning of these instructions, cutting as much from the end
+ * as needed.
+ */
+ @Nullable
+ public RichText getCue() {
+ return mCue;
}
@Override
@@ -154,17 +251,19 @@
Step step = (Step) o;
return Objects.equals(getManeuver(), step.getManeuver())
&& Objects.equals(getDistance(), step.getDistance())
- && Objects.equals(getLanes(), step.getLanes());
+ && Objects.equals(getLanes(), step.getLanes())
+ && Objects.equals(getLanesImage(), step.getLanesImage())
+ && Objects.equals(getCue(), step.getCue());
}
@Override
public int hashCode() {
- return Objects.hash(getManeuver(), getDistance(), getLanes());
+ return Objects.hash(getManeuver(), getDistance(), getLanes(), getLanesImage(), getCue());
}
@Override
public String toString() {
- return String.format("{maneuver: %s, distance: %s, lanes: %s}", mManeuver, mDistance,
- mLanes);
+ return String.format("{maneuver: %s, distance: %s, lanes: %s, lanesImage: %s, cue: %s}",
+ mManeuver, mDistance, mLanes, mLanesImage, mCue);
}
}
diff --git a/car/core/res/drawable/car_spinner_background.xml b/car/core/res/drawable/car_spinner_background.xml
index 8a14fd2..c03d263c 100644
--- a/car/core/res/drawable/car_spinner_background.xml
+++ b/car/core/res/drawable/car_spinner_background.xml
@@ -18,11 +18,6 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
- <item>
- <shape>
- <solid android:color="?android:attr/colorPrimary"/>
- </shape>
- </item>
<item
android:width="@dimen/car_spinner_dropdown_arrow_size"
android:height="@dimen/car_spinner_dropdown_arrow_size"
diff --git a/car/core/res/layout/preference_material_car.xml b/car/core/res/layout/preference_material_car.xml
index 03abfc9..0c4b20c 100644
--- a/car/core/res/layout/preference_material_car.xml
+++ b/car/core/res/layout/preference_material_car.xml
@@ -36,10 +36,11 @@
android:layout_marginBottom="@dimen/car_padding_2"/>
<LinearLayout
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_toEndOf="@android:id/icon"
+ android:layout_toStartOf="@android:id/widget_frame"
android:layout_centerVertical="true"
android:layout_marginTop="@dimen/car_padding_2"
android:layout_marginBottom="@dimen/car_padding_2">
diff --git a/car/core/res/values/styles.xml b/car/core/res/values/styles.xml
index c72daf8..3770347 100644
--- a/car/core/res/values/styles.xml
+++ b/car/core/res/values/styles.xml
@@ -210,6 +210,21 @@
<item name="navigationIcon">@drawable/ic_nav_arrow_back</item>
</style>
+ <!-- The styling for the default action bar. -->
+ <style name="Widget.Car.ActionBar" parent="Widget.AppCompat.Light.ActionBar.Solid">
+ <item name="titleTextStyle">@style/TextAppearance.Car.Body1</item>
+ <item name="subtitleTextStyle">@style/TextAppearance.Car.Body3</item>
+ </style>
+
+ <!-- The styling for the default action bar that has a dark background. -->
+ <style name="Widget.Car.ActionBar.Dark">
+ <item name="background">@android:color/black</item>
+ <item name="backgroundStacked">@android:color/black</item>
+ <item name="backgroundSplit">@android:color/black</item>
+ <item name="titleTextStyle">@style/TextAppearance.Car.Body1.Light</item>
+ <item name="subtitleTextStyle">@style/TextAppearance.Car.Body3.Light</item>
+ </style>
+
<!-- ================ -->
<!-- CarToolbar Style -->
<!-- ================ -->
diff --git a/car/core/res/values/themes.xml b/car/core/res/values/themes.xml
index e6c232c..cbbaeac 100644
--- a/car/core/res/values/themes.xml
+++ b/car/core/res/values/themes.xml
@@ -21,7 +21,7 @@
<!-- Base style for the Car that will have dark colors for card backgrounds and light color for
text. The colors will not change for night mode. -->
- <style name="Theme.Car.NoActionBar" parent="Theme.AppCompat.NoActionBar">
+ <style name="Theme.Car" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:alertDialogTheme">@style/Theme.Car.Dark.Dialog.Alert</item>
<item name="android:colorAccent">@color/car_accent_light</item>
<item name="android:colorForeground">@color/car_body2_light</item>
@@ -55,6 +55,7 @@
<item name="android:spinnerItemStyle">@style/CarSpinnerItem</item>
<item name="android:spinnerDropDownItemStyle">@style/CarSpinnerItem</item>
<item name="actionBarItemBackground">@drawable/car_card_ripple_background</item>
+ <item name="actionBarStyle">@style/Widget.Car.ActionBar.Dark</item>
<item name="actionBarSize">@dimen/car_app_bar_height</item>
<item name="actionButtonStyle">@style/Widget.Car.ActionButton.Light</item>
<item name="actionMenuTextAppearance">@style/TextAppearance.Car.ActionBar.Menu</item>
@@ -86,6 +87,12 @@
<item name="toolbarStyle">@style/Widget.Car.Toolbar.Light</item>
</style>
+ <!-- Base style for the car that removes the action bar. -->
+ <style name="Theme.Car.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+
<!-- Light theme for the Car. This theme has light colors for card backgrounds and dark
text. These colors will invert for night. -->
<style name="Theme.Car.Light.NoActionBar" parent="Theme.Car.NoActionBar">
diff --git a/car/core/src/main/java/androidx/car/widget/ActionBar.java b/car/core/src/main/java/androidx/car/widget/ActionBar.java
index a2497ad..21aaadb 100644
--- a/car/core/src/main/java/androidx/car/widget/ActionBar.java
+++ b/car/core/src/main/java/androidx/car/widget/ActionBar.java
@@ -341,7 +341,7 @@
TransitionManager.beginDelayedTransition(mRowsContainer, mIsExpanded ? mFadeIn : mFadeOut);
for (int i = 0; i < mNumExtraRowsInUse; i++) {
- mRowsContainer.getChildAt(i).setVisibility(mIsExpanded ? View.VISIBLE : View.GONE);
+ mRowsContainer.getChildAt(i).setVisibility(mIsExpanded ? VISIBLE : GONE);
}
}
diff --git a/car/core/src/main/java/androidx/car/widget/AlphaJumpOverlayView.java b/car/core/src/main/java/androidx/car/widget/AlphaJumpOverlayView.java
index 1546828..8afce1e 100644
--- a/car/core/src/main/java/androidx/car/widget/AlphaJumpOverlayView.java
+++ b/car/core/src/main/java/androidx/car/widget/AlphaJumpOverlayView.java
@@ -109,7 +109,7 @@
*/
public void show() {
mAdapter.onAlphaJumpEnter();
- setVisibility(View.VISIBLE);
+ setVisibility(VISIBLE);
startAnimation(mOpenAnimation);
}
@@ -118,6 +118,6 @@
*/
public void hide() {
startAnimation(mCloseAnimation);
- setVisibility(View.GONE);
+ setVisibility(GONE);
}
}
\ No newline at end of file
diff --git a/car/core/src/main/java/androidx/car/widget/CarToolbar.java b/car/core/src/main/java/androidx/car/widget/CarToolbar.java
index 0f281c1..b941f65 100644
--- a/car/core/src/main/java/androidx/car/widget/CarToolbar.java
+++ b/car/core/src/main/java/androidx/car/widget/CarToolbar.java
@@ -135,7 +135,7 @@
desiredHeight, MeasureSpec.AT_MOST);
int width = 0;
- if (mNavButtonView.getVisibility() != View.GONE) {
+ if (mNavButtonView.getVisibility() != GONE) {
// Size of nav button is fixed.
int measureSpec = MeasureSpec.makeMeasureSpec(mNavButtonIconSize, MeasureSpec.EXACTLY);
mNavButtonView.measure(measureSpec, measureSpec);
@@ -144,7 +144,7 @@
int navWidth = Math.max(mNavButtonContainerWidth, mNavButtonView.getMeasuredWidth());
width += navWidth + getHorizontalMargins(mNavButtonView);
}
- if (mTitleTextView.getVisibility() != View.GONE) {
+ if (mTitleTextView.getVisibility() != GONE) {
measureChild(mTitleTextView, widthMeasureSpec, width, childHeightMeasureSpec, 0);
width += mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
}
@@ -158,7 +158,7 @@
int height = bottom - top;
int layoutLeft = getPaddingLeft();
- if (mNavButtonView.getVisibility() != View.GONE) {
+ if (mNavButtonView.getVisibility() != GONE) {
// Nav button is centered in container.
int navButtonWidth = mNavButtonView.getMeasuredWidth();
int containerWidth = Math.max(mNavButtonContainerWidth, navButtonWidth);
@@ -168,7 +168,7 @@
layoutLeft += containerWidth;
}
- if (mTitleTextView.getVisibility() != View.GONE) {
+ if (mTitleTextView.getVisibility() != GONE) {
layoutViewVerticallyCentered(mTitleTextView, layoutLeft, height);
}
}
@@ -199,11 +199,11 @@
*/
public void setNavigationIcon(@Nullable Icon icon) {
if (icon == null) {
- mNavButtonView.setVisibility(View.GONE);
+ mNavButtonView.setVisibility(GONE);
mNavButtonView.setImageDrawable(null);
return;
}
- mNavButtonView.setVisibility(View.VISIBLE);
+ mNavButtonView.setVisibility(VISIBLE);
mNavButtonView.setImageDrawable(icon.loadDrawable(getContext()));
}
@@ -267,7 +267,7 @@
public void setTitle(CharSequence title) {
mTitleText = title;
mTitleTextView.setText(title);
- mTitleTextView.setVisibility(TextUtils.isEmpty(title) ? View.GONE : View.VISIBLE);
+ mTitleTextView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
}
/**
diff --git a/car/core/src/main/java/androidx/car/widget/PagedListView.java b/car/core/src/main/java/androidx/car/widget/PagedListView.java
index 10ffded..e832b46 100644
--- a/car/core/src/main/java/androidx/car/widget/PagedListView.java
+++ b/car/core/src/main/java/androidx/car/widget/PagedListView.java
@@ -982,7 +982,7 @@
return;
}
- mScrollBarView.setVisibility(View.VISIBLE);
+ mScrollBarView.setVisibility(VISIBLE);
mScrollBarView.setUpEnabled(!isAtStart);
mScrollBarView.setDownEnabled(!isAtEnd);
@@ -1022,7 +1022,7 @@
if ((isAtStart && isAtEnd) || layoutManager == null || layoutManager.getItemCount() == 0) {
mScrollBarView.setVisibility(View.INVISIBLE);
} else {
- mScrollBarView.setVisibility(View.VISIBLE);
+ mScrollBarView.setVisibility(VISIBLE);
}
mScrollBarView.setUpEnabled(!isAtStart);
mScrollBarView.setDownEnabled(!isAtEnd);
diff --git a/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java b/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
index f33b8f7..3542965 100644
--- a/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
+++ b/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
@@ -178,7 +178,7 @@
}
void setShowAlphaJump(boolean show) {
- mAlphaJumpButton.setVisibility(show ? View.VISIBLE : View.GONE);
+ mAlphaJumpButton.setVisibility(show ? VISIBLE : GONE);
}
/** Returns {@code true} if the scroll bar thumb is visible */
@@ -192,7 +192,7 @@
*/
public void setScrollbarThumbEnabled(boolean show) {
mShowScrollBarThumb = show;
- mScrollThumb.setVisibility(mShowScrollBarThumb ? View.VISIBLE : View.GONE);
+ mScrollThumb.setVisibility(mShowScrollBarThumb ? VISIBLE : GONE);
}
/**
@@ -223,7 +223,7 @@
}
// If the scroll bars aren't visible, then no need to update.
- if (getVisibility() == View.GONE || range == 0) {
+ if (getVisibility() == GONE || range == 0) {
return;
}
@@ -257,7 +257,7 @@
*/
void setParametersInLayout(int range, int offset, int extent) {
// If the scroll bars aren't visible, then no need to update.
- if (getVisibility() == View.GONE || range == 0) {
+ if (getVisibility() == GONE || range == 0) {
return;
}
diff --git a/cardview/build.gradle b/cardview/build.gradle
index c950dac..7df2ca3 100644
--- a/cardview/build.gradle
+++ b/cardview/build.gradle
@@ -18,7 +18,7 @@
supportLibrary {
name = "Android Support CardView v7"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.CARDVIEW
mavenGroup = LibraryGroups.CARDVIEW
inceptionYear = "2011"
description = "Android Support CardView v7"
diff --git a/collection/build.gradle b/collection/build.gradle
index 11fac32..4bc2d3a 100644
--- a/collection/build.gradle
+++ b/collection/build.gradle
@@ -23,7 +23,7 @@
}
dependencies {
- compile(project(":annotation"))
+ compile("androidx.annotation:annotation:1.0.0")
annotationProcessor(NULLAWAY)
testCompile(JUNIT)
}
diff --git a/content/build.gradle b/content/build.gradle
index abc6fe7..c2a5c78 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -35,7 +35,7 @@
supportLibrary {
name = "Android Support Content"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.CONTENTPAGER
mavenGroup = LibraryGroups.CONTENTPAGER
inceptionYear = "2017"
description = "Library providing support for paging across content exposed via a ContentProvider. Use of this library allows a client to avoid expensive interprocess \"cursor window swaps\" on the UI thread."
diff --git a/core/build.gradle b/core/build.gradle
index 6f57be3..2fb9f51 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -11,6 +11,8 @@
api("androidx.collection:collection:1.0.0")
api(ARCH_LIFECYCLE_RUNTIME, libs.exclude_annotations_transitive)
api project(':versionedparcelable')
+ api(GUAVA_LISTENABLE_FUTURE)
+ implementation(project(":concurrent:concurrent-futures"))
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(TEST_RULES)
diff --git a/core/ktx/build.gradle b/core/ktx/build.gradle
index b627dec..0d3473e 100644
--- a/core/ktx/build.gradle
+++ b/core/ktx/build.gradle
@@ -25,12 +25,13 @@
androidTestImplementation(TEST_RULES)
androidTestImplementation(TRUTH)
androidTestImplementation(project(":internal-testutils-ktx"))
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
supportLibrary {
name = "Core Kotlin Extensions"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.CORE_KTX
mavenGroup = LibraryGroups.CORE
inceptionYear = "2018"
description = "Kotlin extensions for 'core' artifact"
diff --git a/core/src/androidTest/java/androidx/core/content/pm/ShareTargetXmlParserTest.java b/core/src/androidTest/java/androidx/core/content/pm/ShareTargetXmlParserTest.java
new file mode 100644
index 0000000..692b84d
--- /dev/null
+++ b/core/src/androidTest/java/androidx/core/content/pm/ShareTargetXmlParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 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 androidx.core.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ShareTargetXmlParserTest {
+
+ private static final ArrayList<ShareTargetCompat> sExpectedValues = new ArrayList<>();
+
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ mContext = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ initExpectedValues();
+ }
+
+ private void initExpectedValues() {
+ // These values must exactly match the content of shortcuts.xml resource
+ sExpectedValues.add(new ShareTargetCompat(
+ new ShareTargetCompat.TargetData[]{
+ new ShareTargetCompat.TargetData("http", "www.google.com", "1234",
+ "somePath", "somePathPattern", "somePathPrefix", "text/plain")},
+ "com.test.googlex.directshare.TestActivity1",
+ new String[]{"com.test.googlex.category.CATEGORY1",
+ "com.test.googlex.category.CATEGORY2"}));
+ sExpectedValues.add(new ShareTargetCompat(
+ new ShareTargetCompat.TargetData[]{
+ new ShareTargetCompat.TargetData(null, null, null, null, null, null,
+ "video/mp4"),
+ new ShareTargetCompat.TargetData("content", null, null, null, null, null,
+ "video/*")},
+ "com.test.googlex.directshare.TestActivity5",
+ new String[]{"com.test.googlex.category.CATEGORY5",
+ "com.test.googlex.category.CATEGORY6"}));
+ }
+
+ private void assertShareTargetEquals(ShareTargetCompat expected, ShareTargetCompat actual) {
+ assertEquals(expected.mTargetData.length, actual.mTargetData.length);
+ for (int i = 0; i < expected.mTargetData.length; i++) {
+ assertEquals(expected.mTargetData[i].mScheme, actual.mTargetData[i].mScheme);
+ assertEquals(expected.mTargetData[i].mHost, actual.mTargetData[i].mHost);
+ assertEquals(expected.mTargetData[i].mPort, actual.mTargetData[i].mPort);
+ assertEquals(expected.mTargetData[i].mPath, actual.mTargetData[i].mPath);
+ assertEquals(expected.mTargetData[i].mPathPrefix, actual.mTargetData[i].mPathPrefix);
+ assertEquals(expected.mTargetData[i].mPathPattern, actual.mTargetData[i].mPathPattern);
+ assertEquals(expected.mTargetData[i].mMimeType, actual.mTargetData[i].mMimeType);
+ }
+
+ assertEquals(expected.mTargetClass, actual.mTargetClass);
+
+ assertEquals(expected.mCategories.length, actual.mCategories.length);
+ for (int i = 0; i < expected.mCategories.length; i++) {
+ assertEquals(expected.mCategories[i], actual.mCategories[i]);
+ }
+ }
+
+ /**
+ * Tests if ShareTargetXmlParser is able to:
+ * a) locate the xml resource and read it
+ * b) ignore the legacy shortcut definitions if any
+ * c) drop incomplete share-target definitions
+ * d) read and return the expected share-targets
+ */
+ @Test
+ public void testGetShareTargets() {
+ ArrayList<ShareTargetCompat> shareTargets = ShareTargetXmlParser.getShareTargets(mContext);
+
+ assertNotNull(shareTargets);
+ assertEquals(sExpectedValues.size(), shareTargets.size());
+ for (int i = 0; i < sExpectedValues.size(); i++) {
+ assertShareTargetEquals(sExpectedValues.get(i), shareTargets.get(i));
+ }
+ }
+}
diff --git a/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatSaverTest.java b/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatSaverTest.java
new file mode 100644
index 0000000..3cbb9553
--- /dev/null
+++ b/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatSaverTest.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2018 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 androidx.core.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+
+import androidx.core.app.Person;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.core.test.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ShortcutInfoCompatSaverTest {
+
+ private static final String ID_SHORTCUT_RESOURCE_ICON = "shortcut-resource-icon";
+ private static final String ID_SHORTCUT_BITMAP_ICON = "shortcut-bitmap-icon";
+ private static final String ID_SHORTCUT_ADAPTIVE_BITMAP_ICON = "shortcut-adaptive-bitmap-icon";
+ private static final String ID_SHORTCUT_NO_ICON = "shortcut-no-icon";
+
+ private Context mContext;
+ private ShortcutInfoCompatSaver mShortcutInfoSaver;
+ private ExecutorService mCacheUpdateService;
+ private ExecutorService mDiskIoService;
+
+ private List<ShortcutInfoCompat> mTestShortcuts = new ArrayList<>();
+
+ private IconCompat mTestResourceIcon;
+ private IconCompat mTestBitmapIcon;
+ private IconCompat mTestAdaptiveBitmapIcon;
+
+ @Before
+ public void setup() {
+ mContext = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ mCacheUpdateService = ShortcutInfoCompatSaver.createExecutorService();
+ mDiskIoService = ShortcutInfoCompatSaver.createExecutorService();
+ mShortcutInfoSaver = new ShortcutInfoCompatSaver(mContext, mCacheUpdateService,
+ mDiskIoService);
+ catchAsyncExceptions(mShortcutInfoSaver.removeAllShortcuts());
+
+ HashSet<String> testCategories = new HashSet<>();
+ testCategories.add("TestCategory1");
+ testCategories.add("TestCategory2");
+
+ Person[] testPersons = {
+ new Person.Builder().setName("test person 1").build(),
+ new Person.Builder().setName("test person 2").build()};
+
+ Intent[] testIntents = {new Intent("TestAction1"), new Intent("TestAction2")};
+ testIntents[1].setClassName("test package", "test class");
+
+ Bitmap redBitmap = Bitmap.createBitmap(200, 150, Bitmap.Config.ARGB_8888);
+ redBitmap.eraseColor(Color.RED);
+ Bitmap blueBitmap = Bitmap.createBitmap(150, 200, Bitmap.Config.ARGB_8888);
+ blueBitmap.eraseColor(Color.BLUE);
+
+ mTestResourceIcon = IconCompat.createWithResource(mContext, R.drawable.bmp_test);
+ mTestBitmapIcon = IconCompat.createWithBitmap(redBitmap);
+ mTestAdaptiveBitmapIcon = IconCompat.createWithAdaptiveBitmap(blueBitmap);
+
+ mTestShortcuts.add(new ShortcutInfoCompat.Builder(mContext, ID_SHORTCUT_RESOURCE_ICON)
+ .setShortLabel("test short label 1")
+ .setLongLabel("test long label 1")
+ .setDisabledMessage("test disabled message 1")
+ .setLongLived()
+ .setPersons(testPersons)
+ .setCategories(testCategories)
+ .setActivity(new ComponentName("test package", "test class"))
+ .setAlwaysBadged()
+ .setIcon(mTestResourceIcon)
+ .setIntents(testIntents)
+ .build());
+ mTestShortcuts.add(new ShortcutInfoCompat.Builder(mContext, ID_SHORTCUT_BITMAP_ICON)
+ .setShortLabel("test short label 2")
+ .setCategories(testCategories)
+ .setActivity(new ComponentName("test package", "test class"))
+ .setIcon(mTestBitmapIcon)
+ .setIntent(testIntents[0])
+ .build());
+ mTestShortcuts.add(new ShortcutInfoCompat.Builder(mContext,
+ ID_SHORTCUT_ADAPTIVE_BITMAP_ICON)
+ .setShortLabel("test short label 3")
+ .setLongLabel("test long label 3")
+ .setDisabledMessage("test disabled message 3")
+ .setCategories(testCategories)
+ .setActivity(new ComponentName("test package", "test class"))
+ .setAlwaysBadged()
+ .setIcon(mTestAdaptiveBitmapIcon)
+ .setIntent(testIntents[1])
+ .build());
+ mTestShortcuts.add(new ShortcutInfoCompat.Builder(mContext, ID_SHORTCUT_NO_ICON)
+ .setShortLabel("test short label 4")
+ .setLongLabel("test long label 4")
+ .setPerson(testPersons[0])
+ .setCategories(testCategories)
+ .setIntents(testIntents)
+ .build());
+ mTestShortcuts.add(new ShortcutInfoCompat.Builder(mContext, "shortcut-no-category")
+ .setShortLabel("test short label 5")
+ .setActivity(new ComponentName("test package", "test class"))
+ .setIcon(mTestResourceIcon)
+ .setIntents(testIntents)
+ .build());
+ }
+
+ @After
+ public void tearDown() {
+ try {
+ mShortcutInfoSaver.removeAllShortcuts().get();
+ } catch (Exception e) {
+ /* Ignore */
+ }
+ }
+
+ private List<ShortcutInfoCompat> testShortcutsWithCategories() {
+ List<ShortcutInfoCompat> shortcuts = new ArrayList<>();
+ for (ShortcutInfoCompat item : mTestShortcuts) {
+ Set<String> categories = item.getCategories();
+ if (categories != null && !categories.isEmpty()) {
+ shortcuts.add(item);
+ }
+ }
+ return shortcuts;
+ }
+
+ private void assertShortcutsListEquals(List<ShortcutInfoCompat> expected,
+ List<ShortcutInfoCompat> actual) {
+ assertNotNull(expected);
+ assertNotNull(actual);
+ assertEquals(expected.size(), actual.size());
+
+ // The order in the lists is not important
+ for (ShortcutInfoCompat expectedShortcut : expected) {
+ boolean exists = false;
+ for (ShortcutInfoCompat actualShortcut : actual) {
+ if (expectedShortcut.getId().equals(actualShortcut.getId())) {
+ assertShortcutEquals(expectedShortcut, actualShortcut);
+ exists = true;
+ }
+ }
+ assertTrue(exists);
+ }
+ }
+
+ private void assertShortcutEquals(ShortcutInfoCompat expected, ShortcutInfoCompat actual) {
+ assertNotNull(expected);
+ assertNotNull(actual);
+
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getDisabledMessage(), actual.getDisabledMessage());
+ assertEquals(expected.getLongLabel(), actual.getLongLabel());
+ assertEquals(expected.getShortLabel(), actual.getShortLabel());
+
+ if (expected.getActivity() == null) {
+ assertNull(actual.getActivity());
+ } else {
+ assertNotNull(actual.getActivity());
+ assertEquals(expected.getActivity().getPackageName(),
+ actual.getActivity().getPackageName());
+ assertEquals(expected.getActivity().getClassName(),
+ actual.getActivity().getClassName());
+ }
+
+ if (expected.getCategories() == null) {
+ assertNull(actual.getCategories());
+ } else {
+ assertNotNull(actual.getCategories());
+ Set<String> expectedCats = expected.getCategories();
+ Set<String> actualCats = actual.getCategories();
+ assertEquals(expectedCats.size(), actualCats.size());
+ assertTrue(actualCats.containsAll(expectedCats));
+ }
+
+ Intent[] expIntents = expected.getIntents();
+ Intent[] actIntents = actual.getIntents();
+ assertEquals(expIntents.length, actIntents.length);
+ for (int i = 0; i < expIntents.length; i++) {
+ assertEquals(expIntents[i].getAction(), actIntents[i].getAction());
+ if (expIntents[i].getComponent() == null) {
+ assertNull(actIntents[i].getComponent());
+ } else {
+ assertEquals(expIntents[i].getComponent().flattenToString(),
+ actIntents[i].getComponent().flattenToString());
+ }
+ }
+ }
+
+ @Test
+ public void testGetInstance() {
+ ShortcutInfoCompatSaver saver = ShortcutInfoCompatSaver.getInstance(mContext);
+ assertNotNull(saver);
+ assertEquals(saver, ShortcutInfoCompatSaver.getInstance(mContext));
+ }
+
+ @Test
+ public void testGetShortcuts_noShortcuts() throws Exception {
+ List<ShortcutInfoCompat> shortcuts = mShortcutInfoSaver.getShortcuts();
+
+ assertNotNull(shortcuts);
+ assertTrue(shortcuts.isEmpty());
+ }
+
+ @Test
+ public void testAddShortcuts_skipShortcutsWithNoCategories() throws Exception {
+ catchAsyncExceptions(mShortcutInfoSaver.addShortcuts(mTestShortcuts));
+ assertShortcutsListEquals(testShortcutsWithCategories(), mShortcutInfoSaver.getShortcuts());
+ }
+
+ @Test
+ public void testAddShortcuts_forceReload() throws Exception {
+ ListenableFuture<?> future = mShortcutInfoSaver.addShortcuts(mTestShortcuts);
+ catchAsyncExceptions(future);
+ forceReloadFromDisk(future);
+
+ assertShortcutsListEquals(testShortcutsWithCategories(), mShortcutInfoSaver.getShortcuts());
+ }
+
+ @Test
+ public void testAddShortcuts_incrementalAddWithIdOverlap() throws Exception {
+ List<ShortcutInfoCompat> firstBatch = new ArrayList<>();
+ firstBatch.add(mTestShortcuts.get(0));
+ firstBatch.add(mTestShortcuts.get(1));
+ List<ShortcutInfoCompat> secondBatch = new ArrayList<>();
+ secondBatch.add(mTestShortcuts.get(1));
+ secondBatch.add(mTestShortcuts.get(2));
+ List<ShortcutInfoCompat> allShortcuts = new ArrayList<>();
+ allShortcuts.add(mTestShortcuts.get(0));
+ allShortcuts.add(mTestShortcuts.get(1));
+ allShortcuts.add(mTestShortcuts.get(2));
+
+ catchAsyncExceptions(mShortcutInfoSaver.addShortcuts(firstBatch));
+ ListenableFuture<Void> future = mShortcutInfoSaver.addShortcuts(secondBatch);
+ catchAsyncExceptions(future);
+ assertShortcutsListEquals(allShortcuts, mShortcutInfoSaver.getShortcuts());
+
+ forceReloadFromDisk(future);
+ assertShortcutsListEquals(allShortcuts, mShortcutInfoSaver.getShortcuts());
+ }
+
+ @Test
+ public void testRemoveShortcuts() throws Exception {
+ catchAsyncExceptions(mShortcutInfoSaver.addShortcuts(mTestShortcuts));
+ ArrayList<String> removeIds = new ArrayList<>();
+ removeIds.add(mTestShortcuts.get(1).getId());
+ removeIds.add(mTestShortcuts.get(3).getId());
+
+ ListenableFuture<?> future = mShortcutInfoSaver.removeShortcuts(removeIds);
+ catchAsyncExceptions(future);
+
+ mTestShortcuts.remove(3);
+ mTestShortcuts.remove(1);
+ assertShortcutsListEquals(testShortcutsWithCategories(), mShortcutInfoSaver.getShortcuts());
+
+ forceReloadFromDisk(future);
+ assertShortcutsListEquals(testShortcutsWithCategories(), mShortcutInfoSaver.getShortcuts());
+ }
+
+ @Test
+ public void testRemoveAllShortcuts() throws Exception {
+ catchAsyncExceptions(mShortcutInfoSaver.addShortcuts(mTestShortcuts));
+ assertShortcutsListEquals(testShortcutsWithCategories(), mShortcutInfoSaver.getShortcuts());
+
+ ListenableFuture<?> future = mShortcutInfoSaver.removeAllShortcuts();
+ catchAsyncExceptions(future);
+ assertTrue(mShortcutInfoSaver.getShortcuts().isEmpty());
+
+ forceReloadFromDisk(future);
+ assertTrue(mShortcutInfoSaver.getShortcuts().isEmpty());
+ }
+
+ @Test
+ public void verifyIconsAreNotKeptInMemory() throws Exception {
+ ListenableFuture<?> future = mShortcutInfoSaver.addShortcuts(mTestShortcuts);
+ catchAsyncExceptions(future);
+ forceReloadFromDisk(future);
+
+ List<ShortcutInfoCompat> shortcuts = mShortcutInfoSaver.getShortcuts();
+ for (ShortcutInfoCompat item : shortcuts) {
+ assertNull(item.mIcon);
+ }
+ }
+
+ @Test
+ public void testGetShortcutIcon() throws Exception {
+ catchAsyncExceptions(mShortcutInfoSaver.addShortcuts(mTestShortcuts));
+
+ List<ShortcutInfoCompat> shortcuts = mShortcutInfoSaver.getShortcuts();
+ for (ShortcutInfoCompat item : shortcuts) {
+ verifyCorrectIconLoaded(item.getId(), mShortcutInfoSaver.getShortcutIcon(item.getId()));
+ }
+ }
+
+ @Test
+ public void testGetShortcutIcon_forceReload() throws Exception {
+ ListenableFuture<?> future = mShortcutInfoSaver.addShortcuts(mTestShortcuts);
+ catchAsyncExceptions(future);
+ forceReloadFromDisk(future);
+
+ List<ShortcutInfoCompat> shortcuts = mShortcutInfoSaver.getShortcuts();
+ for (ShortcutInfoCompat item : shortcuts) {
+ verifyCorrectIconLoaded(item.getId(), mShortcutInfoSaver.getShortcutIcon(item.getId()));
+ }
+ }
+
+ @Test
+ public void testGetShortcutIcon_unknownId() throws Exception {
+ catchAsyncExceptions(mShortcutInfoSaver.addShortcuts(mTestShortcuts));
+
+ assertNull(mShortcutInfoSaver.getShortcutIcon("unknown-id"));
+ }
+
+ private void verifyCorrectIconLoaded(String id, IconCompat icon) throws Exception {
+ switch (id) {
+ case ID_SHORTCUT_RESOURCE_ICON:
+ assertNotNull(icon);
+ assertEquals(Icon.TYPE_RESOURCE, icon.getType());
+ assertEquals(mTestResourceIcon.getResId(), icon.getResId());
+ break;
+ case ID_SHORTCUT_BITMAP_ICON:
+ assertNotNull(icon);
+ assertEquals(Icon.TYPE_BITMAP, icon.getType());
+ assertEquals(getCenterColor(mTestBitmapIcon), getCenterColor(icon));
+ break;
+ case ID_SHORTCUT_ADAPTIVE_BITMAP_ICON:
+ assertNotNull(icon);
+ // Adaptive icons are restored from disk as legacy (non-adaptive) icons. If icon is
+ // still waiting to be saved, the original Adaptive Icon will be returned.
+ assertTrue(icon.getType() == Icon.TYPE_BITMAP
+ || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP);
+ assertEquals(getCenterColor(mTestAdaptiveBitmapIcon), getCenterColor(icon));
+ break;
+ case ID_SHORTCUT_NO_ICON:
+ assertNull(icon);
+ break;
+ default:
+ throw new Exception("Unknown shortcut Id: " + id);
+ }
+ }
+
+ private int getCenterColor(IconCompat icon) {
+ assertNotNull(icon);
+ Bitmap bitmap = icon.getBitmap();
+ assertNotNull(bitmap);
+ return bitmap.getPixel(bitmap.getWidth() / 2, bitmap.getHeight() / 2);
+ }
+
+ private void forceReloadFromDisk(ListenableFuture<?> lastFuture) throws Exception {
+ // Wait until the last async operation is finished.
+ lastFuture.get();
+
+ mCacheUpdateService = ShortcutInfoCompatSaver.createExecutorService();
+ mDiskIoService = ShortcutInfoCompatSaver.createExecutorService();
+ mShortcutInfoSaver = new ShortcutInfoCompatSaver(mContext, mCacheUpdateService,
+ mDiskIoService);
+ }
+
+ private void catchAsyncExceptions(final ListenableFuture<?> future) {
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ future.get();
+ } catch (Exception e) {
+ throw new RuntimeException("Async operation failed", e);
+ }
+ }
+ }, new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ // Run in the current thread
+ command.run();
+ }
+ });
+ }
+}
diff --git a/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java b/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
index a603f80..2fa6579 100644
--- a/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
+++ b/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
@@ -133,6 +133,31 @@
}
@Test
+ public void testBuilder_copyConstructor() {
+ String longLabel = "Test long label";
+ ComponentName activity = new ComponentName("Package name", "Class name");
+ String disabledMessage = "Test disabled message";
+ Set<String> categories = new HashSet<>();
+ categories.add("cat1");
+ categories.add("cat2");
+ ShortcutInfoCompat compat = mBuilder
+ .setActivity(activity)
+ .setCategories(categories)
+ .setDisabledMessage(disabledMessage)
+ .setLongLabel(longLabel)
+ .build();
+
+ ShortcutInfoCompat copyCompat = new ShortcutInfoCompat.Builder(compat).build();
+ assertEquals(TEST_SHORTCUT_ID, copyCompat.getId());
+ assertEquals(TEST_SHORTCUT_SHORT_LABEL, copyCompat.getShortLabel());
+ assertEquals(mAction, copyCompat.getIntent());
+ assertEquals(longLabel, copyCompat.getLongLabel());
+ assertEquals(disabledMessage, copyCompat.getDisabledMessage());
+ assertEquals(activity, copyCompat.getActivity());
+ assertEquals(categories, copyCompat.getCategories());
+ }
+
+ @Test
public void testBuilder_getters() {
String longLabel = "Test long label";
ComponentName activity = new ComponentName("Package name", "Class name");
diff --git a/core/src/androidTest/java/androidx/core/text/PrecomputedTextCompatTest.java b/core/src/androidTest/java/androidx/core/text/PrecomputedTextCompatTest.java
index 999f950..4e36e59 100644
--- a/core/src/androidTest/java/androidx/core/text/PrecomputedTextCompatTest.java
+++ b/core/src/androidTest/java/androidx/core/text/PrecomputedTextCompatTest.java
@@ -158,6 +158,57 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 23)
+ public void testParams_equalsWithoutTextDirection() {
+ final Params base = new Params.Builder(PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setTextDirection(LTR).build();
+
+ assertTrue(base.equalsWithoutTextDirection(base));
+
+ Params other = new Params.Builder(PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setTextDirection(LTR).build();
+ assertTrue(base.equalsWithoutTextDirection(other));
+ assertTrue(other.equalsWithoutTextDirection(base));
+ assertEquals(base.hashCode(), other.hashCode());
+
+ other = new Params.Builder(PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setTextDirection(LTR).build();
+ assertFalse(base.equalsWithoutTextDirection(other));
+ assertFalse(other.equalsWithoutTextDirection(base));
+
+ other = new Params.Builder(PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .setTextDirection(LTR).build();
+ assertFalse(base.equalsWithoutTextDirection(other));
+ assertFalse(other.equalsWithoutTextDirection(base));
+
+
+ other = new Params.Builder(PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setTextDirection(RTL).build();
+ assertTrue(base.equalsWithoutTextDirection(other));
+ assertTrue(other.equalsWithoutTextDirection(base));
+
+
+ TextPaint anotherPaint = new TextPaint(PAINT);
+ anotherPaint.setTextSize(PAINT.getTextSize() * 2.0f);
+ other = new Params.Builder(anotherPaint)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setTextDirection(LTR).build();
+ assertFalse(base.equalsWithoutTextDirection(other));
+ assertFalse(other.equalsWithoutTextDirection(base));
+ }
+
+ @Test
public void testParams_equals2() {
final Params base = new Params.Builder(PAINT).build();
@@ -178,6 +229,24 @@
}
@Test
+ public void testParams_equalsWithoutTextDirection2() {
+ final Params base = new Params.Builder(PAINT).build();
+
+ assertTrue(base.equalsWithoutTextDirection(base));
+
+ Params other = new Params.Builder(PAINT).build();
+ assertTrue(base.equalsWithoutTextDirection(other));
+ assertTrue(other.equalsWithoutTextDirection(base));
+ assertEquals(base.hashCode(), other.hashCode());
+
+ TextPaint paint = new TextPaint(PAINT);
+ paint.setTextSize(paint.getTextSize() * 2.0f + 1.0f);
+ other = new Params.Builder(paint).build();
+ assertFalse(base.equalsWithoutTextDirection(other));
+ assertFalse(other.equalsWithoutTextDirection(base));
+ }
+
+ @Test
public void testCreate_withNull() {
final Params param = new Params.Builder(PAINT).build();
try {
diff --git a/core/src/androidTest/res/xml/shortcuts.xml b/core/src/androidTest/res/xml/shortcuts.xml
new file mode 100644
index 0000000..547da7e
--- /dev/null
+++ b/core/src/androidTest/res/xml/shortcuts.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2018 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.
+ -->
+
+<!-- Test XML resource to read from, used in ShareTargetXmlParserTest.java -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Legacy shortcut, ignored by ShareTargetXmlParser -->
+ <shortcut>
+ android:shortcutId="dummy_shortcut1"
+ android:enabled="true">
+ <intent
+ android:action="android.intent.action.VIEW"
+ android:targetPackage="com.test.googlex.somepackage"
+ android:targetClass="com.test.googlex.somepackage.someclass" />
+ <categories android:name="android.shortcut.conversation" />
+ </shortcut>
+
+ <!-- Valid share target definition example -->
+ <share-target android:targetClass="com.test.googlex.directshare.TestActivity1">
+ <data
+ android:scheme="http"
+ android:host="www.google.com"
+ android:port="1234"
+ android:path="somePath"
+ android:pathPrefix="somePathPrefix"
+ android:pathPattern="somePathPattern"
+ android:mimeType="text/plain"/>
+ <category android:name="com.test.googlex.category.CATEGORY1"/>
+ <category android:name="com.test.googlex.category.CATEGORY2"/>
+ </share-target>
+
+ <!-- Share target missing data tag, will be dropped -->
+ <share-target android:targetClass="com.test.googlex.directshare.TestActivity">
+ <category android:name="com.test.googlex.category.CATEGORY2"/>
+ </share-target>
+
+ <!-- Share target missing target class, will be dropped -->
+ <share-target>
+ <data
+ android:scheme="file"
+ android:host="www.somehost.com"
+ android:port="1234"
+ android:mimeType="video/*"/>
+ <category android:name="com.test.googlex.category.CATEGORY3"/>
+ </share-target>
+
+ <!-- Legacy shortcut, ignored by ShareTargetXmlParser -->
+ <shortcut>
+ android:shortcutId="dummy_shortcut2"
+ android:enabled="true">
+ <intent
+ android:action="android.intent.action.VIEW"
+ android:targetPackage="com.test.googlex.somepackage"
+ android:targetClass="com.test.googlex.somepackage.someclass" />
+ <categories android:name="android.shortcut.conversation" />
+ </shortcut>
+
+ <!-- Share target missing category, will be dropped -->
+ <share-target android:targetClass="com.test.googlex.directshare.TestActivity">
+ <data
+ android:scheme="content"
+ android:mimeType="text/plain"/>
+ </share-target>
+
+ <!-- Valid share target definition example -->
+ <share-target android:targetClass="com.test.googlex.directshare.TestActivity5">
+ <category android:name="com.test.googlex.category.CATEGORY5"/>
+ <category android:name="com.test.googlex.category.CATEGORY6"/>
+ <data android:mimeType="video/mp4"/>
+ <data
+ android:scheme="content"
+ android:mimeType="video/*"/>
+ </share-target>
+</shortcuts>
+
diff --git a/core/src/main/java/androidx/core/content/pm/ShareTargetCompat.java b/core/src/main/java/androidx/core/content/pm/ShareTargetCompat.java
new file mode 100644
index 0000000..019878e
--- /dev/null
+++ b/core/src/main/java/androidx/core/content/pm/ShareTargetCompat.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018 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 androidx.core.content.pm;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Represents a Share Target definition read from the app's manifest.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+class ShareTargetCompat {
+ static class TargetData {
+ final String mScheme;
+ final String mHost;
+ final String mPort;
+ final String mPath;
+ final String mPathPattern;
+ final String mPathPrefix;
+ final String mMimeType;
+
+ TargetData(String scheme, String host, String port, String path, String pathPattern,
+ String pathPrefix, String mimeType) {
+ mScheme = scheme;
+ mHost = host;
+ mPort = port;
+ mPath = path;
+ mPathPattern = pathPattern;
+ mPathPrefix = pathPrefix;
+ mMimeType = mimeType;
+ }
+ }
+
+ final TargetData[] mTargetData;
+ final String mTargetClass;
+ final String[] mCategories;
+
+ ShareTargetCompat(TargetData[] data, String targetClass, String[] categories) {
+ mTargetData = data;
+ mTargetClass = targetClass;
+ mCategories = categories;
+ }
+}
diff --git a/core/src/main/java/androidx/core/content/pm/ShareTargetXmlParser.java b/core/src/main/java/androidx/core/content/pm/ShareTargetXmlParser.java
new file mode 100644
index 0000000..a3a7f9e
--- /dev/null
+++ b/core/src/main/java/androidx/core/content/pm/ShareTargetXmlParser.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2018 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 androidx.core.content.pm;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.util.Log;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to parse the list of {@link ShareTargetCompat} from app's Xml resource.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+class ShareTargetXmlParser {
+
+ static final String TAG = "ShareTargetXmlParser";
+
+ private static final String TAG_SHARE_TARGET = "share-target";
+ private static final String ATTR_TARGET_CLASS = "targetClass";
+
+ private static final String TAG_DATA = "data";
+ private static final String ATTR_SCHEME = "scheme";
+ private static final String ATTR_HOST = "host";
+ private static final String ATTR_PORT = "port";
+ private static final String ATTR_PATH = "path";
+ private static final String ATTR_PATH_PATTERN = "pathPattern";
+ private static final String ATTR_PATH_PREFIX = "pathPrefix";
+ private static final String ATTR_MIME_TYPE = "mimeType";
+
+ private static final String TAG_CATEGORY = "category";
+ private static final String ATTR_NAME = "name";
+
+ // List of share targets loaded from app's manifest. Will not change while the app is running.
+ private static ArrayList<ShareTargetCompat> sShareTargets;
+
+ @WorkerThread
+ static ArrayList<ShareTargetCompat> getShareTargets(Context context) {
+ if (sShareTargets == null) {
+ sShareTargets = parseShareTargets(context);
+ }
+ return sShareTargets;
+ }
+
+ private ShareTargetXmlParser() {
+ /* Hide the constructor */
+ }
+
+ private static ArrayList<ShareTargetCompat> parseShareTargets(Context context) {
+ ArrayList<ShareTargetCompat> targets = new ArrayList<>();
+ XmlResourceParser parser = getXmlResourceParser(context);
+
+ try {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_SHARE_TARGET)) {
+ ShareTargetCompat target = parseShareTarget(parser);
+ if (target != null) {
+ targets.add(target);
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to parse the Xml resource: ", e);
+ }
+
+ parser.close();
+ return targets;
+ }
+
+ private static XmlResourceParser getXmlResourceParser(Context context) {
+ // TODO: Parse the main manifest to find the right Xml resource for share targets
+ Resources res = context.getResources();
+ return res.getXml(res.getIdentifier("shortcuts", "xml", context.getPackageName()));
+ }
+
+ private static ShareTargetCompat parseShareTarget(XmlResourceParser parser) throws Exception {
+ String targetClass = getAttributeValue(parser, ATTR_TARGET_CLASS);
+ ArrayList<ShareTargetCompat.TargetData> targetData = new ArrayList<>();
+ ArrayList<String> categories = new ArrayList<>();
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case TAG_DATA:
+ targetData.add(parseTargetData(parser));
+ break;
+ case TAG_CATEGORY:
+ categories.add(getAttributeValue(parser, ATTR_NAME));
+ break;
+ }
+ } else if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_SHARE_TARGET)) {
+ break;
+ }
+ }
+ if (targetData.isEmpty() || targetClass == null || categories.isEmpty()) {
+ return null;
+ }
+ return new ShareTargetCompat(
+ targetData.toArray(new ShareTargetCompat.TargetData[targetData.size()]),
+ targetClass, categories.toArray(new String[categories.size()]));
+ }
+
+ private static ShareTargetCompat.TargetData parseTargetData(XmlResourceParser parser) {
+ String scheme = getAttributeValue(parser, ATTR_SCHEME);
+ String host = getAttributeValue(parser, ATTR_HOST);
+ String port = getAttributeValue(parser, ATTR_PORT);
+ String path = getAttributeValue(parser, ATTR_PATH);
+ String pathPattern = getAttributeValue(parser, ATTR_PATH_PATTERN);
+ String pathPrefix = getAttributeValue(parser, ATTR_PATH_PREFIX);
+ String mimeType = getAttributeValue(parser, ATTR_MIME_TYPE);
+
+ return new ShareTargetCompat.TargetData(scheme, host, port, path, pathPattern, pathPrefix,
+ mimeType);
+ }
+
+ private static String getAttributeValue(XmlResourceParser parser, String attribute) {
+ String value = parser.getAttributeValue("http://schemas.android.com/apk/res/android",
+ attribute);
+ if (value == null) {
+ value = parser.getAttributeValue(null, attribute);
+ }
+ return value;
+ }
+}
diff --git a/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java b/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
index bcea227..fdb3948 100644
--- a/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
+++ b/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
@@ -35,6 +35,7 @@
import androidx.core.graphics.drawable.IconCompat;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -272,6 +273,30 @@
}
/**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public Builder(@NonNull ShortcutInfoCompat shortcutInfo) {
+ mInfo = new ShortcutInfoCompat();
+ mInfo.mContext = shortcutInfo.mContext;
+ mInfo.mId = shortcutInfo.mId;
+ mInfo.mIntents = Arrays.copyOf(shortcutInfo.mIntents, shortcutInfo.mIntents.length);
+ mInfo.mActivity = shortcutInfo.mActivity;
+ mInfo.mLabel = shortcutInfo.mLabel;
+ mInfo.mLongLabel = shortcutInfo.mLongLabel;
+ mInfo.mDisabledMessage = shortcutInfo.mDisabledMessage;
+ mInfo.mIcon = shortcutInfo.mIcon;
+ mInfo.mIsAlwaysBadged = shortcutInfo.mIsAlwaysBadged;
+ mInfo.mIsLongLived = shortcutInfo.mIsLongLived;
+ if (shortcutInfo.mPersons != null) {
+ mInfo.mPersons = Arrays.copyOf(shortcutInfo.mPersons, shortcutInfo.mPersons.length);
+ }
+ if (shortcutInfo.mCategories != null) {
+ mInfo.mCategories = new HashSet<>(shortcutInfo.mCategories);
+ }
+ }
+
+ /**
* Sets the short title of a shortcut.
*
* <p>This is a mandatory field when publishing a new shortcut.
diff --git a/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompatSaver.java b/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompatSaver.java
new file mode 100644
index 0000000..d8eee12
--- /dev/null
+++ b/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompatSaver.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2018 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 androidx.core.content.pm;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.WorkerThread;
+import androidx.collection.ArrayMap;
+import androidx.concurrent.futures.ResolvableFuture;
+import androidx.core.content.pm.ShortcutsInfoSerialization.ShortcutContainer;
+import androidx.core.graphics.drawable.IconCompat;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Provides APIs to access and update a persistable list of {@link ShortcutInfoCompat}. This class
+ * keeps an up-to-date cache of the complete list in memory for quick access, except shortcuts'
+ * Icons, which are stored on the disk and only loaded from disk separately if necessary.
+ */
+@RequiresApi(19)
+//TODO: we need Futures.addCallback and CallbackToFutureAdapter, update once they're available
+class ShortcutInfoCompatSaver {
+
+ static final String TAG = "ShortcutInfoCompatSaver";
+
+ private static final String DIRECTORY_TARGETS = "ShortcutInfoCompatSaver_share_targets";
+ private static final String DIRECTORY_BITMAPS = "ShortcutInfoCompatSaver_share_targets_bitmaps";
+ private static final String FILENAME_XML = "targets.xml";
+ // The maximum time background idle threads will wait for new tasks before terminating
+ private static final int EXECUTOR_KEEP_ALIVE_TIME_SECS = 20;
+ private static final Object GET_INSTANCE_LOCK = new Object();
+
+ private static volatile ShortcutInfoCompatSaver sINSTANCE;
+
+ @SuppressWarnings("WeakerAccess")
+ final Context mContext;
+ // mShortcutsMap is strictly only accessed by mCacheUpdateService
+ @SuppressWarnings("WeakerAccess")
+ final Map<String, ShortcutContainer> mShortcutsMap = new ArrayMap<>();
+ @SuppressWarnings("WeakerAccess")
+ final Map<String, ListenableFuture<?>> mScheduledBitmapTasks = new ArrayMap<>();
+
+ @SuppressWarnings("WeakerAccess")
+ final ExecutorService mCacheUpdateService;
+ // Single threaded tasks queue for IO operations on disk
+ private final ExecutorService mDiskIoService;
+
+ @SuppressWarnings("WeakerAccess")
+ final File mTargetsXmlFile;
+ @SuppressWarnings("WeakerAccess")
+ final File mBitmapsDir;
+
+ @AnyThread
+ static ShortcutInfoCompatSaver getInstance(Context context) {
+ if (sINSTANCE == null) {
+ synchronized (GET_INSTANCE_LOCK) {
+ if (sINSTANCE == null) {
+ sINSTANCE = new ShortcutInfoCompatSaver(context,
+ createExecutorService(),
+ createExecutorService());
+ }
+ }
+ }
+ return sINSTANCE;
+ }
+
+ @AnyThread
+ static ExecutorService createExecutorService() {
+ return new ThreadPoolExecutor(
+ // Set to 0 to avoid persistent background thread when idle
+ 0, /* core pool size */
+ // Set to 1 to ensure tasks will run strictly in the submit order
+ 1, /* max pool size */
+ EXECUTOR_KEEP_ALIVE_TIME_SECS, /* keep alive time */
+ TimeUnit.SECONDS, /* keep alive time unit */
+ new LinkedBlockingQueue<Runnable>() /* Not used */);
+ }
+
+ @AnyThread
+ ShortcutInfoCompatSaver(Context context, ExecutorService cacheUpdateService,
+ ExecutorService diskIoService) {
+ mContext = context.getApplicationContext();
+ mCacheUpdateService = cacheUpdateService;
+ mDiskIoService = diskIoService;
+ final File workingDirectory = new File(context.getFilesDir(), DIRECTORY_TARGETS);
+ mBitmapsDir = new File(workingDirectory, DIRECTORY_BITMAPS);
+ mTargetsXmlFile = new File(workingDirectory, FILENAME_XML);
+ // we trying to recover from errors during following submit:
+ // if xml was corrupted it is removed and saver is started clean
+ // however it is still not great and there is chance to swallow an exception
+ mCacheUpdateService.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ensureDir(workingDirectory);
+ ensureDir(mBitmapsDir);
+ mShortcutsMap.putAll(ShortcutsInfoSerialization.loadFromXml(mTargetsXmlFile,
+ mContext));
+ deleteDanglingBitmaps(new ArrayList<>(mShortcutsMap.values()));
+ } catch (Exception e) {
+ Log.w(TAG, "ShortcutInfoCompatSaver started with an exceptions ", e);
+ }
+ }
+ });
+ }
+
+ @AnyThread
+ ListenableFuture<Void> removeShortcuts(List<String> shortcutIds) {
+ final List<String> idList = new ArrayList<>(shortcutIds);
+ final ResolvableFuture<Void> result = ResolvableFuture.create();
+ mCacheUpdateService.submit(new Runnable() {
+ @Override
+ public void run() {
+ for (String id : idList) {
+ mShortcutsMap.remove(id);
+ ListenableFuture<?> removed = mScheduledBitmapTasks.remove(id);
+ if (removed != null) {
+ removed.cancel(false);
+ }
+ }
+ scheduleSyncCurrentState(result);
+ }
+ });
+ return result;
+ }
+
+ @AnyThread
+ ListenableFuture<Void> removeAllShortcuts() {
+ final ResolvableFuture<Void> result =
+ ResolvableFuture.create();
+ mCacheUpdateService.submit(new Runnable() {
+ @Override
+ public void run() {
+ mShortcutsMap.clear();
+ for (ListenableFuture<?> task : mScheduledBitmapTasks.values()) {
+ task.cancel(false);
+ }
+ mScheduledBitmapTasks.clear();
+ scheduleSyncCurrentState(result);
+ }
+ });
+
+ return result;
+ }
+
+ @WorkerThread
+ List<ShortcutInfoCompat> getShortcuts() throws Exception {
+ return mCacheUpdateService.submit(new Callable<ArrayList<ShortcutInfoCompat>>() {
+ @Override
+ public ArrayList<ShortcutInfoCompat> call() {
+ ArrayList<ShortcutInfoCompat> shortcuts = new ArrayList<>();
+ for (ShortcutContainer item : mShortcutsMap.values()) {
+ shortcuts.add(new ShortcutInfoCompat.Builder(item.mShortcutInfo).build());
+ }
+ return shortcuts;
+ }
+ }).get();
+ }
+
+ @WorkerThread
+ IconCompat getShortcutIcon(final String shortcutId) throws Exception {
+ final ShortcutContainer container = mCacheUpdateService.submit(
+ new Callable<ShortcutContainer>() {
+ @Override
+ public ShortcutContainer call() {
+ return mShortcutsMap.get(shortcutId);
+ }
+ }).get();
+ if (container == null) {
+ return null;
+ }
+ if (!TextUtils.isEmpty(container.mResourceName)) {
+ int id = 0;
+ try {
+ id = mContext.getResources().getIdentifier(container.mResourceName, null, null);
+ } catch (Exception e) {
+ /* Do nothing, continue and try mBitmapPath */
+ }
+ if (id != 0) {
+ return IconCompat.createWithResource(mContext, id);
+ }
+ }
+ if (!TextUtils.isEmpty(container.mBitmapPath)) {
+ Bitmap bitmap = mDiskIoService.submit(new Callable<Bitmap>() {
+ @Override
+ public Bitmap call() {
+ return BitmapFactory.decodeFile(container.mBitmapPath);
+ }
+ }).get();
+ // TODO: Re-create an adaptive icon if the original icon was adaptive
+ return bitmap != null ? IconCompat.createWithBitmap(bitmap) : null;
+ }
+ return null;
+ }
+
+ /**
+ * Delete bitmap files from the disk if they are not associated with any shortcuts in the list.
+ *
+ * Strictly called by mDiskIoService only
+ */
+ @SuppressWarnings("WeakerAccess")
+ void deleteDanglingBitmaps(List<ShortcutContainer> shortcutsList) {
+ List<String> bitmapPaths = new ArrayList<>();
+ for (ShortcutContainer item : shortcutsList) {
+ if (!TextUtils.isEmpty(item.mBitmapPath)) {
+ bitmapPaths.add(item.mBitmapPath);
+ }
+ }
+ for (File bitmap : mBitmapsDir.listFiles()) {
+ if (!bitmapPaths.contains(bitmap.getAbsolutePath())) {
+ bitmap.delete();
+ }
+ }
+ }
+
+ ListenableFuture<Void> addShortcuts(List<ShortcutInfoCompat> shortcuts) {
+ final List<ShortcutInfoCompat> copy = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfoCompat infoCompat : shortcuts) {
+ copy.add(new ShortcutInfoCompat.Builder(infoCompat).build());
+ }
+ final ResolvableFuture<Void> result = ResolvableFuture.create();
+ mCacheUpdateService.submit(new Runnable() {
+ @Override
+ public void run() {
+ for (final ShortcutInfoCompat info : copy) {
+ Set<String> categories = info.getCategories();
+ if (categories == null || categories.isEmpty()) {
+ continue;
+ }
+ ShortcutContainer container = containerFrom(info);
+ IconCompat icon = info.mIcon;
+ // not null only if it is safe to call getBitmap
+ Bitmap bitmap = container.mBitmapPath != null ? icon.getBitmap() : null;
+ final String id = info.getId();
+ mShortcutsMap.put(id, container);
+ if (bitmap != null) {
+ final ListenableFuture<Void> future = scheduleBitmapSaving(bitmap,
+ container.mBitmapPath);
+ ListenableFuture<?> old = mScheduledBitmapTasks.put(id, future);
+ if (old != null) {
+ old.cancel(false);
+ }
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ mScheduledBitmapTasks.remove(id);
+ // saving bitmap was skipped, but it is okay
+ if (future.isCancelled()) {
+ return;
+ }
+ try {
+ future.get();
+ } catch (Exception e) {
+ // propagate an exception up to the chain.
+ result.setException(e);
+ }
+ }
+ }, mCacheUpdateService);
+ }
+ }
+ scheduleSyncCurrentState(result);
+ }
+ });
+ return result;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ ListenableFuture<Void> scheduleBitmapSaving(final Bitmap bitmap, final String path) {
+ return submitDiskOperation(new Runnable() {
+ @Override
+ public void run() {
+ saveBitmap(bitmap, path);
+ }
+ });
+ }
+
+ private ListenableFuture<Void> submitDiskOperation(final Runnable runnable) {
+ final ResolvableFuture<Void> result = ResolvableFuture.create();
+ mDiskIoService.submit(new Runnable() {
+ @Override
+ public void run() {
+ if (result.isCancelled()) {
+ return;
+ }
+ try {
+ runnable.run();
+ result.set(null);
+ } catch (Exception e) {
+ result.setException(e);
+ }
+ }
+ });
+ return result;
+ }
+
+ // must be called on mCacheUpdateService
+ @SuppressWarnings("WeakerAccess")
+ void scheduleSyncCurrentState(final ResolvableFuture<Void> output) {
+ final List<ShortcutContainer> containers = new ArrayList<>(mShortcutsMap.values());
+ final ListenableFuture<Void> future = submitDiskOperation(new Runnable() {
+ @Override
+ public void run() {
+ deleteDanglingBitmaps(containers);
+ ShortcutsInfoSerialization.saveAsXml(containers, mTargetsXmlFile);
+ }
+ });
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ future.get();
+ output.set(null);
+ } catch (Exception e) {
+ output.setException(e);
+ }
+ }
+ }, mCacheUpdateService);
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ ShortcutContainer containerFrom(ShortcutInfoCompat shortcut) {
+ String resourceName = null;
+ String bitmapPath = null;
+ if (shortcut.mIcon != null) {
+ IconCompat icon = shortcut.mIcon;
+ switch (icon.getType()) {
+ case Icon.TYPE_RESOURCE:
+ resourceName = mContext.getResources().getResourceName(icon.getResId());
+ break;
+ case Icon.TYPE_BITMAP:
+ case Icon.TYPE_ADAPTIVE_BITMAP:
+ // Choose a unique file name to serialize the bitmap
+ bitmapPath = new File(mBitmapsDir, UUID.randomUUID().toString())
+ .getAbsolutePath();
+ break;
+ case Icon.TYPE_DATA:
+ case Icon.TYPE_URI:
+ case IconCompat.TYPE_UNKNOWN:
+ break;
+ }
+ }
+ ShortcutInfoCompat shortcutCopy = new ShortcutInfoCompat.Builder(shortcut)
+ .setIcon(null).build();
+ return new ShortcutContainer(shortcutCopy, resourceName, bitmapPath);
+ }
+
+ /*
+ * Suppress wrong thread warning since Bitmap.compress() and saveBitmap() are both annotated
+ * @WorkerThread, but from different packages.
+ * androidx.annotation.WorkerThread vs android.annotation.WorkerThread
+ */
+ @WorkerThread
+ @SuppressWarnings("WrongThread")
+ void saveBitmap(Bitmap bitmap, String path) {
+ if (bitmap == null) {
+ throw new IllegalArgumentException("bitmap is null");
+ }
+ if (TextUtils.isEmpty(path)) {
+ throw new IllegalArgumentException("path is empty");
+ }
+
+ try (FileOutputStream fileStream = new FileOutputStream(new File(path))) {
+ if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100 /* quality */, fileStream)) {
+ Log.wtf(TAG, "Unable to compress bitmap");
+ throw new RuntimeException("Unable to compress bitmap for saving " + path);
+ }
+ } catch (IOException | RuntimeException | OutOfMemoryError e) {
+ Log.wtf(TAG, "Unable to write bitmap to file", e);
+ throw new RuntimeException("Unable to write bitmap to file " + path, e);
+ }
+ }
+
+ static boolean ensureDir(File directory) {
+ if (directory.exists() && !directory.isDirectory() && !directory.delete()) {
+ return false;
+ }
+ if (!directory.exists()) {
+ return directory.mkdirs();
+ }
+ return true;
+ }
+}
diff --git a/core/src/main/java/androidx/core/content/pm/ShortcutsInfoSerialization.java b/core/src/main/java/androidx/core/content/pm/ShortcutsInfoSerialization.java
new file mode 100644
index 0000000..5be685a
--- /dev/null
+++ b/core/src/main/java/androidx/core/content/pm/ShortcutsInfoSerialization.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2018 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 androidx.core.content.pm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.WorkerThread;
+import androidx.collection.ArrayMap;
+import androidx.core.util.AtomicFile;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+class ShortcutsInfoSerialization {
+ private static final String TAG = "ShortcutInfoCompatSaver";
+
+ private static final String TAG_ROOT = "share_targets";
+ private static final String TAG_TARGET = "target";
+ private static final String TAG_INTENT = "intent";
+ private static final String TAG_CATEGORY = "categories";
+
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_COMPONENT = "component";
+ private static final String ATTR_SHORT_LABEL = "short_label";
+ private static final String ATTR_LONG_LABEL = "long_label";
+ private static final String ATTR_DISABLED_MSG = "disabled_message";
+
+ private static final String ATTR_ICON_RES_NAME = "icon_resource_name";
+ private static final String ATTR_ICON_BMP_PATH = "icon_bitmap_path";
+
+ private static final String ATTR_ACTION = "action";
+ private static final String ATTR_TARGET_PACKAGE = "targetPackage";
+ private static final String ATTR_TARGET_CLASS = "targetClass";
+ private static final String ATTR_NAME = "name";
+
+ private ShortcutsInfoSerialization() {
+ }
+
+ /**
+ * Class to keep {@link ShortcutInfoCompat}s with extra info (resource name or file path) about
+ * their serialized Icon for lazy loading.
+ */
+ static class ShortcutContainer {
+ final String mResourceName;
+ final String mBitmapPath;
+ final ShortcutInfoCompat mShortcutInfo;
+
+ @AnyThread
+ ShortcutContainer(ShortcutInfoCompat shortcut, String resourceName, String bitmapPath) {
+ mShortcutInfo = shortcut;
+ mResourceName = resourceName;
+ mBitmapPath = bitmapPath;
+ }
+
+ }
+
+ static void saveAsXml(List<ShortcutContainer> shortcutsList, File output) {
+ final AtomicFile atomicFile = new AtomicFile(output);
+ FileOutputStream fileStream = null;
+ try {
+ fileStream = atomicFile.startWrite();
+ final BufferedOutputStream stream = new BufferedOutputStream(fileStream);
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(stream, "UTF_8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_ROOT);
+
+ for (ShortcutContainer shortcut : shortcutsList) {
+ serializeShortcutContainer(serializer, shortcut);
+ }
+
+ serializer.endTag(null, TAG_ROOT);
+ serializer.endDocument();
+
+ stream.flush();
+ fileStream.flush();
+ atomicFile.finishWrite(fileStream);
+
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to write to file " + atomicFile.getBaseFile(), e);
+ atomicFile.failWrite(fileStream);
+ throw new RuntimeException("Failed to write to file " + atomicFile.getBaseFile(), e);
+ }
+ }
+
+ @WorkerThread
+ static Map<String, ShortcutContainer> loadFromXml(File input, Context context) {
+ Map<String, ShortcutContainer> shortcutsList = new ArrayMap<>();
+ try {
+ if (!input.exists()) {
+ return shortcutsList;
+ }
+ final FileInputStream stream = new FileInputStream(input);
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, "UTF_8");
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_TARGET)) {
+ ShortcutContainer shortcut = parseShortcutContainer(parser, context);
+ if (shortcut != null && shortcut.mShortcutInfo != null) {
+ shortcutsList.put(shortcut.mShortcutInfo.getId(), shortcut);
+ }
+ }
+ }
+ } catch (Exception e) {
+ // trying to recover by deleting input file.
+ input.delete();
+ Log.e(TAG, "Failed to load saved values from file " + input.getAbsolutePath()
+ + ". Old state removed, new added", e);
+ }
+ return shortcutsList;
+ }
+
+ @WorkerThread
+ private static ShortcutContainer parseShortcutContainer(XmlPullParser parser,
+ Context context) throws Exception {
+ if (!parser.getName().equals(TAG_TARGET)) {
+ return null;
+ }
+
+ String id = getAttributeValue(parser, ATTR_ID);
+ CharSequence label = getAttributeValue(parser, ATTR_SHORT_LABEL);
+ if (TextUtils.isEmpty(id) || TextUtils.isEmpty(label)) {
+ return null;
+ }
+
+ CharSequence longLabel = getAttributeValue(parser, ATTR_LONG_LABEL);
+ CharSequence disabledMessage = getAttributeValue(parser, ATTR_DISABLED_MSG);
+ ComponentName activity = parseComponentName(parser);
+ String iconResourceName = getAttributeValue(parser, ATTR_ICON_RES_NAME);
+ String iconBitmapPath = getAttributeValue(parser, ATTR_ICON_BMP_PATH);
+
+ ArrayList<Intent> intents = new ArrayList<>();
+ Set<String> categories = new HashSet<>();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case TAG_INTENT:
+ Intent intent = parseIntent(parser);
+ if (intent != null) {
+ intents.add(intent);
+ }
+ break;
+ case TAG_CATEGORY:
+ String category = getAttributeValue(parser, ATTR_NAME);
+ if (!TextUtils.isEmpty(category)) {
+ categories.add(category);
+ }
+ break;
+ }
+ } else if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_TARGET)) {
+ break;
+ }
+ }
+
+ ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(context, id)
+ .setShortLabel(label);
+ if (!TextUtils.isEmpty(longLabel)) {
+ builder.setLongLabel(longLabel);
+ }
+ if (!TextUtils.isEmpty(disabledMessage)) {
+ builder.setDisabledMessage(disabledMessage);
+ }
+ if (activity != null) {
+ builder.setActivity(activity);
+ }
+ if (!intents.isEmpty()) {
+ builder.setIntents(intents.toArray(new Intent[0]));
+ }
+ if (!categories.isEmpty()) {
+ builder.setCategories(categories);
+ }
+ return new ShortcutContainer(builder.build(), iconResourceName, iconBitmapPath);
+ }
+
+ @WorkerThread
+ private static ComponentName parseComponentName(XmlPullParser parser) {
+ String value = getAttributeValue(parser, ATTR_COMPONENT);
+ return TextUtils.isEmpty(value) ? null : ComponentName.unflattenFromString(value);
+ }
+
+ @WorkerThread
+ private static Intent parseIntent(XmlPullParser parser) {
+ String action = getAttributeValue(parser, ATTR_ACTION);
+ String targetPackage = getAttributeValue(parser, ATTR_TARGET_PACKAGE);
+ String targetClass = getAttributeValue(parser, ATTR_TARGET_CLASS);
+
+ if (action == null) {
+ return null;
+ }
+ Intent intent = new Intent(action);
+ if (!TextUtils.isEmpty(targetPackage) && !TextUtils.isEmpty(targetClass)) {
+ intent.setClassName(targetPackage, targetClass);
+ }
+ return intent;
+ }
+
+ @WorkerThread
+ private static String getAttributeValue(XmlPullParser parser, String attribute) {
+ String value = parser.getAttributeValue("http://schemas.android.com/apk/res/android",
+ attribute);
+ if (value == null) {
+ value = parser.getAttributeValue(null, attribute);
+ }
+ return value;
+ }
+
+ @WorkerThread
+ private static void serializeShortcutContainer(XmlSerializer serializer,
+ ShortcutContainer container)
+ throws IOException {
+ serializer.startTag(null, TAG_TARGET);
+
+ ShortcutInfoCompat shortcut = container.mShortcutInfo;
+ serializeAttribute(serializer, ATTR_ID, shortcut.getId());
+ serializeAttribute(serializer, ATTR_SHORT_LABEL, shortcut.getShortLabel().toString());
+ if (!TextUtils.isEmpty(shortcut.getLongLabel())) {
+ serializeAttribute(serializer, ATTR_LONG_LABEL, shortcut.getLongLabel().toString());
+ }
+ if (!TextUtils.isEmpty(shortcut.getDisabledMessage())) {
+ serializeAttribute(serializer, ATTR_DISABLED_MSG,
+ shortcut.getDisabledMessage().toString());
+ }
+ if (shortcut.getActivity() != null) {
+ serializeAttribute(serializer, ATTR_COMPONENT,
+ shortcut.getActivity().flattenToString());
+ }
+ if (!TextUtils.isEmpty(container.mResourceName)) {
+ serializeAttribute(serializer, ATTR_ICON_RES_NAME, container.mResourceName);
+ }
+ if (!TextUtils.isEmpty(container.mBitmapPath)) {
+ serializeAttribute(serializer, ATTR_ICON_BMP_PATH, container.mBitmapPath);
+ }
+ for (Intent intent : shortcut.getIntents()) {
+ serializeIntent(serializer, intent);
+ }
+ for (String category : shortcut.mCategories) {
+ serializeCategory(serializer, category);
+ }
+
+ serializer.endTag(null, TAG_TARGET);
+ }
+
+ @WorkerThread
+ private static void serializeIntent(XmlSerializer serializer, Intent intent)
+ throws IOException {
+ serializer.startTag(null, TAG_INTENT);
+
+ serializeAttribute(serializer, ATTR_ACTION, intent.getAction());
+ if (intent.getComponent() != null) {
+ serializeAttribute(serializer, ATTR_TARGET_PACKAGE,
+ intent.getComponent().getPackageName());
+ serializeAttribute(serializer, ATTR_TARGET_CLASS, intent.getComponent().getClassName());
+ }
+
+ serializer.endTag(null, TAG_INTENT);
+ }
+
+ @WorkerThread
+ private static void serializeCategory(XmlSerializer serializer, String category)
+ throws IOException {
+ if (TextUtils.isEmpty(category)) {
+ return;
+ }
+ serializer.startTag(null, TAG_CATEGORY);
+ serializeAttribute(serializer, ATTR_NAME, category);
+ serializer.endTag(null, TAG_CATEGORY);
+ }
+
+ @WorkerThread
+ private static void serializeAttribute(XmlSerializer serializer, String attribute, String value)
+ throws IOException {
+ if (TextUtils.isEmpty(value)) {
+ return;
+ }
+ serializer.attribute(null, attribute, value);
+ }
+
+}
diff --git a/core/src/main/java/androidx/core/text/PrecomputedTextCompat.java b/core/src/main/java/androidx/core/text/PrecomputedTextCompat.java
index 6e079ddb..9ac55aa7 100644
--- a/core/src/main/java/androidx/core/text/PrecomputedTextCompat.java
+++ b/core/src/main/java/androidx/core/text/PrecomputedTextCompat.java
@@ -37,6 +37,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.UiThread;
+import androidx.core.os.BuildCompat;
import androidx.core.os.TraceCompat;
import androidx.core.util.ObjectsCompat;
import androidx.core.util.Preconditions;
@@ -58,7 +59,7 @@
* layout information will be included in this instance, {@link android.widget.TextView} or
* {@link StaticLayout} will not have to recalculate this information.
*
- * On API 28 or later, there is full PrecomputedText support by framework. From API 21 to API 27,
+ * On API 29 or later, there is full PrecomputedText support by framework. From API 21 to API 27,
* PrecomputedTextCompat relies on internal text layout cache. PrecomputedTextCompat immediately
* computes the text layout in the constuctor to warm up the internal text layout cache. On API 20
* or before, PrecomputedTextCompat does nothing.
@@ -193,7 +194,7 @@
Params(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir,
int strategy, int frequency) {
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
mWrapped = new PrecomputedText.Params.Builder(paint).setBreakStrategy(strategy)
.setHyphenationFrequency(frequency).setTextDirection(textDir).build();
} else {
@@ -211,8 +212,7 @@
mTextDir = wrapped.getTextDirection();
mBreakStrategy = wrapped.getBreakStrategy();
mHyphenationFrequency = wrapped.getHyphenationFrequency();
- mWrapped = wrapped;
-
+ mWrapped = (BuildCompat.isAtLeastQ()) ? wrapped : null;
}
/**
@@ -261,20 +261,14 @@
return mHyphenationFrequency;
}
+
/**
- * Check if the same text layout.
- *
- * @return true if this and the given param result in the same text layout
+ * Similar to equals but don't compare text direction
+ * @hide
*/
- @Override
- public boolean equals(@Nullable Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof Params)) {
- return false;
- }
- Params other = (Params) o;
+ @RestrictTo(LIBRARY_GROUP)
+ public boolean equalsWithoutTextDirection(@NonNull Params other) {
+
if (mWrapped != null) {
return mWrapped.equals(other.mWrapped);
}
@@ -288,12 +282,6 @@
}
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- if (mTextDir != other.getTextDirection()) {
- return false;
- }
- }
-
if (mPaint.getTextSize() != other.getTextPaint().getTextSize()) {
return false;
}
@@ -335,6 +323,31 @@
return true;
}
+ /**
+ * Check if the same text layout.
+ *
+ * @return true if this and the given param result in the same text layout
+ */
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Params)) {
+ return false;
+ }
+ Params other = (Params) o;
+ if (!equalsWithoutTextDirection(other)) {
+ return false;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (mTextDir != other.getTextDirection()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Override
public int hashCode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@@ -397,7 +410,7 @@
// The list of measured paragraph info.
private final @NonNull int[] mParagraphEnds;
- // null on API 27 or before. Non-null on API 28 or later
+ // null on API 27 or before. Non-null on API 29 or later
private final @Nullable PrecomputedText mWrapped;
/**
@@ -422,7 +435,7 @@
try {
TraceCompat.beginSection("PrecomputedText");
- if (Build.VERSION.SDK_INT >= 28 && params.mWrapped != null) {
+ if (BuildCompat.isAtLeastQ() && params.mWrapped != null) {
return new PrecomputedTextCompat(
PrecomputedText.create(text, params.mWrapped), params);
}
@@ -486,7 +499,7 @@
mText = precomputed;
mParams = params;
mParagraphEnds = null;
- mWrapped = precomputed;
+ mWrapped = (BuildCompat.isAtLeastQ()) ? precomputed : null;
}
/**
@@ -514,7 +527,7 @@
* Returns the count of paragraphs.
*/
public @IntRange(from = 0) int getParagraphCount() {
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
return mWrapped.getParagraphCount();
} else {
return mParagraphEnds.length;
@@ -526,7 +539,7 @@
*/
public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
return mWrapped.getParagraphStart(paraIndex);
} else {
return paraIndex == 0 ? 0 : mParagraphEnds[paraIndex - 1];
@@ -538,7 +551,7 @@
*/
public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
return mWrapped.getParagraphEnd(paraIndex);
} else {
return mParagraphEnds[paraIndex];
@@ -662,7 +675,7 @@
throw new IllegalArgumentException(
"MetricAffectingSpan can not be set to PrecomputedText.");
}
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
mWrapped.setSpan(what, start, end, flags);
} else {
mText.setSpan(what, start, end, flags);
@@ -678,7 +691,7 @@
throw new IllegalArgumentException(
"MetricAffectingSpan can not be removed from PrecomputedText.");
}
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
mWrapped.removeSpan(what);
} else {
mText.removeSpan(what);
@@ -692,7 +705,7 @@
@Override
public <T> T[] getSpans(int start, int end, Class<T> type) {
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
return mWrapped.getSpans(start, end, type);
} else {
return mText.getSpans(start, end, type);
diff --git a/core/src/main/java/androidx/core/widget/TextViewCompat.java b/core/src/main/java/androidx/core/widget/TextViewCompat.java
index 8a0c125..d5b3c01 100644
--- a/core/src/main/java/androidx/core/widget/TextViewCompat.java
+++ b/core/src/main/java/androidx/core/widget/TextViewCompat.java
@@ -58,6 +58,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
+import androidx.core.os.BuildCompat;
import androidx.core.text.PrecomputedTextCompat;
import androidx.core.util.Preconditions;
@@ -879,13 +880,13 @@
public static void setPrecomputedText(@NonNull TextView textView,
@NonNull PrecomputedTextCompat precomputed) {
- if (Build.VERSION.SDK_INT >= 28) {
+ if (BuildCompat.isAtLeastQ()) {
// Framework can not understand PrecomptedTextCompat. Pass underlying PrecomputedText.
// Parameter check is also done by framework.
textView.setText(precomputed.getPrecomputedText());
} else {
PrecomputedTextCompat.Params param = TextViewCompat.getTextMetricsParams(textView);
- if (!param.equals(precomputed.getParams())) {
+ if (!param.equalsWithoutTextDirection(precomputed.getParams())) {
throw new IllegalArgumentException("Given text can not be applied to TextView.");
}
textView.setText(precomputed);
diff --git a/cursoradapter/build.gradle b/cursoradapter/build.gradle
index 2493a2b..3e3cdbb 100644
--- a/cursoradapter/build.gradle
+++ b/cursoradapter/build.gradle
@@ -12,7 +12,7 @@
supportLibrary {
name = "Android Support Library Cursor Adapter"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.CURSORADAPTER
mavenGroup = LibraryGroups.CURSORADAPTER
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/customview/build.gradle b/customview/build.gradle
index 8c8237c..49bab6f 100644
--- a/customview/build.gradle
+++ b/customview/build.gradle
@@ -18,7 +18,7 @@
supportLibrary {
name = "Android Support Library Custom View"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.CUSTOMVIEW
mavenGroup = LibraryGroups.CUSTOMVIEW
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/development/importMaven/build.gradle.kts b/development/importMaven/build.gradle.kts
index 9c87fa2..ee51f38 100644
--- a/development/importMaven/build.gradle.kts
+++ b/development/importMaven/build.gradle.kts
@@ -14,8 +14,94 @@
* limitations under the License.
*/
+import org.apache.maven.model.Dependency
+import org.apache.maven.model.Parent
+import org.apache.maven.model.Repository
+import org.apache.maven.model.building.DefaultModelBuilderFactory
+import org.apache.maven.model.building.DefaultModelBuildingRequest
+import org.apache.maven.model.building.ModelBuildingException
+import org.apache.maven.model.building.ModelBuildingRequest
+import org.apache.maven.model.building.ModelSource
+import org.apache.maven.model.resolution.ModelResolver
import java.security.MessageDigest
import org.gradle.api.artifacts.result.ResolvedArtifactResult
+import java.io.InputStream
+
+buildscript {
+ repositories {
+ jcenter()
+ mavenCentral()
+ google()
+ }
+
+ dependencies {
+ classpath(gradleApi())
+ classpath("org.apache.maven:maven-model:3.5.4")
+ classpath("org.apache.maven:maven-model-builder:3.5.4")
+ }
+}
+
+typealias MavenListener = (String, String, String, File) -> Unit
+
+/**
+ * A Maven module resolver which uses Gradle.
+ */
+class MavenModuleResolver(val project: Project, val listener: MavenListener) : ModelResolver {
+
+ override fun resolveModel(dependency: Dependency): ModelSource? {
+ return resolveModel(dependency.groupId, dependency.artifactId, dependency.version)
+ }
+
+ override fun resolveModel(parent: Parent): ModelSource? {
+ return resolveModel(parent.groupId, parent.artifactId, parent.version)
+ }
+
+ override fun resolveModel(
+ groupId: String,
+ artifactId: String,
+ version: String
+ ): ModelSource? {
+ val pomQuery = project.dependencies.createArtifactResolutionQuery()
+ val pomQueryResult = pomQuery.forModule(groupId, artifactId, version)
+ .withArtifacts(
+ MavenModule::class.java,
+ MavenPomArtifact::class.java
+ )
+ .execute()
+ var result: File? = null
+ for (component in pomQueryResult.resolvedComponents) {
+ val pomArtifacts = component.getArtifacts(MavenPomArtifact::class.java)
+ for (pomArtifact in pomArtifacts) {
+ val pomFile = pomArtifact as? ResolvedArtifactResult
+ if (pomFile != null) {
+ result = pomFile.file
+ listener.invoke(groupId, artifactId, version, result)
+ }
+ }
+ }
+ return object : ModelSource {
+ override fun getInputStream(): InputStream {
+ return result!!.inputStream()
+ }
+
+ override fun getLocation(): String {
+ return result!!.absolutePath
+ }
+ }
+ }
+
+ override fun addRepository(repository: Repository?) {
+ // We don't need to support this
+ }
+
+ override fun addRepository(repository: Repository?, replace: Boolean) {
+ // We don't need to support this
+ }
+
+ override fun newCopy(): ModelResolver {
+ return this
+ }
+}
// The output folder inside prebuilts
val prebuiltsLocation = file("../../../../prebuilts/androidx")
@@ -28,17 +114,17 @@
val artifactName = project.findProperty("artifactName")
val internalArtifacts = listOf(
- "android.arch(.*)?".toRegex(),
- "com.android.support(.*)?".toRegex()
+ "android.arch(.*)?".toRegex(),
+ "com.android.support(.*)?".toRegex()
)
val potentialInternalArtifacts = listOf(
- "androidx(.*)?".toRegex()
+ "androidx(.*)?".toRegex()
)
// Need to exclude androidx.databinding
val forceExternal = setOf(
- ".databinding"
+ ".databinding"
)
plugins {
@@ -91,22 +177,39 @@
/**
* Returns the supporting files (POM, Source files) for a given artifact.
*/
-fun supportingArtifacts(artifact: ResolvedArtifact): List<ResolvedArtifactResult> {
+fun supportingArtifacts(
+ artifact: ResolvedArtifact,
+ internal: Boolean = false
+): List<ResolvedArtifactResult> {
val supportingArtifacts = mutableListOf<ResolvedArtifactResult>()
val pomQuery = project.dependencies.createArtifactResolutionQuery()
+ val modelBuilderFactory = DefaultModelBuilderFactory()
+ val builder = modelBuilderFactory.newInstance()
+ val resolver = MavenModuleResolver(project) { groupId, artifactId, version, pomFile ->
+ copyPomFile(groupId, artifactId, version, pomFile, internal)
+ }
val pomQueryResult = pomQuery.forComponents(artifact.id.componentIdentifier)
- .withArtifacts(
- MavenModule::class.java,
- MavenPomArtifact::class.java)
- .execute()
+ .withArtifacts(
+ MavenModule::class.java,
+ MavenPomArtifact::class.java
+ )
+ .execute()
for (component in pomQueryResult.resolvedComponents) {
- // DefaultResolvedArtifactResult is an internal Gradle class.
- // However, it's being widely used anyway.
val pomArtifacts = component.getArtifacts(MavenPomArtifact::class.java)
for (pomArtifact in pomArtifacts) {
val pomFile = pomArtifact as? ResolvedArtifactResult
if (pomFile != null) {
+ try {
+ val request: ModelBuildingRequest = DefaultModelBuildingRequest()
+ request.modelResolver = resolver
+ request.pomFile = pomFile.file
+ // Turn off validations becuase there are lots of bad POM files out there.
+ request.validationLevel = ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL
+ builder.build(request).effectiveModel
+ } catch (exception: ModelBuildingException) {
+ println("Error building model request for $pomArtifact")
+ }
supportingArtifacts.add(pomFile)
}
}
@@ -116,10 +219,11 @@
// So if artifacts only have a distributable without a source, we still want to copy the POM file.
val sourcesQuery = project.dependencies.createArtifactResolutionQuery()
val sourcesQueryResult = sourcesQuery.forComponents(artifact.id.componentIdentifier)
- .withArtifacts(
- MavenModule::class.java,
- SourcesArtifact::class.java)
- .execute()
+ .withArtifacts(
+ MavenModule::class.java,
+ SourcesArtifact::class.java
+ )
+ .execute()
for (component in sourcesQueryResult.resolvedComponents) {
val sourcesArtifacts = component.getArtifacts(SourcesArtifact::class.java)
@@ -155,18 +259,20 @@
/**
* Copies artifacts to the right locations.
*/
-fun copyLibrary(artifact: ResolvedArtifact, internal: Boolean = false) {
+fun copyArtifact(artifact: ResolvedArtifact, internal: Boolean = false) {
val folder = if (internal) internalFolder else externalFolder
val moduleVersionId = artifact.moduleVersion.id
val group = moduleVersionId.group
val groupPath = group.split(".").joinToString("/")
- val pathComponents = listOf(prebuiltsLocation,
- folder,
- groupPath,
- moduleVersionId.name,
- moduleVersionId.version)
+ val pathComponents = listOf(
+ prebuiltsLocation,
+ folder,
+ groupPath,
+ moduleVersionId.name,
+ moduleVersionId.version
+ )
val location = pathComponents.joinToString("/")
- val supportingArtifacts = supportingArtifacts(artifact)
+ val supportingArtifacts = supportingArtifacts(artifact, internal = internal)
// Copy main artifact
println("Copying $artifact to $location")
copy {
@@ -194,33 +300,69 @@
}
}
+/**
+ * Copies associated POM files to the right location.
+ */
+fun copyPomFile(
+ group: String,
+ name: String,
+ version: String,
+ pomFile: File,
+ internal: Boolean = false
+) {
+ val folder = if (internal) internalFolder else externalFolder
+ val groupPath = group.split(".").joinToString("/")
+ val pathComponents = listOf(
+ prebuiltsLocation,
+ folder,
+ groupPath,
+ name,
+ version
+ )
+ val location = pathComponents.joinToString("/")
+ // Copy associated POM files.
+ println("Copying ${pomFile.name} to $location")
+ copy {
+ from(
+ pomFile,
+ digest(pomFile, "MD5"),
+ digest(pomFile, "SHA1")
+ )
+ into(location)
+ }
+ copy {
+ into(location)
+ }
+}
+
tasks {
val fetchArtifacts by creating {
doLast {
// Collect all the internal and external dependencies.
// Copy the jar/aar's and their respective POM files.
val internalLibraries =
- filterInternalLibraries(
- fetchArtifactsContainer
- .resolvedConfiguration
- .resolvedArtifacts)
+ filterInternalLibraries(
+ fetchArtifactsContainer
+ .resolvedConfiguration
+ .resolvedArtifacts
+ )
val externalLibraries =
- fetchArtifactsContainer
- .resolvedConfiguration
- .resolvedArtifacts.filter {
- val isInternal = internalLibraries.contains(it)
- !isInternal
- }
+ fetchArtifactsContainer
+ .resolvedConfiguration
+ .resolvedArtifacts.filter {
+ val isInternal = internalLibraries.contains(it)
+ !isInternal
+ }
println("\r\nInternal Libraries")
internalLibraries.forEach { library ->
- copyLibrary(library, internal = true)
+ copyArtifact(library, internal = true)
}
println("\r\nExternal Libraries")
externalLibraries.forEach { library ->
- copyLibrary(library, internal = false)
+ copyArtifact(library, internal = false)
}
println("\r\nResolved artifacts for $artifactName.")
}
diff --git a/development/triage-guesser.py b/development/triage-guesser.py
new file mode 100755
index 0000000..e421bd7
--- /dev/null
+++ b/development/triage-guesser.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+
+import sys, re, subprocess, os
+
+def usage():
+ print("""Usage: cat <issues> | triage-guesser.py
+triage-guesser.py attempts to guess the assignee based on the title of the bug
+
+triage-guesser reads issues from stdin
+""")
+ sys.exit(1)
+
+class Issue(object):
+ def __init__(self, issueId, description):
+ self.issueId = issueId
+ self.description = description
+
+class AssigneeRecommendation(object):
+ def __init__(self, usernames, justification):
+ self.usernames = usernames
+ self.justification = justification
+
+ def intersect(self, other):
+ names = []
+ for name in self.usernames:
+ if name in other.usernames:
+ names.append(name)
+ justification = self.justification + ", " + other.justification
+ return AssigneeRecommendation(names, justification)
+
+class RecommenderRule(object):
+ def __init__(self):
+ return
+
+ def recommend(self, bug):
+ return
+
+class ShellRunner(object):
+ def __init__(self):
+ return
+
+ def runAndGetOutput(self, args):
+ return subprocess.check_output(args)
+shellRunner = ShellRunner()
+
+class WordRule(RecommenderRule):
+ def __init__(self, word, assignees):
+ super(WordRule, self).__init__()
+ self.word = word
+ self.assignees = assignees
+
+ def recommend(self, bug):
+ if self.word.lower() in bug.description.lower():
+ return AssigneeRecommendation(self.assignees, '"' + self.word + '"')
+ return None
+
+class FileFinder(object):
+ def __init__(self, rootPath):
+ self.rootPath = rootPath
+ self.resultsCache = {}
+
+ def findIname(self, name):
+ if name not in self.resultsCache:
+ text = shellRunner.runAndGetOutput(["find", self.rootPath , "-type", "f", "-iname", name])
+ filePaths = [path.strip() for path in text.split("\n")]
+ filePaths = [path for path in filePaths if path != ""]
+ self.resultsCache[name] = filePaths
+ return self.resultsCache[name]
+
+class InterestingFileFinder(object):
+ def __init__(self):
+ return
+
+ def findInterestingWords(self, text):
+ words = re.split("#| |\.", text)
+ words = [word for word in words if len(word) >= 4]
+ words.sort(key=len, reverse=True)
+ return words
+interestingFileFinder = InterestingFileFinder()
+
+class GitLogger(object):
+ def __init__(self):
+ return
+
+ def gitLog1Author(self, filePath):
+ text = shellRunner.runAndGetOutput(["bash", "-c", "cd " + os.path.dirname(filePath) + " && git log --no-merges -1 --format='%ae' -- " + filePath]).strip().replace("@google.com", "")
+ return text
+gitLogger = GitLogger()
+
+class LastTouchedBy_Rule(RecommenderRule):
+ def __init__(self, fileFinder):
+ super(LastTouchedBy_Rule, self).__init__()
+ self.fileFinder = fileFinder
+
+ def recommend(self, bug):
+ interestingWords = interestingFileFinder.findInterestingWords(bug.description)
+ for word in interestingWords:
+ for queryString in [word + "*", word + ".*"]:
+ filePaths = self.fileFinder.findIname(queryString)
+ if len(filePaths) > 0 and len(filePaths) <= 4:
+ candidateAuthors = []
+ for path in filePaths:
+ thisAuthor = gitLogger.gitLog1Author(path)
+ if len(candidateAuthors) == 0 or thisAuthor != candidateAuthors[-1]:
+ candidateAuthors.append(thisAuthor)
+ if len(candidateAuthors) == 1:
+ return AssigneeRecommendation(candidateAuthors, "last touched " + os.path.basename(filePaths[0]))
+ return None
+
+class OwnersRule(RecommenderRule):
+ def __init__(self, fileFinder):
+ super(OwnersRule, self).__init__()
+ self.fileFinder = fileFinder
+
+ def recommend(self, bug):
+ interestingWords = interestingFileFinder.findInterestingWords(bug.description)
+ for word in interestingWords:
+ for queryString in [word + "*", word + ".*"]:
+ filePaths = self.fileFinder.findIname(queryString)
+ commonPrefix = os.path.commonprefix(filePaths)
+ dirToCheck = commonPrefix
+ if len(dirToCheck) < 1:
+ continue
+ while True:
+ if dirToCheck[-1] == "/":
+ dirToCheck = dirToCheck[:-1]
+ if len(dirToCheck) <= len(self.fileFinder.rootPath):
+ break
+ ownerFilePath = os.path.join(dirToCheck, "OWNERS")
+ if os.path.isfile(ownerFilePath):
+ with open(ownerFilePath) as ownerFile:
+ lines = ownerFile.readlines()
+ names = [line.replace("@google.com", "").strip() for line in lines]
+ relOwnersPath = os.path.relpath(ownerFilePath, self.fileFinder.rootPath)
+ justification = relOwnersPath + " (" + os.path.basename(filePaths[0] + ' ("' + word + '")')
+ if len(filePaths) > 1:
+ justification += "..."
+ justification += ")"
+ return AssigneeRecommendation(names, justification)
+ else:
+ parent = os.path.dirname(dirToCheck)
+ if len(parent) >= len(dirToCheck):
+ break
+ dirToCheck = parent
+
+
+class Triager(object):
+ def __init__(self, fileFinder):
+ self.recommenderRules = self.parseKnownOwners({
+ "fragment": ["ilake", "mount", "adamp"],
+ "animation": ["chet", "mount", "tianlu"],
+ "transition": ["chet", "mount"],
+ "theme": ["alanv"],
+ "style": ["alanv"],
+ "preferences": ["pavlis", "lpf"],
+ "ViewPager": ["jgielzak", "aurimas"],
+ "DrawerLayout": ["kirillg"],
+ "RecyclerView": ["shepshapard", "yboyar"],
+ "Loaders": ["ilake"],
+ "VectorDrawableCompat": ["tianliu"],
+ "AppCompat": ["kirillg"],
+ "Design Library": ["dcarlsson"],
+ "android.support.design": ["dcarlsson"],
+ "RenderThread": ["jreck"],
+ "VectorDrawable": ["tianliu"],
+ "drawable": ["alanv"],
+ "colorstatelist": ["alanv"],
+ "multilocale": ["nona", "mnita"],
+ "TextView": ["siyamed", "clarabayarri"],
+ "Linkify": ["siyamed", "toki"],
+ "Spannable": ["siyamed"],
+ "Minikin": ["nona"],
+ "Fonts": ["clarabayarri", "dougfelt"],
+ "freetype": ["nona", "junkshik"],
+ "harfbuzz": ["nona", "junkshik"],
+ "slice": ["jmonk", "madym"]
+ })
+ self.recommenderRules.append(OwnersRule(fileFinder))
+ self.recommenderRules.append(LastTouchedBy_Rule(fileFinder))
+
+ def parseKnownOwners(self, ownersDict):
+ rules = []
+ keywords = sorted(ownersDict.keys())
+ for keyword in keywords:
+ assignees = ownersDict[keyword]
+ rules.append(WordRule(keyword, assignees))
+ return rules
+
+ def process(self, lines):
+ issues = self.parseIssues(lines)
+ outputs = []
+ print("Analyzing " + str(len(issues)) + " issues")
+ for issue in issues:
+ print(".")
+ assigneeRecommendation = self.recommendAssignees(issue)
+ recommendationText = "?"
+ if assigneeRecommendation is not None:
+ usernames = assigneeRecommendation.usernames
+ if len(usernames) > 2:
+ usernames = usernames[:2]
+ recommendationText = str(usernames) + " (" + assigneeRecommendation.justification + ")"
+ outputs.append(("(" + issue.issueId + ") " + issue.description.replace("\t", "...."), recommendationText, ))
+ maxColumnWidth = 0
+ for item in outputs:
+ maxColumnWidth = max(maxColumnWidth, len(item[0]))
+ for item in outputs:
+ print(str(item[0]) + (" " * (maxColumnWidth - len(item[0]))) + " -> " + str(item[1]))
+
+ def parseIssues(self, lines):
+ priority = ""
+ issueType = ""
+ description = ""
+ when = ""
+
+ lines = [line.strip() for line in lines]
+ fields = [line for line in lines if line != ""]
+ linesPerIssue = 5
+ if len(fields) % linesPerIssue != 0:
+ raise Exception("Parse error, number of lines must be divisible by " + str(linesPerIssue) + ", not " + str(len(fields)) + ". Last line: " + fields[-1])
+ issues = []
+ while len(fields) > 0:
+ priority = fields[0]
+ issueType = fields[1]
+
+ middle = fields[2].split("\t")
+ expectedNumTabComponents = 3
+ if len(middle) != expectedNumTabComponents:
+ raise Exception("Parse error: wrong number of tabs in " + str(middle) + ", got " + str(len(middle) - 1) + ", expected " + str(expectedNumTabComponents - 1))
+ description = middle[0]
+ currentAssignee = middle[1]
+ status = middle[2]
+
+
+ middle2 = fields[3].split("\t")
+ expectedNumTabComponents = 2
+ if len(middle2) != expectedNumTabComponents:
+ raise Exception("Parse error: wrong number of tabs in " + str(middle2) + ", got " + str(len(middle2) - 1) + ", expected " + str(expectedNumTabComponents - 1))
+ issueId = middle2[1]
+
+ when = fields[4]
+
+ issues.append(Issue(issueId, description))
+ fields = fields[linesPerIssue:]
+ return issues
+
+ def recommendAssignees(self, issue):
+ overallRecommendation = None
+ for rule in self.recommenderRules:
+ thisRecommendation = rule.recommend(issue)
+ if thisRecommendation is not None:
+ if overallRecommendation is None:
+ overallRecommendation = thisRecommendation
+ else:
+ newRecommendation = overallRecommendation.intersect(thisRecommendation)
+ count = len(newRecommendation.usernames)
+ if count > 0 and count < len(overallRecommendation.usernames):
+ overallRecommendation = newRecommendation
+ return overallRecommendation
+
+
+
+def main(args):
+ if len(args) != 1:
+ usage()
+ fileFinder = FileFinder(os.path.dirname(args[0]))
+ print("Reading issues from stdin")
+ lines = sys.stdin.readlines()
+ triager = Triager(fileFinder)
+ triager.process(lines)
+
+
+
+
+main(sys.argv)
diff --git a/docs-fake/build.gradle b/docs-fake/build.gradle
index e73ba9b..685a75c 100644
--- a/docs-fake/build.gradle
+++ b/docs-fake/build.gradle
@@ -37,6 +37,7 @@
android {
defaultConfig {
minSdkVersion SupportConfig.CURRENT_SDK_VERSION
+ javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = false
}
sourceSets {
diff --git a/documentfile/build.gradle b/documentfile/build.gradle
index ab03322..5d57db5 100644
--- a/documentfile/build.gradle
+++ b/documentfile/build.gradle
@@ -15,7 +15,7 @@
supportLibrary {
name = "Android Support Library Document File"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.DOCUMENTFILE
mavenGroup = LibraryGroups.DOCUMENTFILE
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/drawerlayout/build.gradle b/drawerlayout/build.gradle
index a30723a..875e4a0 100644
--- a/drawerlayout/build.gradle
+++ b/drawerlayout/build.gradle
@@ -14,7 +14,7 @@
supportLibrary {
name = "Android Support Library Drawer Layout"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.DRAWERLAYOUT
mavenGroup = LibraryGroups.DRAWERLAYOUT
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index 0f8fca9..015290f 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -21,7 +21,7 @@
supportLibrary {
name = "Android Support DynamicAnimation"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.DYNAMICANIMATION
mavenGroup = LibraryGroups.DYNAMICANIMATION
inceptionYear = "2017"
description = "Physics-based animation in support library, where the animations are driven by physics force. You can use this Animation library to create smooth and realistic animations."
diff --git a/dynamic-animation/ktx/api/1.0.0-alpha01.txt b/dynamic-animation/ktx/api/1.0.0-alpha01.txt
index 01c8728..fe2c248 100644
--- a/dynamic-animation/ktx/api/1.0.0-alpha01.txt
+++ b/dynamic-animation/ktx/api/1.0.0-alpha01.txt
@@ -3,8 +3,8 @@
public final class DynamicAnimationKt {
ctor public DynamicAnimationKt();
- method public static <K extends android.view.View> androidx.dynamicanimation.animation.FlingAnimation flingAnimationOf(K, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property);
- method public static <K extends android.view.View> androidx.dynamicanimation.animation.SpringAnimation springAnimationOf(K, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property, float finalPosition = Float.NaN);
+ method public static <K> androidx.dynamicanimation.animation.FlingAnimation flingAnimationOf(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property);
+ method public static <K> androidx.dynamicanimation.animation.SpringAnimation springAnimationOf(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property, float finalPosition = Float.NaN);
method public static androidx.dynamicanimation.animation.SpringAnimation withSpringForceProperties(androidx.dynamicanimation.animation.SpringAnimation, kotlin.jvm.functions.Function1<? super androidx.dynamicanimation.animation.SpringForce,kotlin.Unit> func);
}
diff --git a/dynamic-animation/ktx/api/current.txt b/dynamic-animation/ktx/api/current.txt
new file mode 100644
index 0000000..01c8728
--- /dev/null
+++ b/dynamic-animation/ktx/api/current.txt
@@ -0,0 +1,12 @@
+// Signature format: 2.0
+package androidx.dynamicanimation.animation {
+
+ public final class DynamicAnimationKt {
+ ctor public DynamicAnimationKt();
+ method public static <K extends android.view.View> androidx.dynamicanimation.animation.FlingAnimation flingAnimationOf(K, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property);
+ method public static <K extends android.view.View> androidx.dynamicanimation.animation.SpringAnimation springAnimationOf(K, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property, float finalPosition = Float.NaN);
+ method public static androidx.dynamicanimation.animation.SpringAnimation withSpringForceProperties(androidx.dynamicanimation.animation.SpringAnimation, kotlin.jvm.functions.Function1<? super androidx.dynamicanimation.animation.SpringForce,kotlin.Unit> func);
+ }
+
+}
+
diff --git a/dynamic-animation/ktx/build.gradle b/dynamic-animation/ktx/build.gradle
index d2c0d63..95c9d3e 100644
--- a/dynamic-animation/ktx/build.gradle
+++ b/dynamic-animation/ktx/build.gradle
@@ -17,11 +17,7 @@
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
-import static androidx.build.dependencies.DependenciesKt.JUNIT
-import static androidx.build.dependencies.DependenciesKt.KOTLIN_STDLIB
-import static androidx.build.dependencies.DependenciesKt.TEST_RULES
-import static androidx.build.dependencies.DependenciesKt.TEST_RUNNER
-import static androidx.build.dependencies.DependenciesKt.TRUTH
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
@@ -46,6 +42,7 @@
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(TEST_RULES)
androidTestImplementation(TRUTH)
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
supportLibrary {
diff --git a/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/animation/DynamicAnimationTest.kt b/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/animation/DynamicAnimationTest.kt
new file mode 100644
index 0000000..1cce0dd
--- /dev/null
+++ b/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/animation/DynamicAnimationTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2018 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 androidx.dynamicanimation.animation
+
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class DynamicAnimationTest {
+
+ private val animObject = Any()
+ private lateinit var floatPropertyCompat: FloatPropertyCompat<Any>
+
+ /**
+ * Setup [FloatPropertyCompat] before test start
+ */
+ @Before fun setup() {
+ floatPropertyCompat = object : FloatPropertyCompat<Any>("") {
+
+ private var value = 0f
+
+ override fun getValue(`object`: Any?): Float {
+ return value
+ }
+
+ override fun setValue(`object`: Any?, value: Float) {
+ this.value = value
+ }
+ }
+ }
+
+ /**
+ * Test extension for creating [FlingAnimation]
+ */
+ @Test fun testCreateFlingAnimation() {
+ val flingAnimation = animObject.flingAnimationOf(floatPropertyCompat)
+ assertNotNull(flingAnimation)
+ assertEquals(flingAnimation.mTarget, animObject)
+ assertEquals(flingAnimation.mProperty, floatPropertyCompat)
+ assertTrue(flingAnimation.friction == 1f)
+ flingAnimation.friction = 1.5f
+ assertTrue(flingAnimation.friction == 1.5f)
+ }
+
+ /**
+ * Test extension for creating [SpringAnimation]
+ */
+ @Test fun testCreateSpringAnimation() {
+ val springAnimationWithoutFinalPosition = animObject.springAnimationOf(floatPropertyCompat)
+ assertNotNull(springAnimationWithoutFinalPosition)
+ assertEquals(springAnimationWithoutFinalPosition.mTarget, animObject)
+ assertEquals(springAnimationWithoutFinalPosition.mProperty, floatPropertyCompat)
+ assertNull(springAnimationWithoutFinalPosition.spring)
+ val springAnimationWithFinalPosition = animObject.springAnimationOf(floatPropertyCompat,
+ 100f)
+ assertNotNull(springAnimationWithFinalPosition)
+ assertEquals(springAnimationWithFinalPosition.mTarget, animObject)
+ assertEquals(springAnimationWithFinalPosition.mProperty, floatPropertyCompat)
+ assertNotNull(springAnimationWithFinalPosition.spring)
+ assertEquals(springAnimationWithFinalPosition.spring.finalPosition, 100f)
+ }
+
+ /**
+ * Test extension for setting up [SpringForce]
+ */
+ @Test fun testSpringForce() {
+ val springAnimationWithoutFinalPosition = animObject
+ .springAnimationOf(floatPropertyCompat)
+ .withSpringForceProperties {
+ finalPosition = 100f
+ dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
+ stiffness = SpringForce.STIFFNESS_HIGH
+ }
+ assertNotNull(springAnimationWithoutFinalPosition.spring)
+ assertEquals(springAnimationWithoutFinalPosition.spring.finalPosition, 100f)
+ assertEquals(springAnimationWithoutFinalPosition.spring.dampingRatio,
+ SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
+ assertEquals(springAnimationWithoutFinalPosition.spring.stiffness,
+ SpringForce.STIFFNESS_HIGH)
+ val springAnimationWithFinalPosition = animObject
+ .springAnimationOf(floatPropertyCompat, 120f)
+ .withSpringForceProperties {
+ dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ stiffness = SpringForce.STIFFNESS_LOW
+ }
+ assertNotNull(springAnimationWithFinalPosition.spring)
+ assertEquals(springAnimationWithFinalPosition.spring.finalPosition, 120f)
+ assertEquals(springAnimationWithFinalPosition.spring.dampingRatio,
+ SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ assertEquals(springAnimationWithFinalPosition.spring.stiffness,
+ SpringForce.STIFFNESS_LOW)
+ }
+}
\ No newline at end of file
diff --git a/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt b/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt
index 7a25b3e..bf0eaef 100644
--- a/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt
+++ b/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt
@@ -16,28 +16,26 @@
package androidx.dynamicanimation.animation
-import android.view.View
-
/**
- * Creates [FlingAnimation] for view.
+ * Creates [FlingAnimation] for object.
*
- * @param property View property to be animated.
+ * @param property object's property to be animated.
* @return [FlingAnimation]
*/
-inline fun <K : View> K.flingAnimationOf(property: FloatPropertyCompat<K>): FlingAnimation {
+inline fun <K> K.flingAnimationOf(property: FloatPropertyCompat<K>): FlingAnimation {
return FlingAnimation(this, property)
}
/**
- * Creates [SpringAnimation] for view.
+ * Creates [SpringAnimation] for object.
* If finalPosition is not [Float.NaN] then create [SpringAnimation] with
* [SpringForce.mFinalPosition].
*
- * @param property View property to be animated.
+ * @param property object's property to be animated.
* @param finalPosition [SpringForce.mFinalPosition] Final position of spring.
* @return [SpringAnimation]
*/
-inline fun <K : View> K.springAnimationOf(
+inline fun <K> K.springAnimationOf(
property: FloatPropertyCompat<K>,
finalPosition: Float = Float.NaN
): SpringAnimation {
diff --git a/dynamic-animation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java b/dynamic-animation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java
index b872bdc..6683eaf 100644
--- a/dynamic-animation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java
+++ b/dynamic-animation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java
@@ -102,7 +102,7 @@
}
/**
- * Test that spring animation can work with a single property without an object.
+ * Test that fling animation can work with a single property without an object.
*/
@Test
public void testFloatValueHolder() {
diff --git a/emoji/appcompat/build.gradle b/emoji/appcompat/build.gradle
index 332c667..2e8e6d6 100644
--- a/emoji/appcompat/build.gradle
+++ b/emoji/appcompat/build.gradle
@@ -29,7 +29,7 @@
supportLibrary {
name = "Android Emoji AppCompat"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.EMOJI
mavenGroup = LibraryGroups.EMOJI
inceptionYear = "2017"
description = "EmojiCompat Widgets for AppCompat integration"
diff --git a/emoji/bundled/build.gradle b/emoji/bundled/build.gradle
index 51be3c6..8b72f6f 100644
--- a/emoji/bundled/build.gradle
+++ b/emoji/bundled/build.gradle
@@ -22,7 +22,7 @@
supportLibrary {
name = "Android Emoji Compat"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.EMOJI
mavenGroup = LibraryGroups.EMOJI
inceptionYear = "2017"
description = "Library bundled with assets to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters."
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index 2c892ff..253b91c 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -56,7 +56,7 @@
supportLibrary {
name = "Android Emoji Compat"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.EMOJI
mavenGroup = LibraryGroups.EMOJI
inceptionYear = "2017"
description = "Core library to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters."
diff --git a/exifinterface/build.gradle b/exifinterface/build.gradle
index ffe28d3..df7161f 100644
--- a/exifinterface/build.gradle
+++ b/exifinterface/build.gradle
@@ -15,7 +15,7 @@
supportLibrary {
name = "Android Support ExifInterface"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.EXIFINTERFACE
mavenGroup = LibraryGroups.EXIFINTERFACE
inceptionYear = "2016"
description = "Android Support ExifInterface"
diff --git a/fragment/build.gradle b/fragment/build.gradle
index 5978565..0eacb2a 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -31,6 +31,7 @@
androidTestImplementation project(':internal-testutils'), {
exclude group: 'androidx.fragment', module: 'fragment'
}
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
supportLibrary {
diff --git a/fragment/ktx/build.gradle b/fragment/ktx/build.gradle
index feffd87..afe98c1 100644
--- a/fragment/ktx/build.gradle
+++ b/fragment/ktx/build.gradle
@@ -41,6 +41,7 @@
androidTestImplementation(TRUTH)
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(TEST_RULES)
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
supportLibrary {
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
index eebbe55..434170f 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
@@ -17,6 +17,8 @@
package androidx.fragment.app;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -1025,6 +1027,201 @@
fc2.dispatchDestroy();
}
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * not retained and the referrer fragment is not retained.
+ */
+ @Test
+ @UiThreadTest
+ public void targetFragmentNonRetainedNonRetained() {
+ final FragmentController fc = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+
+ final FragmentManager fm = fc.getSupportFragmentManager();
+
+ fc.attachHost(null);
+ fc.dispatchCreate();
+ fc.dispatchActivityCreated();
+ fc.noteStateNotSaved();
+ fc.execPendingActions();
+ fc.dispatchStart();
+ fc.dispatchResume();
+ fc.execPendingActions();
+
+ final Fragment target = new TargetFragment();
+ final Fragment referrer = new ReferrerFragment();
+ referrer.setTargetFragment(target, 0);
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fm.beginTransaction()
+ .remove(referrer)
+ .commitNow();
+
+ assertWithMessage("Target Fragment should be accessible after being removed")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fc.dispatchPause();
+ fc.dispatchStop();
+ fc.dispatchDestroy();
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * retained and the referrer fragment is not retained.
+ */
+ @Test
+ @UiThreadTest
+ public void targetFragmentRetainedNonRetained() {
+ final FragmentController fc = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+
+ final FragmentManager fm = fc.getSupportFragmentManager();
+
+ fc.attachHost(null);
+ fc.dispatchCreate();
+ fc.dispatchActivityCreated();
+ fc.noteStateNotSaved();
+ fc.execPendingActions();
+ fc.dispatchStart();
+ fc.dispatchResume();
+ fc.execPendingActions();
+
+ final Fragment target = new TargetFragment();
+ target.setRetainInstance(true);
+ final Fragment referrer = new ReferrerFragment();
+ referrer.setTargetFragment(target, 0);
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fm.beginTransaction()
+ .remove(referrer)
+ .commitNow();
+
+ assertWithMessage("Target Fragment should be accessible after being removed")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fc.dispatchPause();
+ fc.dispatchStop();
+ fc.dispatchDestroy();
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * not retained and the referrer fragment is retained.
+ */
+ @Test
+ @UiThreadTest
+ public void targetFragmentNonRetainedRetained() {
+ final FragmentController fc = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+
+ final FragmentManager fm = fc.getSupportFragmentManager();
+
+ fc.attachHost(null);
+ fc.dispatchCreate();
+ fc.dispatchActivityCreated();
+ fc.noteStateNotSaved();
+ fc.execPendingActions();
+ fc.dispatchStart();
+ fc.dispatchResume();
+ fc.execPendingActions();
+
+ final Fragment target = new TargetFragment();
+ final Fragment referrer = new ReferrerFragment();
+ referrer.setTargetFragment(target, 0);
+ referrer.setRetainInstance(true);
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ // Save the state
+ fc.dispatchPause();
+ fc.saveAllState();
+ fc.retainNestedNonConfig();
+ fc.dispatchStop();
+ fc.dispatchDestroy();
+
+
+ assertWithMessage("Target Fragment should be null after target Fragment destruction")
+ .that(referrer.getTargetFragment())
+ .isNull();
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * retained and the referrer fragment is also retained.
+ */
+ @Test
+ @UiThreadTest
+ public void targetFragmentRetainedRetained() {
+ final FragmentController fc = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+
+ final FragmentManager fm = fc.getSupportFragmentManager();
+
+ fc.attachHost(null);
+ fc.dispatchCreate();
+ fc.dispatchActivityCreated();
+ fc.noteStateNotSaved();
+ fc.execPendingActions();
+ fc.dispatchStart();
+ fc.dispatchResume();
+ fc.execPendingActions();
+
+ final Fragment target = new TargetFragment();
+ target.setRetainInstance(true);
+ final Fragment referrer = new ReferrerFragment();
+ referrer.setRetainInstance(true);
+ referrer.setTargetFragment(target, 0);
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+
+ // Save the state
+ fc.dispatchPause();
+ fc.saveAllState();
+ fc.retainNestedNonConfig();
+ fc.dispatchStop();
+ fc.dispatchDestroy();
+
+ assertWithMessage("Target Fragment should be accessible after FragmentManager destruction")
+ .that(referrer.getTargetFragment())
+ .isSameAs(target);
+ }
+
@Test
public void targetFragmentNoCycles() throws Throwable {
final Fragment one = new Fragment();
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.java
index 2e9c9bc..7864152 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.java
@@ -27,12 +27,14 @@
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
+import androidx.annotation.RequiresApi;
import androidx.fragment.app.test.FragmentTestActivity;
import androidx.fragment.test.R;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -81,6 +83,7 @@
@LargeTest
@Test
+ @SdkSuppress(minSdkVersion = 16) // waitForHalfFadeIn requires API 16
public void testChildFragmentManagerGone() throws Throwable {
final FragmentA fragmentA = new FragmentA();
final FragmentB fragmentB = new FragmentB();
@@ -127,6 +130,7 @@
FragmentTestUtil.popBackStackImmediate(mActivityRule);
}
+ @RequiresApi(16) // ViewTreeObserver.OnDrawListener was added in API 16
private void waitForHalfFadeIn(Fragment fragment) throws Throwable {
if (fragment.getView() == null) {
FragmentTestUtil.waitForExecution(mActivityRule);
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java b/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
index 0ba9b8b..79697ec 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
@@ -1039,6 +1039,15 @@
f.mHost = null;
f.mParentFragment = null;
f.mFragmentManager = null;
+ if (f.mTargetWho != null) {
+ Fragment target = mActive.get(f.mTargetWho);
+ if (target != null && target.getRetainInstance()) {
+ // Only keep references to other retained Fragments
+ // to avoid developers accessing Fragments that
+ // are never coming back
+ f.mTarget = target;
+ }
+ }
}
}
}
@@ -1338,6 +1347,11 @@
mActive.put(f.mWho, null);
removeRetainedFragment(f);
+ if (f.mTargetWho != null) {
+ // Restore the target Fragment so that it can be accessed
+ // even after the Fragment is removed.
+ f.mTarget = mActive.get(f.mTargetWho);
+ }
f.initState();
}
diff --git a/fragment/testing/build.gradle b/fragment/testing/build.gradle
index d0556a1..4786100 100644
--- a/fragment/testing/build.gradle
+++ b/fragment/testing/build.gradle
@@ -42,6 +42,7 @@
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(TEST_RULES)
androidTestImplementation(TRUTH)
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
supportLibrary {
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 30bcd11..503a1f6 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -28,7 +28,7 @@
supportLibrary {
name = "Android Support VectorDrawable"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.VECTORDRAWABLE
mavenGroup = LibraryGroups.VECTORDRAWABLE
inceptionYear = "2015"
description = "Android Support VectorDrawable"
diff --git a/heifwriter/build.gradle b/heifwriter/build.gradle
index 7728023..d7d1f58 100644
--- a/heifwriter/build.gradle
+++ b/heifwriter/build.gradle
@@ -23,7 +23,7 @@
supportLibrary {
name = "Android Support HeifWriter"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.HEIFWRITER
mavenGroup = LibraryGroups.HEIFWRITER
inceptionYear = "2018"
description = "Android Support HeifWriter for writing HEIF still images"
diff --git a/interpolator/build.gradle b/interpolator/build.gradle
index 480d1a3..8ec605a 100644
--- a/interpolator/build.gradle
+++ b/interpolator/build.gradle
@@ -12,7 +12,7 @@
supportLibrary {
name = "Android Support Library Interpolators"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.INTERPOLATOR
mavenGroup = LibraryGroups.INTERPOLATOR
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/legacy/core-ui/build.gradle b/legacy/core-ui/build.gradle
index 5d5140b..1298d4e 100644
--- a/legacy/core-ui/build.gradle
+++ b/legacy/core-ui/build.gradle
@@ -23,7 +23,7 @@
supportLibrary {
name = "Android Support Library core UI"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.LEGACY
mavenGroup = LibraryGroups.LEGACY
inceptionYear = "2011"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/legacy/core-utils/build.gradle b/legacy/core-utils/build.gradle
index c9303ef..10eb003 100644
--- a/legacy/core-utils/build.gradle
+++ b/legacy/core-utils/build.gradle
@@ -17,7 +17,7 @@
supportLibrary {
name = "Android Support Library core utils"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.LEGACY
mavenGroup = LibraryGroups.LEGACY
inceptionYear = "2011"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/lifecycle/savedstate-core/api/1.0.0-alpha01.txt b/lifecycle/savedstate-core/api/1.0.0-alpha01.txt
index 678b337f..51da4ec 100644
--- a/lifecycle/savedstate-core/api/1.0.0-alpha01.txt
+++ b/lifecycle/savedstate-core/api/1.0.0-alpha01.txt
@@ -1,29 +1,35 @@
// Signature format: 2.0
package androidx.lifecycle {
- public final class BundlableSavedStateRegistry implements androidx.lifecycle.SavedStateRegistry {
- ctor public BundlableSavedStateRegistry();
- method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
- method @MainThread public boolean isRestored();
+ public abstract class AbstractSavedStateRegistry<S> implements androidx.lifecycle.SavedStateRegistry<S> {
+ ctor public AbstractSavedStateRegistry();
+ method @MainThread public final S? consumeRestoredStateForKey(String);
+ method @MainThread public final boolean isRestored();
+ method @MainThread public final void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider<S>);
+ method @MainThread protected final void restoreSavedState(java.util.Map<java.lang.String,S>?);
+ method @MainThread protected final java.util.Map<java.lang.String,S> saveState();
+ method @MainThread public final void unregisterSavedStateProvider(String);
+ }
+
+ public final class BundleSavedStateRegistry extends androidx.lifecycle.AbstractSavedStateRegistry<android.os.Bundle> {
+ ctor public BundleSavedStateRegistry();
method @MainThread public void performRestore(android.os.Bundle?);
method @MainThread public void performSave(android.os.Bundle);
- method @MainThread public void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider);
- method @MainThread public void unregisterSavedStateProvider(String);
}
- public interface SavedStateRegistry {
- method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
+ public interface BundleSavedStateRegistryOwner {
+ method public androidx.lifecycle.SavedStateRegistry<android.os.Bundle> getBundleSavedStateRegistry();
+ }
+
+ public interface SavedStateRegistry<S> {
+ method @MainThread public S? consumeRestoredStateForKey(String);
method @MainThread public boolean isRestored();
- method @MainThread public void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider);
+ method @MainThread public void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider<S>);
method @MainThread public void unregisterSavedStateProvider(String);
}
- public static interface SavedStateRegistry.SavedStateProvider {
- method public android.os.Bundle saveState();
- }
-
- public interface SavedStateRegistryOwner {
- method public androidx.lifecycle.SavedStateRegistry getSavedState();
+ public static interface SavedStateRegistry.SavedStateProvider<S> {
+ method public S saveState();
}
}
diff --git a/lifecycle/savedstate-core/api/current.txt b/lifecycle/savedstate-core/api/current.txt
index 678b337f..51da4ec 100644
--- a/lifecycle/savedstate-core/api/current.txt
+++ b/lifecycle/savedstate-core/api/current.txt
@@ -1,29 +1,35 @@
// Signature format: 2.0
package androidx.lifecycle {
- public final class BundlableSavedStateRegistry implements androidx.lifecycle.SavedStateRegistry {
- ctor public BundlableSavedStateRegistry();
- method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
- method @MainThread public boolean isRestored();
+ public abstract class AbstractSavedStateRegistry<S> implements androidx.lifecycle.SavedStateRegistry<S> {
+ ctor public AbstractSavedStateRegistry();
+ method @MainThread public final S? consumeRestoredStateForKey(String);
+ method @MainThread public final boolean isRestored();
+ method @MainThread public final void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider<S>);
+ method @MainThread protected final void restoreSavedState(java.util.Map<java.lang.String,S>?);
+ method @MainThread protected final java.util.Map<java.lang.String,S> saveState();
+ method @MainThread public final void unregisterSavedStateProvider(String);
+ }
+
+ public final class BundleSavedStateRegistry extends androidx.lifecycle.AbstractSavedStateRegistry<android.os.Bundle> {
+ ctor public BundleSavedStateRegistry();
method @MainThread public void performRestore(android.os.Bundle?);
method @MainThread public void performSave(android.os.Bundle);
- method @MainThread public void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider);
- method @MainThread public void unregisterSavedStateProvider(String);
}
- public interface SavedStateRegistry {
- method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
+ public interface BundleSavedStateRegistryOwner {
+ method public androidx.lifecycle.SavedStateRegistry<android.os.Bundle> getBundleSavedStateRegistry();
+ }
+
+ public interface SavedStateRegistry<S> {
+ method @MainThread public S? consumeRestoredStateForKey(String);
method @MainThread public boolean isRestored();
- method @MainThread public void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider);
+ method @MainThread public void registerSavedStateProvider(String, androidx.lifecycle.SavedStateRegistry.SavedStateProvider<S>);
method @MainThread public void unregisterSavedStateProvider(String);
}
- public static interface SavedStateRegistry.SavedStateProvider {
- method public android.os.Bundle saveState();
- }
-
- public interface SavedStateRegistryOwner {
- method public androidx.lifecycle.SavedStateRegistry getSavedState();
+ public static interface SavedStateRegistry.SavedStateProvider<S> {
+ method public S saveState();
}
}
diff --git a/lifecycle/savedstate-core/build.gradle b/lifecycle/savedstate-core/build.gradle
index 62e3460..798f658 100644
--- a/lifecycle/savedstate-core/build.gradle
+++ b/lifecycle/savedstate-core/build.gradle
@@ -28,6 +28,10 @@
api(SUPPORT_ANNOTATIONS)
implementation(ARCH_CORE_COMMON)
+ testImplementation(JUNIT)
+ testImplementation(TEST_RUNNER)
+ testImplementation(KOTLIN_STDLIB)
+
androidTestImplementation(JUNIT)
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(KOTLIN_STDLIB)
diff --git a/lifecycle/savedstate-core/src/androidTest/java/androidx/lifecycle/BundlableSavedStateRegistryTest.kt b/lifecycle/savedstate-core/src/androidTest/java/androidx/lifecycle/BundlableSavedStateRegistryTest.kt
index 31e537a..3f36f50 100644
--- a/lifecycle/savedstate-core/src/androidTest/java/androidx/lifecycle/BundlableSavedStateRegistryTest.kt
+++ b/lifecycle/savedstate-core/src/androidTest/java/androidx/lifecycle/BundlableSavedStateRegistryTest.kt
@@ -19,38 +19,24 @@
import android.os.Bundle
import androidx.test.filters.SmallTest
import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.nullValue
import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@SmallTest
@RunWith(JUnit4::class)
-class SavedStateRegistryTest {
-
- @Test
- fun registerWithSameKey() {
- val registry = BundlableSavedStateRegistry()
- registry.registerSavedStateProvider("key") { Bundle.EMPTY }
- try {
- registry.registerSavedStateProvider("key") { Bundle.EMPTY }
- Assert.fail("can't register with the same key")
- } catch (e: IllegalArgumentException) {
- // fail as expected
- }
- }
+class BundlableSavedStateRegistryTest {
@Test
fun saveRestoreFlow() {
- val registry = BundlableSavedStateRegistry()
+ val registry = BundleSavedStateRegistry()
registry.registerSavedStateProvider("a") { bundleOf("foo", 1) }
registry.registerSavedStateProvider("b") { bundleOf("foo", 2) }
val state = Bundle()
registry.performSave(state)
- val newRegistry = BundlableSavedStateRegistry()
+ val newRegistry = BundleSavedStateRegistry()
newRegistry.performRestore(state)
assertThat(newRegistry.isRestored, `is`(true))
@@ -60,69 +46,6 @@
assertThat(bundleForA.isSame(bundleOf("foo", 1)), `is`(true))
assertThat(bundleForB.isSame(bundleOf("foo", 2)), `is`(true))
}
-
- @Test
- fun consumeSameTwice() {
- val registry = BundlableSavedStateRegistry()
- registry.registerSavedStateProvider("a") { bundleOf("foo", 1) }
- val state = Bundle()
- registry.performSave(state)
-
- val newStore = BundlableSavedStateRegistry()
- newStore.performRestore(state)
-
- assertThat(newStore.isRestored, `is`(true))
- val bundleForA = newStore.consumeRestoredStateForKey("a")
- assertThat(bundleForA.isSame(bundleOf("foo", 1)), `is`(true))
- assertThat(newStore.consumeRestoredStateForKey("a"), nullValue())
- }
-
- @Test
- fun unregister() {
- val registry = BundlableSavedStateRegistry()
- registry.registerSavedStateProvider("a") { bundleOf("foo", 1) }
- registry.unregisterSavedStateProvider("a")
- // this call should succeed
- registry.registerSavedStateProvider("a") { bundleOf("foo", 2) }
- registry.unregisterSavedStateProvider("a")
- val state = Bundle()
- registry.performRestore(state)
- assertThat(state.isEmpty, `is`(true))
- }
-
- @Test
- fun unconsumedSavedState() {
- val registry = BundlableSavedStateRegistry()
- registry.registerSavedStateProvider("a") { bundleOf("foo", 1) }
- val savedState1 = Bundle()
- registry.performSave(savedState1)
- val intermediateStore = BundlableSavedStateRegistry()
- intermediateStore.performRestore(savedState1)
- val savedState2 = Bundle()
- intermediateStore.performSave(savedState2)
- val newRegistry = BundlableSavedStateRegistry()
- newRegistry.performRestore(savedState2)
- val bundleForA = newRegistry.consumeRestoredStateForKey("a")
- assertThat(bundleForA.isSame(bundleOf("foo", 1)), `is`(true))
- }
-
- @Test
- fun unconsumedSavedStateClashWithCallback() {
- val registry = BundlableSavedStateRegistry()
- registry.registerSavedStateProvider("a") { bundleOf("foo", 1) }
- val savedState1 = Bundle()
- registry.performSave(savedState1)
- val intermediateStore = BundlableSavedStateRegistry()
- // there is unconsumed value for "a"
- intermediateStore.performRestore(savedState1)
- intermediateStore.registerSavedStateProvider("a") { bundleOf("foo", 2) }
- val savedState2 = Bundle()
- intermediateStore.performSave(savedState2)
- val newStore = BundlableSavedStateRegistry()
- newStore.performRestore(savedState2)
- val bundleForA = newStore.consumeRestoredStateForKey("a")
- assertThat(bundleForA.isSame(bundleOf("foo", 2)), `is`(true))
- }
}
private fun bundleOf(key: String, value: Int): Bundle {
diff --git a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/AbstractSavedStateRegistry.java b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/AbstractSavedStateRegistry.java
new file mode 100644
index 0000000..edd7b6e
--- /dev/null
+++ b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/AbstractSavedStateRegistry.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2018 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 androidx.lifecycle;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.internal.SafeIterableMap;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This class provides a skeletal implementation of the {@link SavedStateRegistry}.
+ * <p>
+ * Implementations simply need to call {@link #restoreSavedState(Map)} to initialize restored state
+ * and call {@link #saveState()} once system requests saved state.
+ *
+ * @param <S> represents a class for saving a state, typically it is {@link Bundle}
+ *
+ * @see BundleSavedStateRegistry
+ */
+@SuppressLint("RestrictedApi")
+public abstract class AbstractSavedStateRegistry<S> implements SavedStateRegistry<S> {
+ private SafeIterableMap<String, SavedStateProvider<S>> mComponents =
+ new SafeIterableMap<>();
+ private Map<String, S> mSavedState;
+ private boolean mRestored;
+
+ @MainThread
+ @Nullable
+ @Override
+ public final S consumeRestoredStateForKey(@NonNull String key) {
+ if (!mRestored) {
+ throw new IllegalStateException("You can consumeRestoredStateForKey "
+ + "only after super.onCreate of corresponding component");
+ }
+ S state = null;
+ if (mSavedState != null) {
+ state = mSavedState.remove(key);
+ if (mSavedState.isEmpty()) {
+ mSavedState = null;
+ }
+ }
+ return state;
+ }
+
+ @MainThread
+ @Override
+ public final void registerSavedStateProvider(@NonNull String key,
+ @NonNull SavedStateProvider<S> provider) {
+ SavedStateProvider<S> previous = mComponents.putIfAbsent(key, provider);
+ if (previous != null) {
+ throw new IllegalArgumentException("SavedStateProvider with the given key is"
+ + " already registered");
+ }
+ }
+
+ /**
+ * Unregisters a component previously registered by the given {@code key}
+ *
+ * @param key a key with which a component was previously registered.
+ */
+ @MainThread
+ @Override
+ public final void unregisterSavedStateProvider(@NonNull String key) {
+ mComponents.remove(key);
+ }
+
+ /**
+ * Returns if state was restored after creation and can be safely consumed
+ * with {@link #consumeRestoredStateForKey(String)}
+ *
+ * @return true if state was restored.
+ */
+ @MainThread
+ @Override
+ public final boolean isRestored() {
+ return mRestored;
+ }
+
+ /**
+ * Subclasses of this {@code AbstractSavedStateRegistry} should call this
+ * method to initialize restored state.
+ */
+ @SuppressWarnings("WeakerAccess")
+ @MainThread
+ protected final void restoreSavedState(@Nullable Map<String, S> initialState) {
+ if (initialState != null) {
+ mSavedState = new HashMap<>(initialState);
+ }
+ mRestored = true;
+ }
+
+ /**
+ * Subclasses of this {@code AbstractSavedStateRegistry} should call this
+ * method to perform state saving, this method will call all registered providers and
+ * merge a state provided by them with all unconsumed values since previous restoration.
+ *
+ * @return state that should be saved.
+ */
+ @MainThread
+ @NonNull
+ protected final Map<String, S> saveState() {
+ Map<String, S> savedState = new HashMap<>();
+ if (mSavedState != null) {
+ savedState.putAll(mSavedState);
+ }
+ for (Iterator<Map.Entry<String, SavedStateProvider<S>>> it =
+ mComponents.iteratorWithAdditions(); it.hasNext(); ) {
+ Map.Entry<String, SavedStateProvider<S>> entry = it.next();
+ savedState.put(entry.getKey(), entry.getValue().saveState());
+ }
+ return savedState;
+ }
+}
diff --git a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundlableSavedStateRegistry.java b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundlableSavedStateRegistry.java
deleted file mode 100644
index e744236..0000000
--- a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundlableSavedStateRegistry.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2018 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 androidx.lifecycle;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.internal.SafeIterableMap;
-import androidx.lifecycle.SavedStateRegistry.SavedStateProvider;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * A default implementation of {@link SavedStateRegistry} backed by {@link Bundle}.
- * <p>
- * An owner of this {@link BundlableSavedStateRegistry} must call {@link #performRestore(Bundle)}
- * once previously saved state becomes available to it.
- * <p>
- * To collect saved state supplied by {@link SavedStateProvider}
- * an owner should call {@link #performSave(Bundle)}
- */
-@SuppressLint("RestrictedApi")
-public final class BundlableSavedStateRegistry implements SavedStateRegistry {
- private static final String SAVED_COMPONENTS_KEY =
- "androidx.lifecycle.BundlableSavedStateRegistry.key";
- private SafeIterableMap<String, SavedStateProvider> mComponents = new SafeIterableMap<>();
- private Bundle mSavedState;
- private boolean mRestored;
-
- @MainThread
- @Nullable
- @Override
- public Bundle consumeRestoredStateForKey(@NonNull String key) {
- if (!mRestored) {
- throw new IllegalStateException("You can consumeRestoredStateForKey "
- + "only after super.onCreate of corresponding component");
- }
- Bundle state = null;
- if (mSavedState != null) {
- state = mSavedState.getBundle(key);
- mSavedState.remove(key);
- if (mSavedState.isEmpty()) {
- mSavedState = null;
- }
- }
- return state;
- }
-
- @MainThread
- @Override
- public void registerSavedStateProvider(@NonNull String key,
- @NonNull SavedStateProvider provider) {
- SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
- if (previous != null) {
- throw new IllegalArgumentException("SavedStateProvider with the given key is"
- + " already registered");
- }
- }
-
- /**
- * Unregisters a component previously registered by the given {@code key}
- *
- * @param key a key with which a component was previously registered.
- */
- @MainThread
- @Override
- public void unregisterSavedStateProvider(@NonNull String key) {
- mComponents.remove(key);
- }
-
- /**
- * Returns if state was restored after creation and can be safely consumed
- * with {@link #consumeRestoredStateForKey(String)}
- * @return true if state was restored.
- */
- @MainThread
- @Override
- public boolean isRestored() {
- return mRestored;
- }
-
- /**
- * An interface for an owner of this @{code {@link SavedStateRegistry} to restore saved state.
- * @param savedState restored state
- */
- @SuppressWarnings("WeakerAccess")
- @MainThread
- public void performRestore(@Nullable Bundle savedState) {
- mSavedState = savedState != null ? savedState.getBundle(SAVED_COMPONENTS_KEY) : null;
- mRestored = true;
- }
-
- /**
- * An interface for an owner of this @{code {@link SavedStateRegistry}
- * to perform state saving, it will call all registered providers and
- * merge with unconsumed state.
- *
- * @param outBundle Bundle in which to place a saved state
- */
- @MainThread
- public void performSave(@NonNull Bundle outBundle) {
- Bundle res = mSavedState == null ? new Bundle() : new Bundle(mSavedState);
- for (Iterator<Map.Entry<String, SavedStateProvider>> it =
- mComponents.iteratorWithAdditions(); it.hasNext(); ) {
- Map.Entry<String, SavedStateProvider> entry = it.next();
- res.putBundle(entry.getKey(), entry.getValue().saveState());
- }
- outBundle.putBundle(SAVED_COMPONENTS_KEY, res);
- }
-}
diff --git a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundleSavedStateRegistry.java b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundleSavedStateRegistry.java
new file mode 100644
index 0000000..da640dc
--- /dev/null
+++ b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundleSavedStateRegistry.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 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 androidx.lifecycle;
+
+import android.os.Bundle;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A default implementation of {@link SavedStateRegistry} backed by {@link Bundle}.
+ * <p>
+ * An owner of this {@link BundleSavedStateRegistry} must call {@link #performRestore(Bundle)}
+ * once previously saved state becomes available to it.
+ * <p>
+ * To collect saved state supplied by {@link SavedStateProvider}
+ * an owner should call {@link #performSave(Bundle)}
+ */
+public final class BundleSavedStateRegistry extends AbstractSavedStateRegistry<Bundle> {
+ private static final String SAVED_COMPONENTS_KEY =
+ "androidx.lifecycle.BundlableSavedStateRegistry.key";
+
+ /**
+ * An interface for an owner of this @{code {@link SavedStateRegistry} to restore saved state.
+ *
+ * @param savedState restored state
+ */
+ @SuppressWarnings("WeakerAccess")
+ @MainThread
+ public void performRestore(@Nullable Bundle savedState) {
+ Bundle componentsState = savedState != null ? savedState.getBundle(SAVED_COMPONENTS_KEY)
+ : null;
+ if (componentsState == null || componentsState.isEmpty()) {
+ restoreSavedState(null);
+ return;
+ }
+ Map<String, Bundle> initialState = new HashMap<>();
+ for (String key : componentsState.keySet()) {
+ initialState.put(key, componentsState.getBundle(key));
+ }
+ restoreSavedState(initialState);
+ }
+
+ /**
+ * An interface for an owner of this @{code {@link SavedStateRegistry}
+ * to perform state saving, it will call all registered providers and
+ * merge with unconsumed state.
+ *
+ * @param outBundle Bundle in which to place a saved state
+ */
+ @MainThread
+ public void performSave(@NonNull Bundle outBundle) {
+ Map<String, Bundle> bundleMap = saveState();
+ Bundle components = new Bundle();
+ for (Map.Entry<String, Bundle> entry : bundleMap.entrySet()) {
+ components.putBundle(entry.getKey(), entry.getValue());
+ }
+ outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
+ }
+}
diff --git a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistryOwner.java b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundleSavedStateRegistryOwner.java
similarity index 86%
rename from lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistryOwner.java
rename to lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundleSavedStateRegistryOwner.java
index 9ac507b..d34382a 100644
--- a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistryOwner.java
+++ b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/BundleSavedStateRegistryOwner.java
@@ -17,17 +17,19 @@
package androidx.lifecycle;
+import android.os.Bundle;
+
import androidx.annotation.NonNull;
/**
* A scope that owns {@link SavedStateRegistry}
*/
-public interface SavedStateRegistryOwner {
+public interface BundleSavedStateRegistryOwner {
/**
* Returns owned {@link SavedStateRegistry}
*
* @return a {@link SavedStateRegistry}
*/
@NonNull
- SavedStateRegistry getSavedState();
+ SavedStateRegistry<Bundle> getBundleSavedStateRegistry();
}
diff --git a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistry.java b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistry.java
index ade256e..af77b34 100644
--- a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistry.java
+++ b/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistry.java
@@ -23,9 +23,11 @@
import androidx.annotation.Nullable;
/**
- * A class for managing saved state.
+ * An interface for plugging components that consumes and contributes to the saved state.
+ *
+ * @param <S> represents a class for saving a state, typically it is {@link Bundle}
*/
-public interface SavedStateRegistry {
+public interface SavedStateRegistry<S> {
/**
* Consumes saved state previously supplied by {@link SavedStateProvider} registered
* via {@link #registerSavedStateProvider(String, SavedStateProvider)}
@@ -42,11 +44,11 @@
* that a saved state can be safely consumed.
*
* @param key a key with which {@link SavedStateProvider} was previously registered.
- * @return {@code Bundle} with the previously saved state or {@code null}
+ * @return {@code S} with the previously saved state or {@code null}
*/
@MainThread
@Nullable
- Bundle consumeRestoredStateForKey(@NonNull String key);
+ S consumeRestoredStateForKey(@NonNull String key);
/**
* Returns if a state was restored and can be safely consumed
@@ -59,8 +61,10 @@
/**
* This interface marks a component that contributes to saved state.
+ *
+ * @param <S> represents a class for saving a state, typically it is {@link Bundle}
*/
- interface SavedStateProvider {
+ interface SavedStateProvider<S> {
/**
* Called to retrieve a state from a component before being killed
* so later the state can be received from {@link #consumeRestoredStateForKey(String)}
@@ -68,7 +72,7 @@
* @return Bundle with your saved state.
*/
@NonNull
- Bundle saveState();
+ S saveState();
}
/**
@@ -87,7 +91,7 @@
*/
@MainThread
void registerSavedStateProvider(@NonNull String key,
- @NonNull SavedStateProvider savedStateProvider);
+ @NonNull SavedStateProvider<S> savedStateProvider);
/**
* Unregisters a component previously registered by the given {@code key}
diff --git a/lifecycle/savedstate-core/src/test/java/androidx/lifecycle/AbstractSavedStateRegistryTest.kt b/lifecycle/savedstate-core/src/test/java/androidx/lifecycle/AbstractSavedStateRegistryTest.kt
new file mode 100644
index 0000000..61d8195
--- /dev/null
+++ b/lifecycle/savedstate-core/src/test/java/androidx/lifecycle/AbstractSavedStateRegistryTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2018 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 androidx.lifecycle
+
+import androidx.test.filters.SmallTest
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AbstractSavedStateRegistryTest {
+
+ @Test
+ fun registerWithSameKey() {
+ val registry = TestSavedStateRegistry()
+ registry.registerSavedStateProvider("key") { "a" }
+ try {
+ registry.registerSavedStateProvider("key") { "b" }
+ Assert.fail("can't register with the same key")
+ } catch (e: IllegalArgumentException) {
+ // fail as expected
+ }
+ }
+
+ @Test
+ fun saveRestoreFlow() {
+ val registry = TestSavedStateRegistry()
+ registry.registerSavedStateProvider("a") { "bla" }
+ registry.registerSavedStateProvider("b") { "boo" }
+ val savedState = registry.savedState()
+
+ val newRegistry = TestSavedStateRegistry()
+ newRegistry.restoreState(savedState)
+
+ assertThat(newRegistry.isRestored, `is`(true))
+ assertThat(newRegistry.consumeRestoredStateForKey("a"), `is`("bla"))
+ assertThat(newRegistry.consumeRestoredStateForKey("b"), `is`("boo"))
+ }
+
+ @Test
+ fun consumeSameTwice() {
+ val registry = TestSavedStateRegistry()
+ registry.registerSavedStateProvider("a") { "foo" }
+ val state = registry.savedState()
+
+ val newStore = TestSavedStateRegistry()
+ newStore.restoreState(state)
+
+ assertThat(newStore.isRestored, `is`(true))
+ assertThat(newStore.consumeRestoredStateForKey("a"), `is`("foo"))
+ assertThat(newStore.consumeRestoredStateForKey("a"), nullValue())
+ }
+
+ @Test
+ fun unregister() {
+ val registry = TestSavedStateRegistry()
+ registry.registerSavedStateProvider("a") { "foo" }
+ registry.unregisterSavedStateProvider("a")
+ // this call should succeed
+ registry.registerSavedStateProvider("a") { "foo" }
+ registry.unregisterSavedStateProvider("a")
+
+ assertThat(registry.savedState().isEmpty(), `is`(true))
+ }
+
+ @Test
+ fun unconsumedSavedState() {
+ val registry = TestSavedStateRegistry()
+ registry.registerSavedStateProvider("a") { "foo" }
+ val savedState1 = registry.savedState()
+ val intermediateStore = TestSavedStateRegistry()
+ intermediateStore.restoreState(savedState1)
+ val savedState2 = intermediateStore.savedState()
+ val newRegistry = TestSavedStateRegistry()
+ newRegistry.restoreState(savedState2)
+ assertThat(newRegistry.consumeRestoredStateForKey("a"), `is`("foo"))
+ }
+
+ @Test
+ fun unconsumedSavedStateClashWithCallback() {
+ val registry = TestSavedStateRegistry()
+ registry.registerSavedStateProvider("a") { "foo" }
+ val savedState1 = registry.savedState()
+ val intermediateStore = TestSavedStateRegistry()
+ intermediateStore.restoreState(savedState1)
+ // there is unconsumed value for "a"
+ intermediateStore.registerSavedStateProvider("a") { "bar" }
+ val savedState2 = intermediateStore.savedState()
+ val newStore = TestSavedStateRegistry()
+ newStore.restoreState(savedState2)
+ assertThat(newStore.consumeRestoredStateForKey("a"), `is`("bar"))
+ }
+
+ class TestSavedStateRegistry : AbstractSavedStateRegistry<String>() {
+ fun restoreState(state: Map<String, String>) {
+ restoreSavedState(state)
+ }
+ fun savedState() = saveState()
+ }
+}
diff --git a/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/SavedStateRegistriesTest.java b/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/SavedStateRegistriesTest.java
index ab6003e..57801ac 100644
--- a/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/SavedStateRegistriesTest.java
+++ b/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/SavedStateRegistriesTest.java
@@ -27,7 +27,7 @@
import androidx.annotation.NonNull;
import androidx.arch.core.util.Function;
-import androidx.lifecycle.BundlableSavedStateRegistry;
+import androidx.lifecycle.BundleSavedStateRegistry;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
@@ -62,7 +62,7 @@
return Arrays.asList(FRAGMENT_MODE, ACTIVITY_MODE);
}
- private BundlableSavedStateRegistry testedSavedStateRegistry(
+ private BundleSavedStateRegistry testedSavedStateRegistry(
SavedStateActivity currentActivity) {
if (FRAGMENT_MODE.equals(mode)) {
return SavedStateRegistries.of(currentActivity.getFragment());
@@ -100,7 +100,7 @@
Lifecycle.State currentState = testedLifecycleOwner(activity)
.getLifecycle().getCurrentState();
assertThat(currentState.isAtLeast(Lifecycle.State.CREATED), is(true));
- BundlableSavedStateRegistry store = testedSavedStateRegistry(activity);
+ BundleSavedStateRegistry store = testedSavedStateRegistry(activity);
assertThat(store.consumeRestoredStateForKey(CALLBACK_KEY), nullValue());
testedSavedStateRegistry(activity)
.registerSavedStateProvider(CALLBACK_KEY, new DefaultProvider());
@@ -146,9 +146,9 @@
SavedStateActivity activity = initializeSavedState();
SavedStateActivity.duringOnCreate(FRAGMENT_MODE.equals(mode),
- new Function<BundlableSavedStateRegistry, Void>() {
+ new Function<BundleSavedStateRegistry, Void>() {
@Override
- public Void apply(BundlableSavedStateRegistry store) {
+ public Void apply(BundleSavedStateRegistry store) {
checkDefaultSavedState(store);
return null;
}
@@ -156,7 +156,7 @@
recreateActivity(activity, mActivityRule);
}
- private static class DefaultProvider implements SavedStateRegistry.SavedStateProvider {
+ private static class DefaultProvider implements SavedStateRegistry.SavedStateProvider<Bundle> {
@NonNull
@Override
public Bundle saveState() {
@@ -166,7 +166,7 @@
}
}
- private static void checkDefaultSavedState(BundlableSavedStateRegistry store) {
+ private static void checkDefaultSavedState(BundleSavedStateRegistry store) {
Bundle savedState = store.consumeRestoredStateForKey(CALLBACK_KEY);
assertThat(savedState, notNullValue());
//noinspection ConstantConditions
diff --git a/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/activity/SavedStateActivity.java b/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/activity/SavedStateActivity.java
index b9dc95d..26e6c63 100644
--- a/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/activity/SavedStateActivity.java
+++ b/lifecycle/savedstate-fragment/src/androidTest/java/androidx/lifecycle/savedstate/activity/SavedStateActivity.java
@@ -23,29 +23,29 @@
import androidx.arch.core.util.Function;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.BundlableSavedStateRegistry;
+import androidx.lifecycle.BundleSavedStateRegistry;
import androidx.lifecycle.SavedStateRegistries;
public class SavedStateActivity extends FragmentActivity {
private static final String TAG = "fragment";
- private static Function<BundlableSavedStateRegistry, Void> sOnCreateRunnable;
+ private static Function<BundleSavedStateRegistry, Void> sOnCreateRunnable;
private static boolean sInFragment;
public static void duringOnCreate(boolean inFragment,
- Function<BundlableSavedStateRegistry, Void> f) {
+ Function<BundleSavedStateRegistry, Void> f) {
sInFragment = inFragment;
sOnCreateRunnable = f;
}
- private static void shotOnCreateRunnable(BundlableSavedStateRegistry store) {
+ private static void shotOnCreateRunnable(BundleSavedStateRegistry store) {
if (sOnCreateRunnable != null) {
sOnCreateRunnable.apply(store);
sOnCreateRunnable = null;
}
}
- private BundlableSavedStateRegistry mSavedStateStore;
+ private BundleSavedStateRegistry mSavedStateStore;
public SavedStateActivity() {
if (!sInFragment && sOnCreateRunnable != null) {
@@ -70,7 +70,7 @@
}
public static class FragmentWithRunnable extends Fragment {
- private BundlableSavedStateRegistry mSavedStateStore;
+ private BundleSavedStateRegistry mSavedStateStore;
@SuppressWarnings("WeakerAccess")
public FragmentWithRunnable() {
if (sInFragment && sOnCreateRunnable != null) {
diff --git a/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateHolderFragmentController.java b/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateHolderFragmentController.java
index 40a8653..05a74d8 100644
--- a/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateHolderFragmentController.java
+++ b/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateHolderFragmentController.java
@@ -42,7 +42,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static class SavedStateHolderFragment extends Fragment {
- private BundlableSavedStateRegistry mRegistry;
+ private BundleSavedStateRegistry mRegistry;
@SuppressWarnings({"WeakerAccess", "unused"})
public SavedStateHolderFragment() {
@@ -50,7 +50,7 @@
@SuppressLint("ValidFragment")
@SuppressWarnings("WeakerAccess")
- SavedStateHolderFragment(BundlableSavedStateRegistry registry) {
+ SavedStateHolderFragment(BundleSavedStateRegistry registry) {
mRegistry = registry;
}
@@ -62,14 +62,14 @@
getParentFragment() != null ? getParentFragment() : getActivity());
}
if (mRegistry == null) {
- mRegistry = new BundlableSavedStateRegistry();
+ mRegistry = new BundleSavedStateRegistry();
}
if (!mRegistry.isRestored()) {
mRegistry.performRestore(savedInstanceState);
}
}
- BundlableSavedStateRegistry getSavedStateStore() {
+ BundleSavedStateRegistry getSavedStateStore() {
return mRegistry;
}
@@ -90,7 +90,7 @@
static final String HOLDER_TAG = "androidx.lifecycle.SavedStateHolderFragment";
@SuppressWarnings("WeakerAccess")
- Map<LifecycleOwner, BundlableSavedStateRegistry> mNotCommittedStores = new HashMap<>();
+ Map<LifecycleOwner, BundleSavedStateRegistry> mNotCommittedStores = new HashMap<>();
@SuppressWarnings("WeakerAccess")
static SavedStateHolderFragment findHolderFragment(FragmentManager manager) {
@@ -106,10 +106,10 @@
return (SavedStateHolderFragment) fragmentByTag;
}
- private BundlableSavedStateRegistry createHolderFragment(
+ private BundleSavedStateRegistry createHolderFragment(
final FragmentManagerCall fragmentManager, LifecycleOwner owner) {
- final BundlableSavedStateRegistry registry = new BundlableSavedStateRegistry();
+ final BundleSavedStateRegistry registry = new BundleSavedStateRegistry();
if (fragmentManager.call() != null) {
registry.performRestore(null);
@@ -138,7 +138,7 @@
}
if (event == Lifecycle.Event.ON_DESTROY) {
source.getLifecycle().removeObserver(this);
- BundlableSavedStateRegistry notCommitted = mNotCommittedStores.remove(source);
+ BundleSavedStateRegistry notCommitted = mNotCommittedStores.remove(source);
if (notCommitted != null) {
Log.e(LOG_TAG, "Failed to save a SavedStateComponents for " + source);
}
@@ -148,7 +148,7 @@
return registry;
}
- private BundlableSavedStateRegistry savedStateRegistry(FragmentManagerCall fragmentManager,
+ private BundleSavedStateRegistry savedStateRegistry(FragmentManagerCall fragmentManager,
LifecycleOwner owner) {
FragmentManager fm = fragmentManager.call();
if (fm != null) {
@@ -157,7 +157,7 @@
return holder.getSavedStateStore();
}
}
- BundlableSavedStateRegistry store = mNotCommittedStores.get(owner);
+ BundleSavedStateRegistry store = mNotCommittedStores.get(owner);
if (store != null) {
return store;
}
@@ -165,11 +165,11 @@
return createHolderFragment(fragmentManager, owner);
}
- static BundlableSavedStateRegistry savedStateRegistry(FragmentActivity activity) {
+ static BundleSavedStateRegistry savedStateRegistry(FragmentActivity activity) {
return sHolderFragmentManager.savedStateRegistry(fragmentManager(activity), activity);
}
- static BundlableSavedStateRegistry savedStateRegistry(Fragment parentFragment) {
+ static BundleSavedStateRegistry savedStateRegistry(Fragment parentFragment) {
return sHolderFragmentManager.savedStateRegistry(fragmentManager(parentFragment),
parentFragment);
}
diff --git a/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateRegistries.java b/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateRegistries.java
index 9578108..44f7f9d 100644
--- a/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateRegistries.java
+++ b/lifecycle/savedstate-fragment/src/main/java/androidx/lifecycle/SavedStateRegistries.java
@@ -21,7 +21,7 @@
import androidx.fragment.app.FragmentActivity;
/**
- * Provides simple accessor for {@link BundlableSavedStateRegistry} of Activity and Fragments
+ * Provides simple accessor for {@link BundleSavedStateRegistry} of Activity and Fragments
*/
public class SavedStateRegistries {
@@ -29,20 +29,20 @@
}
/**
- * Returns {@link BundlableSavedStateRegistry} for the given fragment.
- * @param fragment a fragment whose {@link BundlableSavedStateRegistry} is requested
+ * Returns {@link BundleSavedStateRegistry} for the given fragment.
+ * @param fragment a fragment whose {@link BundleSavedStateRegistry} is requested
* @return a {@code SavedStateStore}
*/
- public static BundlableSavedStateRegistry of(Fragment fragment) {
+ public static BundleSavedStateRegistry of(Fragment fragment) {
return SavedStateHolderFragmentController.savedStateRegistry(fragment);
}
/**
- * Returns {@link BundlableSavedStateRegistry} for the given activity.
- * @param activity a activity whose {@link BundlableSavedStateRegistry} is requested
+ * Returns {@link BundleSavedStateRegistry} for the given activity.
+ * @param activity a activity whose {@link BundleSavedStateRegistry} is requested
* @return a {@code SavedStateStore}
*/
- public static BundlableSavedStateRegistry of(FragmentActivity activity) {
+ public static BundleSavedStateRegistry of(FragmentActivity activity) {
return SavedStateHolderFragmentController.savedStateRegistry(activity);
}
}
diff --git a/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/VMSavedStateInitializer.java b/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/VMSavedStateInitializer.java
index 8cf0d1b..4bae785 100644
--- a/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/VMSavedStateInitializer.java
+++ b/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/VMSavedStateInitializer.java
@@ -89,7 +89,7 @@
}
@SuppressWarnings("WeakerAccess")
- static void attach(BundlableSavedStateRegistry savedStateStore, ViewModelStoreOwner store) {
+ static void attach(BundleSavedStateRegistry savedStateStore, ViewModelStoreOwner store) {
ViewModelStore viewModelStore = store.getViewModelStore();
for (String key : viewModelStore.keys()) {
ViewModel viewModel = viewModelStore.get(key);
diff --git a/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/ViewModelsWithStateFactories.java b/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/ViewModelsWithStateFactories.java
index dc56975..cc4a339 100644
--- a/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/ViewModelsWithStateFactories.java
+++ b/lifecycle/viewmodel-fragment/src/main/java/androidx/lifecycle/ViewModelsWithStateFactories.java
@@ -136,7 +136,7 @@
static class FragmentVmFactory extends SavedStateVMFactory {
- FragmentVmFactory(Application app, BundlableSavedStateRegistry savedStateStore,
+ FragmentVmFactory(Application app, BundleSavedStateRegistry savedStateStore,
ViewModelWithStateFactory factory, Bundle initialArgs) {
super(savedStateStore, initialArgs, factory);
VMSavedStateInitializer.initializeIfNeeded(app);
diff --git a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateAccessorHolder.java b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateAccessorHolder.java
index 2c3e3d6..c4ff4d1 100644
--- a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateAccessorHolder.java
+++ b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateAccessorHolder.java
@@ -27,7 +27,7 @@
import java.util.Map;
import java.util.Set;
-class SavedStateAccessorHolder implements SavedStateRegistry.SavedStateProvider {
+class SavedStateAccessorHolder implements SavedStateRegistry.SavedStateProvider<Bundle> {
private static final String VALUES = "values";
private static final String KEYS = "keys";
diff --git a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
index 2f4591e..85e90fe 100644
--- a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
+++ b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
@@ -45,7 +45,7 @@
return mHolder.savedStateAccessor();
}
- SavedStateRegistry.SavedStateProvider savedStateComponent() {
+ SavedStateRegistry.SavedStateProvider<Bundle> savedStateComponent() {
return mHolder;
}
diff --git a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java
index 1dbb539..5fbba45 100644
--- a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java
+++ b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java
@@ -24,10 +24,10 @@
static final String TAG_SAVED_STATE_HANDLE = "androidx.lifecycle.savedstate.vm.tag";
private final ViewModelWithStateFactory mWrappedFactory;
- private final BundlableSavedStateRegistry mSavedStateStore;
+ private final SavedStateRegistry<Bundle> mSavedStateStore;
private final Bundle mInitialArgs;
- SavedStateVMFactory(BundlableSavedStateRegistry savedStateStore, Bundle initialArgs,
+ SavedStateVMFactory(SavedStateRegistry<Bundle> savedStateStore, Bundle initialArgs,
ViewModelWithStateFactory factory) {
mWrappedFactory = factory;
mSavedStateStore = savedStateStore;
diff --git a/loader/api/1.1.0-alpha01.txt b/loader/api/1.1.0-alpha01.txt
new file mode 100644
index 0000000..1bc56b87
--- /dev/null
+++ b/loader/api/1.1.0-alpha01.txt
@@ -0,0 +1,102 @@
+// Signature format: 2.0
+package androidx.loader.app {
+
+ public abstract class LoaderManager {
+ ctor public LoaderManager();
+ method @MainThread public abstract void destroyLoader(int);
+ method @Deprecated public abstract void dump(String!, java.io.FileDescriptor!, java.io.PrintWriter!, String[]!);
+ method public static void enableDebugLogging(boolean);
+ method public static <T extends androidx.lifecycle.LifecycleOwner & androidx.lifecycle.ViewModelStoreOwner> androidx.loader.app.LoaderManager getInstance(T);
+ method public abstract <D> androidx.loader.content.Loader<D>? getLoader(int);
+ method public boolean hasRunningLoaders();
+ method @MainThread public abstract <D> androidx.loader.content.Loader<D> initLoader(int, android.os.Bundle?, androidx.loader.app.LoaderManager.LoaderCallbacks<D>);
+ method public abstract void markForRedelivery();
+ method @MainThread public abstract <D> androidx.loader.content.Loader<D> restartLoader(int, android.os.Bundle?, androidx.loader.app.LoaderManager.LoaderCallbacks<D>);
+ }
+
+ public static interface LoaderManager.LoaderCallbacks<D> {
+ method @MainThread public androidx.loader.content.Loader<D> onCreateLoader(int, android.os.Bundle?);
+ method @MainThread public void onLoadFinished(androidx.loader.content.Loader<D>, D!);
+ method @MainThread public void onLoaderReset(androidx.loader.content.Loader<D>);
+ }
+
+}
+
+package androidx.loader.content {
+
+ public abstract class AsyncTaskLoader<D> extends androidx.loader.content.Loader<D> {
+ ctor public AsyncTaskLoader(android.content.Context);
+ method public void cancelLoadInBackground();
+ method protected java.util.concurrent.Executor getExecutor();
+ method public boolean isLoadInBackgroundCanceled();
+ method public abstract D? loadInBackground();
+ method public void onCanceled(D?);
+ method protected D? onLoadInBackground();
+ method public void setUpdateThrottle(long);
+ }
+
+ public class CursorLoader extends androidx.loader.content.AsyncTaskLoader<android.database.Cursor> {
+ ctor public CursorLoader(android.content.Context);
+ ctor public CursorLoader(android.content.Context, android.net.Uri, String[]?, String?, String[]?, String?);
+ method public void deliverResult(android.database.Cursor!);
+ method public String[]? getProjection();
+ method public String? getSelection();
+ method public String[]? getSelectionArgs();
+ method public String? getSortOrder();
+ method public android.net.Uri getUri();
+ method public android.database.Cursor! loadInBackground();
+ method public void onCanceled(android.database.Cursor!);
+ method public void setProjection(String[]?);
+ method public void setSelection(String?);
+ method public void setSelectionArgs(String[]?);
+ method public void setSortOrder(String?);
+ method public void setUri(android.net.Uri);
+ }
+
+ public class Loader<D> {
+ ctor public Loader(android.content.Context);
+ method @MainThread public void abandon();
+ method @MainThread public boolean cancelLoad();
+ method public void commitContentChanged();
+ method public String dataToString(D?);
+ method @MainThread public void deliverCancellation();
+ method @MainThread public void deliverResult(D?);
+ method @Deprecated public void dump(String!, java.io.FileDescriptor!, java.io.PrintWriter!, String[]!);
+ method @MainThread public void forceLoad();
+ method public android.content.Context getContext();
+ method public int getId();
+ method public boolean isAbandoned();
+ method public boolean isReset();
+ method public boolean isStarted();
+ method @MainThread protected void onAbandon();
+ method @MainThread protected boolean onCancelLoad();
+ method @MainThread public void onContentChanged();
+ method @MainThread protected void onForceLoad();
+ method @MainThread protected void onReset();
+ method @MainThread protected void onStartLoading();
+ method @MainThread protected void onStopLoading();
+ method @MainThread public void registerListener(int, androidx.loader.content.Loader.OnLoadCompleteListener<D>);
+ method @MainThread public void registerOnLoadCanceledListener(androidx.loader.content.Loader.OnLoadCanceledListener<D>);
+ method @MainThread public void reset();
+ method public void rollbackContentChanged();
+ method @MainThread public final void startLoading();
+ method @MainThread public void stopLoading();
+ method public boolean takeContentChanged();
+ method @MainThread public void unregisterListener(androidx.loader.content.Loader.OnLoadCompleteListener<D>);
+ method @MainThread public void unregisterOnLoadCanceledListener(androidx.loader.content.Loader.OnLoadCanceledListener<D>);
+ }
+
+ public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
+ ctor public Loader.ForceLoadContentObserver();
+ }
+
+ public static interface Loader.OnLoadCanceledListener<D> {
+ method public void onLoadCanceled(androidx.loader.content.Loader<D>);
+ }
+
+ public static interface Loader.OnLoadCompleteListener<D> {
+ method public void onLoadComplete(androidx.loader.content.Loader<D>, D?);
+ }
+
+}
+
diff --git a/loader/api/current.txt b/loader/api/current.txt
index ba1f38c..1bc56b87 100644
--- a/loader/api/current.txt
+++ b/loader/api/current.txt
@@ -27,6 +27,7 @@
public abstract class AsyncTaskLoader<D> extends androidx.loader.content.Loader<D> {
ctor public AsyncTaskLoader(android.content.Context);
method public void cancelLoadInBackground();
+ method protected java.util.concurrent.Executor getExecutor();
method public boolean isLoadInBackgroundCanceled();
method public abstract D? loadInBackground();
method public void onCanceled(D?);
diff --git a/loader/build.gradle b/loader/build.gradle
index 73d8803..c927d80 100644
--- a/loader/build.gradle
+++ b/loader/build.gradle
@@ -22,7 +22,7 @@
supportLibrary {
name = "Android Support Library loader"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.LOADER
mavenGroup = LibraryGroups.LOADER
inceptionYear = "2011"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/loader/src/androidTest/java/androidx/loader/content/AsyncTaskLoaderTest.java b/loader/src/androidTest/java/androidx/loader/content/AsyncTaskLoaderTest.java
new file mode 100644
index 0000000..4881c83
--- /dev/null
+++ b/loader/src/androidTest/java/androidx/loader/content/AsyncTaskLoaderTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 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 androidx.loader.content;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AsyncTaskLoaderTest {
+
+ @Test
+ public void testForceLoad_runsAsyncTask() throws InterruptedException {
+ TestAsyncTaskLoader loader = new TestAsyncTaskLoader(1);
+ loader.forceLoad();
+
+ assertTrue(loader.mLoadInBackgoundLatch.await(1, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testForceLoad_runsOnCustomExecutor() throws InterruptedException {
+ final CountDownLatch executorLatch = new CountDownLatch(1);
+ TestAsyncTaskLoader loader = new TestAsyncTaskLoader(1);
+ loader.mExecutor = new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ executorLatch.countDown();
+ command.run();
+ }
+ };
+ loader.forceLoad();
+
+ assertTrue(executorLatch.await(1, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testGetExecutor_notCalledOnConstruction() {
+ TestAsyncTaskLoader loader = new TestAsyncTaskLoader(1);
+
+ assertEquals(0, loader.mGetExecutorCallCount);
+ }
+
+ @Test
+ public void testGetExecutor_forceLoadMultipleTimes_getExecutorCalledOnce()
+ throws InterruptedException {
+ TestAsyncTaskLoader loader = new TestAsyncTaskLoader(3);
+ final AtomicInteger loadCount = new AtomicInteger(3);
+ loader.registerListener(0, new Loader.OnLoadCompleteListener<Void>() {
+ @Override
+ public void onLoadComplete(Loader<Void> loader, Void data) {
+ if (loadCount.getAndDecrement() > 0) {
+ loader.forceLoad();
+ }
+ }
+ });
+ loader.forceLoad();
+
+ assertTrue(loader.mLoadInBackgoundLatch.await(1, TimeUnit.SECONDS));
+ assertEquals(1, loader.mGetExecutorCallCount);
+ }
+
+ private static class TestAsyncTaskLoader extends AsyncTaskLoader<Void> {
+ final CountDownLatch mLoadInBackgoundLatch;
+ Executor mExecutor = null;
+ int mGetExecutorCallCount = 0;
+
+ TestAsyncTaskLoader(int latchCount) {
+ super(InstrumentationRegistry.getContext());
+ mLoadInBackgoundLatch = new CountDownLatch(latchCount);
+ }
+
+ @Override
+ public Void loadInBackground() {
+ mLoadInBackgoundLatch.countDown();
+ return null;
+ }
+
+ @Override
+ protected Executor getExecutor() {
+ mGetExecutorCallCount += 1;
+ if (mExecutor != null) {
+ return mExecutor;
+ }
+ return super.getExecutor();
+ }
+ }
+}
diff --git a/loader/src/main/java/androidx/loader/content/AsyncTaskLoader.java b/loader/src/main/java/androidx/loader/content/AsyncTaskLoader.java
index bbc14ea..fae3fba 100644
--- a/loader/src/main/java/androidx/loader/content/AsyncTaskLoader.java
+++ b/loader/src/main/java/androidx/loader/content/AsyncTaskLoader.java
@@ -117,7 +117,7 @@
}
}
- private final Executor mExecutor;
+ private Executor mExecutor;
volatile LoadTask mTask;
volatile LoadTask mCancellingTask;
@@ -127,12 +127,7 @@
Handler mHandler;
public AsyncTaskLoader(@NonNull Context context) {
- this(context, AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- private AsyncTaskLoader(@NonNull Context context, @NonNull Executor executor) {
super(context);
- mExecutor = executor;
}
/**
@@ -227,6 +222,9 @@
}
}
if (DEBUG) Log.v(TAG, "Executing: " + mTask);
+ if (mExecutor == null) {
+ mExecutor = getExecutor();
+ }
mTask.executeOnExecutor(mExecutor);
}
}
@@ -335,6 +333,22 @@
}
/**
+ * Returns the {@link Executor} to use for this {@link Loader}'s {@link AsyncTask}s.
+ * By default {@link AsyncTask#THREAD_POOL_EXECUTOR} will be used.
+ *
+ * Override this method to return a custom executor. Note that this method will only be called
+ * once before this {@link Loader}'s first {@link AsyncTask} is run. It is up to the
+ * {@link Loader} to shut down the {@link Executor} at the appropriate place
+ * (e.g. in {@link #onAbandon()}) if necessary.
+ *
+ * @return the {@link Executor} to use for this {@link Loader}'s {@link AsyncTask}s.
+ */
+ @NonNull
+ protected Executor getExecutor() {
+ return AsyncTask.THREAD_POOL_EXECUTOR;
+ }
+
+ /**
* Locks the current thread until the loader completes the current load
* operation. Returns immediately if there is no load operation running.
* Should not be called from the UI thread: calling it from the UI
diff --git a/localbroadcastmanager/build.gradle b/localbroadcastmanager/build.gradle
index 451fada..c97f62f 100644
--- a/localbroadcastmanager/build.gradle
+++ b/localbroadcastmanager/build.gradle
@@ -12,7 +12,7 @@
supportLibrary {
name = "Android Support Library Local Broadcast Manager"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.LOCALBROADCASTMANAGER
mavenGroup = LibraryGroups.LOCALBROADCASTMANAGER
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/media-widget/api/1.0.0-alpha5.txt b/media-widget/api/1.0.0-alpha5.txt
index bc36437..b803bef 100644
--- a/media-widget/api/1.0.0-alpha5.txt
+++ b/media-widget/api/1.0.0-alpha5.txt
@@ -5,35 +5,29 @@
ctor public MediaControlView2(android.content.Context);
ctor public MediaControlView2(android.content.Context, android.util.AttributeSet?);
ctor public MediaControlView2(android.content.Context, android.util.AttributeSet?, int);
- method public boolean checkLayoutParams(android.view.ViewGroup.LayoutParams!);
- method public android.view.ViewGroup.LayoutParams! generateDefaultLayoutParams();
- method public android.view.ViewGroup.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
method public void onMeasure(int, int);
method public void requestPlayButtonFocus();
- method public void setMediaSessionToken2(androidx.media2.SessionToken2!);
- method public void setOnFullScreenListener(androidx.media.widget.MediaControlView2.OnFullScreenListener!);
+ method public void setMediaSessionToken2(androidx.media2.SessionToken2);
+ method public void setOnFullScreenListener(androidx.media.widget.MediaControlView2.OnFullScreenListener);
}
public static interface MediaControlView2.OnFullScreenListener {
- method public void onFullScreen(android.view.View!, boolean);
+ method public void onFullScreen(android.view.View, boolean);
}
public class VideoView2 extends android.view.ViewGroup {
ctor public VideoView2(android.content.Context);
ctor public VideoView2(android.content.Context, android.util.AttributeSet?);
ctor public VideoView2(android.content.Context, android.util.AttributeSet?, int);
- method public boolean checkLayoutParams(android.view.ViewGroup.LayoutParams!);
- method public android.view.ViewGroup.LayoutParams! generateDefaultLayoutParams();
- method public android.view.ViewGroup.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
- method public androidx.media.widget.MediaControlView2! getMediaControlView2();
- method public androidx.media2.SessionToken2! getMediaSessionToken2();
+ method public androidx.media.widget.MediaControlView2? getMediaControlView2();
+ method public androidx.media2.SessionToken2 getMediaSessionToken2();
method public int getViewType();
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onMeasure(int, int);
method public void setAudioAttributes(androidx.media.AudioAttributesCompat);
- method public void setMediaControlView2(androidx.media.widget.MediaControlView2!, long);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void setMediaItem2(androidx.media2.MediaItem2);
+ method public void setMediaControlView2(androidx.media.widget.MediaControlView2, long);
+ method public void setMediaItem2(androidx.media2.MediaItem2);
method public void setViewType(int);
field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
diff --git a/media-widget/api/current.txt b/media-widget/api/current.txt
index bc36437..b803bef 100644
--- a/media-widget/api/current.txt
+++ b/media-widget/api/current.txt
@@ -5,35 +5,29 @@
ctor public MediaControlView2(android.content.Context);
ctor public MediaControlView2(android.content.Context, android.util.AttributeSet?);
ctor public MediaControlView2(android.content.Context, android.util.AttributeSet?, int);
- method public boolean checkLayoutParams(android.view.ViewGroup.LayoutParams!);
- method public android.view.ViewGroup.LayoutParams! generateDefaultLayoutParams();
- method public android.view.ViewGroup.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
method public void onMeasure(int, int);
method public void requestPlayButtonFocus();
- method public void setMediaSessionToken2(androidx.media2.SessionToken2!);
- method public void setOnFullScreenListener(androidx.media.widget.MediaControlView2.OnFullScreenListener!);
+ method public void setMediaSessionToken2(androidx.media2.SessionToken2);
+ method public void setOnFullScreenListener(androidx.media.widget.MediaControlView2.OnFullScreenListener);
}
public static interface MediaControlView2.OnFullScreenListener {
- method public void onFullScreen(android.view.View!, boolean);
+ method public void onFullScreen(android.view.View, boolean);
}
public class VideoView2 extends android.view.ViewGroup {
ctor public VideoView2(android.content.Context);
ctor public VideoView2(android.content.Context, android.util.AttributeSet?);
ctor public VideoView2(android.content.Context, android.util.AttributeSet?, int);
- method public boolean checkLayoutParams(android.view.ViewGroup.LayoutParams!);
- method public android.view.ViewGroup.LayoutParams! generateDefaultLayoutParams();
- method public android.view.ViewGroup.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
- method public androidx.media.widget.MediaControlView2! getMediaControlView2();
- method public androidx.media2.SessionToken2! getMediaSessionToken2();
+ method public androidx.media.widget.MediaControlView2? getMediaControlView2();
+ method public androidx.media2.SessionToken2 getMediaSessionToken2();
method public int getViewType();
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onMeasure(int, int);
method public void setAudioAttributes(androidx.media.AudioAttributesCompat);
- method public void setMediaControlView2(androidx.media.widget.MediaControlView2!, long);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void setMediaItem2(androidx.media2.MediaItem2);
+ method public void setMediaControlView2(androidx.media.widget.MediaControlView2, long);
+ method public void setMediaItem2(androidx.media2.MediaItem2);
method public void setViewType(int);
field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
diff --git a/media-widget/src/androidTest/java/androidx/media/widget/MediaControlView2Test.java b/media-widget/src/androidTest/java/androidx/media/widget/MediaControlView2Test.java
index 9c5b3ac..2e29e89 100644
--- a/media-widget/src/androidTest/java/androidx/media/widget/MediaControlView2Test.java
+++ b/media-widget/src/androidTest/java/androidx/media/widget/MediaControlView2Test.java
@@ -216,33 +216,35 @@
return;
}
- final CountDownLatch latchForPausedState = new CountDownLatch(1);
- final CountDownLatch latchForRew = new CountDownLatch(2);
+ final CountDownLatch latchForFfwd = new CountDownLatch(1);
+ final CountDownLatch latchForRew = new CountDownLatch(1);
final MediaController2 controller =
createController(new MediaController2.ControllerCallback() {
+ long mExpectedPosition;
+ final long mDelta = 1000L;
@Override
public void onPlayerStateChanged(@NonNull MediaController2 controller,
int state) {
if (state == SessionPlayer2.PLAYER_STATE_PAUSED) {
- controller.seekTo(FFWD_MS);
- latchForPausedState.countDown();
+ mExpectedPosition = FFWD_MS;
+ controller.seekTo(mExpectedPosition);
}
}
@Override
public void onSeekCompleted(@NonNull MediaController2 controller,
long position) {
- switch ((int) latchForRew.getCount()) {
- case 2:
- if (position == FFWD_MS) {
- latchForRew.countDown();
- }
- break;
- case 1:
- if (position == FFWD_MS - REW_MS) {
- latchForRew.countDown();
- }
+ assertTrue(equalsSeekPosition(mExpectedPosition, position, mDelta));
+ if (mExpectedPosition == FFWD_MS) {
+ mExpectedPosition = position - REW_MS;
+ latchForFfwd.countDown();
+ } else {
+ latchForRew.countDown();
}
}
+
+ private boolean equalsSeekPosition(long expected, long actual, long delta) {
+ return (actual < expected + delta) && (actual > expected - delta);
+ }
});
mActivityRule.runOnUiThread(new Runnable() {
@Override
@@ -250,7 +252,7 @@
mVideoView.setMediaItem2(mFileSchemeMediaItem);
}
});
- assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
onView(withId(R.id.rew)).perform(click());
assertTrue(latchForRew.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@@ -298,24 +300,27 @@
final long duration = 49056L;
final String title = "BigBuckBunny";
- final CountDownLatch latch = new CountDownLatch(1);
+ final CountDownLatch latch = new CountDownLatch(2);
final MediaController2 controller =
createController(new MediaController2.ControllerCallback() {
@Override
- public void onPlaylistChanged(@NonNull MediaController2 controller,
- @NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
- MediaMetadata2 itemMetadata = list.get(0).getMetadata();
- if (itemMetadata != null) {
- if (itemMetadata.containsKey(MediaMetadata2.METADATA_KEY_TITLE)) {
- assertEquals(title, itemMetadata.getString(
- MediaMetadata2.METADATA_KEY_TITLE));
- }
- if (itemMetadata.containsKey(MediaMetadata2.METADATA_KEY_DURATION)) {
- assertEquals(duration, itemMetadata.getLong(
- MediaMetadata2.METADATA_KEY_DURATION));
+ public void onCurrentMediaItemChanged(@NonNull MediaController2 controller,
+ @Nullable MediaItem2 item) {
+ if (item != null) {
+ MediaMetadata2 metadata = item.getMetadata();
+ if (metadata != null) {
+ if (metadata.containsKey(MediaMetadata2.METADATA_KEY_TITLE)) {
+ assertEquals(title, metadata.getString(
+ MediaMetadata2.METADATA_KEY_TITLE));
+ latch.countDown();
+ }
+ if (metadata.containsKey(MediaMetadata2.METADATA_KEY_DURATION)) {
+ assertEquals(duration, metadata.getLong(
+ MediaMetadata2.METADATA_KEY_DURATION));
+ latch.countDown();
+ }
}
}
- latch.countDown();
}
});
mActivityRule.runOnUiThread(new Runnable() {
@@ -345,28 +350,35 @@
final MediaItem2 uriMediaItem = createTestMediaItem2(uri);
final MediaItem2 fileMediaItem = new FileMediaItem2.Builder(afd.getFileDescriptor(),
afd.getStartOffset(), afd.getLength()).build();
- final CountDownLatch latchForUri = new CountDownLatch(1);
- final CountDownLatch latchForFile = new CountDownLatch(1);
+ final CountDownLatch latchForUri = new CountDownLatch(3);
+ final CountDownLatch latchForFile = new CountDownLatch(3);
final MediaController2 controller =
createController(new MediaController2.ControllerCallback() {
@Override
- public void onPlaylistChanged(@NonNull MediaController2 controller,
- @NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
- MediaMetadata2 itemMetadata = list.get(0).getMetadata();
- if (itemMetadata != null) {
- if (itemMetadata.containsKey(MediaMetadata2.METADATA_KEY_TITLE)) {
- assertEquals(title, itemMetadata.getString(
- MediaMetadata2.METADATA_KEY_TITLE));
- }
- if (itemMetadata.containsKey(MediaMetadata2.METADATA_KEY_ARTIST)) {
- assertEquals(artist, itemMetadata.getString(
- MediaMetadata2.METADATA_KEY_ARTIST));
- }
- if (itemMetadata.containsKey(MediaMetadata2.METADATA_KEY_DURATION)) {
- assertEquals(duration, itemMetadata.getLong(
- MediaMetadata2.METADATA_KEY_DURATION));
+ public void onCurrentMediaItemChanged(@NonNull MediaController2 controller,
+ @Nullable MediaItem2 item) {
+ if (item != null) {
+ MediaMetadata2 metadata = item.getMetadata();
+ if (metadata != null) {
+ if (metadata.containsKey(MediaMetadata2.METADATA_KEY_TITLE)) {
+ assertEquals(title, metadata.getString(
+ MediaMetadata2.METADATA_KEY_TITLE));
+ countDown();
+ }
+ if (metadata.containsKey(MediaMetadata2.METADATA_KEY_ARTIST)) {
+ assertEquals(artist, metadata.getString(
+ MediaMetadata2.METADATA_KEY_ARTIST));
+ countDown();
+ }
+ if (metadata.containsKey(MediaMetadata2.METADATA_KEY_DURATION)) {
+ assertEquals(duration, metadata.getLong(
+ MediaMetadata2.METADATA_KEY_DURATION));
+ countDown();
+ }
}
}
+ }
+ private void countDown() {
if (latchForUri.getCount() != 0) {
latchForUri.countDown();
} else {
diff --git a/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java b/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
index 4939ecd..a4d14bb 100644
--- a/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
+++ b/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
@@ -220,9 +220,7 @@
any(MediaController2.class), eq(SessionPlayer2.PLAYER_STATE_PLAYING));
}
- // TODO: Shortly prevented to be run, since it crashed.
- // Revive @Test annotation after investigating b/118412745.
- // @Test
+ @Test
public void testPlayVideoOnTextureView() throws Throwable {
// Don't run the test if the codec isn't supported.
if (!hasCodec()) {
@@ -248,12 +246,46 @@
.onViewTypeChanged(mVideoView, VideoView2.VIEW_TYPE_TEXTUREVIEW);
verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onConnected(
any(MediaController2.class), any(SessionCommandGroup2.class));
+ verify(mControllerCallback, timeout(TIME_OUT).atLeast(1)).onPlayerStateChanged(
+ any(MediaController2.class), eq(SessionPlayer2.PLAYER_STATE_PAUSED));
mController.play();
verify(mControllerCallback, timeout(TIME_OUT).atLeast(1)).onPlayerStateChanged(
any(MediaController2.class), eq(SessionPlayer2.PLAYER_STATE_PLAYING));
+ }
+
+ @Test
+ public void testSetViewType() throws Throwable {
+ // Don't run the test if the codec isn't supported.
+ if (!hasCodec()) {
+ Log.i(TAG, "SKIPPING testPlayVideoOnTextureView(): codec is not supported");
+ return;
+ }
+
+ final VideoView2.OnViewTypeChangedListener mockViewTypeListener =
+ mock(VideoView2.OnViewTypeChangedListener.class);
+
+ // The default view type is surface view.
+ assertEquals(mVideoView.getViewType(), mVideoView.VIEW_TYPE_SURFACEVIEW);
+
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mVideoView.setOnViewTypeChangedListener(mockViewTypeListener);
+ mVideoView.setViewType(mVideoView.VIEW_TYPE_TEXTUREVIEW);
+ mVideoView.setViewType(mVideoView.VIEW_TYPE_SURFACEVIEW);
+ mVideoView.setViewType(mVideoView.VIEW_TYPE_TEXTUREVIEW);
+ mVideoView.setViewType(mVideoView.VIEW_TYPE_SURFACEVIEW);
+ mVideoView.setMediaItem2(mMediaItem);
+ }
+ });
+
+ verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onConnected(
+ any(MediaController2.class), any(SessionCommandGroup2.class));
verify(mControllerCallback, timeout(TIME_OUT).atLeast(1)).onPlayerStateChanged(
any(MediaController2.class), eq(SessionPlayer2.PLAYER_STATE_PAUSED));
+
+ assertEquals(mVideoView.getViewType(), mVideoView.VIEW_TYPE_SURFACEVIEW);
}
@Test
diff --git a/media-widget/src/main/java/androidx/media/widget/BaseLayout.java b/media-widget/src/main/java/androidx/media/widget/BaseLayout.java
index 982513a..231f259 100644
--- a/media-widget/src/main/java/androidx/media/widget/BaseLayout.java
+++ b/media-widget/src/main/java/androidx/media/widget/BaseLayout.java
@@ -54,12 +54,12 @@
}
@Override
- public boolean checkLayoutParams(LayoutParams p) {
+ protected boolean checkLayoutParams(LayoutParams p) {
return p instanceof MarginLayoutParams;
}
@Override
- public LayoutParams generateDefaultLayoutParams() {
+ protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@@ -69,7 +69,7 @@
}
@Override
- public LayoutParams generateLayoutParams(LayoutParams lp) {
+ protected LayoutParams generateLayoutParams(LayoutParams lp) {
if (lp instanceof MarginLayoutParams) {
return lp;
}
diff --git a/media-widget/src/main/java/androidx/media/widget/MediaControlView2.java b/media-widget/src/main/java/androidx/media/widget/MediaControlView2.java
index f473e96..f8db518 100644
--- a/media-widget/src/main/java/androidx/media/widget/MediaControlView2.java
+++ b/media-widget/src/main/java/androidx/media/widget/MediaControlView2.java
@@ -67,13 +67,13 @@
import androidx.media2.MediaController2;
import androidx.media2.MediaItem2;
import androidx.media2.MediaMetadata2;
+import androidx.media2.MediaPlayer;
import androidx.media2.MediaSession2;
import androidx.media2.SessionCommand2;
import androidx.media2.SessionCommandGroup2;
import androidx.media2.SessionPlayer2;
import androidx.media2.SessionToken2;
import androidx.media2.UriMediaItem2;
-import androidx.media2.XMediaPlayer;
import androidx.mediarouter.app.MediaRouteButton;
import androidx.mediarouter.media.MediaRouteSelector;
@@ -85,7 +85,7 @@
import java.util.concurrent.Executor;
/**
- * A View that contains the controls for {@link XMediaPlayer}.
+ * A View that contains the controls for {@link MediaPlayer}.
* It provides a wide range of buttons that serve the following functions: play/pause,
* rewind/fast-forward, skip to next/previous, select subtitle track, enter/exit full screen mode,
* adjust video quality, select audio track, and adjust playback speed.
@@ -234,6 +234,8 @@
ImageButton mPlayPauseButton;
ImageButton mFfwdButton;
ImageButton mRewButton;
+ // TODO: Disable Next/Previous buttons when the current item does not have a next/previous
+ // item in the playlist. (b/119159436)
private ImageButton mNextButton;
private ImageButton mPrevButton;
@@ -319,11 +321,10 @@
* Sets MediaSession2 token to control corresponding MediaSession2. It makes it possible to
* send and receive data between MediaControlView2 and VideoView2.
*/
- public void setMediaSessionToken2(SessionToken2 token) {
+ public void setMediaSessionToken2(@NonNull SessionToken2 token) {
mController.setMediaSessionToken2(token);
if (mController.hasMetadata()) {
- updateDuration();
- updateTitle();
+ updateMetadata();
}
}
@@ -332,7 +333,7 @@
* This needs to be implemented in order to display the fullscreen button.
* @param l The callback that will be run
*/
- public void setOnFullScreenListener(OnFullScreenListener l) {
+ public void setOnFullScreenListener(@NonNull OnFullScreenListener l) {
mOnFullScreenListener = l;
mFullScreenButton.setVisibility(View.VISIBLE);
}
@@ -354,7 +355,7 @@
/**
* Called to indicate a fullscreen mode change.
*/
- void onFullScreen(View view, boolean fullScreen);
+ void onFullScreen(@NonNull View view, boolean fullScreen);
}
@Override
@@ -1390,36 +1391,34 @@
}
};
- void updateDuration() {
- if (mController.hasMetadata()) {
- mTimeView.setVisibility(View.VISIBLE);
- mDuration = mController.getDurationMs();
- setProgress();
- }
- }
-
- void updateTitle() {
- if (mController.hasMetadata()) {
- mTitleView.setText(mController.getTitle());
- }
- }
-
- void updateAudioMetadata() {
- if (mMediaType != MEDIA_TYPE_MUSIC) {
+ void updateMetadata() {
+ if (!mController.hasMetadata()) {
return;
}
- if (mController.hasMetadata()) {
- String titleText = mController.getTitle();
- String artistText = mController.getArtistText();
- if (titleText == null) {
- titleText = mResources.getString(R.string.mcv2_music_title_unknown_text);
- }
- if (artistText == null) {
- artistText = mResources.getString(R.string.mcv2_music_artist_unknown_text);
- }
+ long duration = mController.getDurationMs();
+ if (duration != 0) {
+ mDuration = duration;
+ mTimeView.setVisibility(View.VISIBLE);
+ setProgress();
+ }
+
+ if (mMediaType != MEDIA_TYPE_MUSIC) {
+ String title = mController.getTitle();
+ if (title != null) {
+ mTitleView.setText(title);
+ }
+ } else {
+ String title = mController.getTitle();
+ if (title == null) {
+ title = mResources.getString(R.string.mcv2_music_title_unknown_text);
+ }
+ String artist = mController.getArtistText();
+ if (artist == null) {
+ artist = mResources.getString(R.string.mcv2_music_artist_unknown_text);
+ }
// Update title for Embedded size type
- mTitleView.setText(titleText + " - " + artistText);
+ mTitleView.setText(title + " - " + artist);
// Remove unnecessary buttons
mVideoQualityButton.setVisibility(View.GONE);
@@ -1660,12 +1659,10 @@
mNextButton = v.findViewById(R.id.next);
if (mNextButton != null) {
mNextButton.setOnClickListener(mNextListener);
- mNextButton.setVisibility(View.GONE);
}
mPrevButton = v.findViewById(R.id.prev);
if (mPrevButton != null) {
mPrevButton.setOnClickListener(mPrevListener);
- mPrevButton.setVisibility(View.GONE);
}
return v;
}
@@ -1831,6 +1828,28 @@
mFfwdButton.setVisibility(View.GONE);
}
}
+ if (commands.hasCommand(
+ SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM)) {
+ if (mPrevButton != null) {
+ mPrevButton.setVisibility(VISIBLE);
+ mPrevButton.setEnabled(true);
+ }
+ } else {
+ if (mPrevButton != null) {
+ mPrevButton.setVisibility(View.GONE);
+ }
+ }
+ if (commands.hasCommand(
+ SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM)) {
+ if (mNextButton != null) {
+ mNextButton.setVisibility(VISIBLE);
+ mNextButton.setEnabled(true);
+ }
+ } else {
+ if (mNextButton != null) {
+ mNextButton.setVisibility(View.GONE);
+ }
+ }
if (commands.hasCommand(SessionCommand2.COMMAND_CODE_PLAYER_SEEK_TO)) {
mSeekAvailable = true;
mProgress.setEnabled(true);
@@ -2344,6 +2363,8 @@
if (DEBUG) {
Log.d(TAG, "onCurrentMediaItemChanged(): " + mediaItem);
}
+ mMediaMetadata2 = mediaItem.getMetadata();
+ updateMetadata();
}
@Override
@@ -2360,7 +2381,16 @@
@Override
public void onConnected(@NonNull MediaController2 controller,
@NonNull SessionCommandGroup2 allowedCommands) {
+ if (DEBUG) {
+ Log.d(TAG, "onConnected(): " + allowedCommands);
+ }
updateAllowedCommands(allowedCommands);
+
+ MediaItem2 mediaItem = controller.getCurrentMediaItem();
+ if (mediaItem != null) {
+ mMediaMetadata2 = mediaItem.getMetadata();
+ updateMetadata();
+ }
}
@Override
@@ -2376,12 +2406,6 @@
if (DEBUG) {
Log.d(TAG, "onPlaylistChanged(): list: " + list);
}
- // Note that currently MediaControlView2 assumes single media item to play.
- MediaItem2 mediaItem = list.isEmpty() ? null : list.get(0);
- mMediaMetadata2 = (mediaItem != null) ? mediaItem.getMetadata() : null;
- updateDuration();
- updateTitle();
- updateAudioMetadata();
}
@Override
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoSurfaceView.java b/media-widget/src/main/java/androidx/media/widget/VideoSurfaceView.java
index 52b2c63..559d6a8 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoSurfaceView.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoSurfaceView.java
@@ -28,7 +28,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import androidx.media2.XMediaPlayer;
+import androidx.core.content.ContextCompat;
+import androidx.media2.MediaPlayer;
@RequiresApi(21)
class VideoSurfaceView extends SurfaceView
@@ -36,11 +37,11 @@
private static final String TAG = "VideoSurfaceView";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private Surface mSurface = null;
- private SurfaceListener mSurfaceListener = null;
- private XMediaPlayer mMediaPlayer;
+ SurfaceListener mSurfaceListener = null;
+ private MediaPlayer mMediaPlayer;
// A flag to indicate taking over other view should be proceed.
private boolean mIsTakingOverOldView;
- private VideoViewInterface mOldView;
+ VideoViewInterface mOldView;
VideoSurfaceView(Context context) {
super(context, null);
@@ -48,16 +49,29 @@
}
////////////////////////////////////////////////////
- // implements VideoViewInterfaceWithMp1
+ // implements VideoViewInterface
////////////////////////////////////////////////////
@Override
- public boolean assignSurfaceToMediaPlayer(XMediaPlayer mp) {
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurface: " + mSurface);
if (mp == null || !hasAvailableSurface()) {
return false;
}
- mp.setSurface(mSurface);
+ mp.setSurface(mSurface).addListener(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mOldView != null) {
+ ((View) mOldView).setVisibility(GONE);
+ mOldView = null;
+ }
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceTakeOverDone(VideoSurfaceView.this);
+ }
+ }
+ }, ContextCompat.getMainExecutor(getContext())
+ );
return true;
}
@@ -72,7 +86,7 @@
}
@Override
- public void setMediaPlayer(XMediaPlayer mp) {
+ public void setMediaPlayer(MediaPlayer mp) {
mMediaPlayer = mp;
if (mIsTakingOverOldView) {
takeOver(mOldView);
@@ -82,12 +96,7 @@
@Override
public void takeOver(@NonNull VideoViewInterface oldView) {
if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
- ((View) oldView).setVisibility(GONE);
mIsTakingOverOldView = false;
- mOldView = null;
- if (mSurfaceListener != null) {
- mSurfaceListener.onSurfaceTakeOverDone(this);
- }
} else {
mIsTakingOverOldView = true;
mOldView = oldView;
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoTextureView.java b/media-widget/src/main/java/androidx/media/widget/VideoTextureView.java
index 44fdb1d3..f45081c 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoTextureView.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoTextureView.java
@@ -27,20 +27,21 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import androidx.media2.XMediaPlayer;
+import androidx.core.content.ContextCompat;
+import androidx.media2.MediaPlayer;
@RequiresApi(21)
class VideoTextureView extends TextureView
implements VideoViewInterface, TextureView.SurfaceTextureListener {
- private static final String TAG = "VideoTextureViewWithMp1";
+ private static final String TAG = "VideoTextureView";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private Surface mSurface;
- private SurfaceListener mSurfaceListener;
- private XMediaPlayer mMediaPlayer;
+ SurfaceListener mSurfaceListener;
+ private MediaPlayer mMediaPlayer;
// A flag to indicate taking over other view should be proceed.
private boolean mIsTakingOverOldView;
- private VideoViewInterface mOldView;
+ VideoViewInterface mOldView;
VideoTextureView(Context context) {
super(context, null);
@@ -48,16 +49,29 @@
}
////////////////////////////////////////////////////
- // implements VideoViewInterfaceWithMp1
+ // implements VideoViewInterface
////////////////////////////////////////////////////
@Override
- public boolean assignSurfaceToMediaPlayer(XMediaPlayer mp) {
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
if (mp == null || !hasAvailableSurface()) {
// Surface is not ready.
return false;
}
- mp.setSurface(mSurface);
+ mp.setSurface(mSurface).addListener(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mOldView != null) {
+ ((View) mOldView).setVisibility(GONE);
+ mOldView = null;
+ }
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceTakeOverDone(VideoTextureView.this);
+ }
+ }
+ }, ContextCompat.getMainExecutor(getContext())
+ );
return true;
}
@@ -72,7 +86,7 @@
}
@Override
- public void setMediaPlayer(XMediaPlayer mp) {
+ public void setMediaPlayer(MediaPlayer mp) {
mMediaPlayer = mp;
if (mIsTakingOverOldView) {
takeOver(mOldView);
@@ -82,12 +96,7 @@
@Override
public void takeOver(@NonNull VideoViewInterface oldView) {
if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
- ((View) oldView).setVisibility(GONE);
mIsTakingOverOldView = false;
- mOldView = null;
- if (mSurfaceListener != null) {
- mSurfaceListener.onSurfaceTakeOverDone(this);
- }
} else {
mIsTakingOverOldView = true;
mOldView = oldView;
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoView2.java b/media-widget/src/main/java/androidx/media/widget/VideoView2.java
index 0c74793..732e7ef 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoView2.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoView2.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
-import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
@@ -37,12 +36,10 @@
import androidx.annotation.VisibleForTesting;
import androidx.media.AudioAttributesCompat;
import androidx.media2.MediaItem2;
-import androidx.media2.MediaMetadata2;
import androidx.media2.SessionToken2;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
/**
* Displays a video file. VideoView2 class is a ViewGroup class which is wrapping
@@ -150,7 +147,7 @@
* @param mediaControlView a media control view2 instance.
* @param intervalMs a time interval in milliseconds until VideoView2 hides MediaControlView2.
*/
- public void setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs) {
+ public void setMediaControlView2(@NonNull MediaControlView2 mediaControlView, long intervalMs) {
mImpl.setMediaControlView2(mediaControlView, intervalMs);
}
@@ -158,39 +155,19 @@
* Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by
* {@link #setMediaControlView2} method.
*/
+ @Nullable
public MediaControlView2 getMediaControlView2() {
return mImpl.getMediaControlView2();
}
/**
- * Sets MediaMetadata2 instance. It will replace the previously assigned MediaMetadata2 instance
- * if any.
- *
- * @param metadata a MediaMetadata2 instance.
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- public void setMediaMetadata(MediaMetadata2 metadata) {
- mImpl.setMediaMetadata(metadata);
- }
-
- /**
- * Returns MediaMetadata2 instance which is retrieved from MediaPlayer inside VideoView2 by
- * default or by {@link #setMediaMetadata} method.
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- public MediaMetadata2 getMediaMetadata() {
- return mImpl.getMediaMetadata();
- }
-
- /**
* Returns {@link SessionToken2} so that developers create their own
* {@link androidx.media2.MediaController2} instance. This method should be called when
* VideoView2 is attached to window, or it throws IllegalStateException.
*
* @throws IllegalStateException if internal MediaSession is not created yet.
*/
+ @NonNull
public SessionToken2 getMediaSessionToken2() {
return mImpl.getMediaSessionToken2();
}
@@ -205,50 +182,9 @@
}
/**
- * Sets video path.
- *
- * @param path the path of the video.
- *
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- public void setVideoPath(String path) {
- mImpl.setVideoUri(Uri.parse(path));
- }
-
- /**
- * Sets video URI.
- *
- * @param uri the URI of the video.
- *
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- public void setVideoUri(Uri uri) {
- mImpl.setVideoUri(uri, null);
- }
-
- /**
- * Sets video URI using specific headers.
- *
- * @param uri the URI of the video.
- * @param headers the headers for the URI request.
- * Note that the cross domain redirection is allowed by default, but that can be
- * changed with key/value pairs through the headers parameter with
- * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
- * to disallow or allow cross domain redirection.
- *
- * @hide
- */
- public void setVideoUri(Uri uri, @Nullable Map<String, String> headers) {
- mImpl.setVideoUri(uri, headers);
- }
-
- /**
* Sets {@link MediaItem2} object to render using VideoView2.
* @param mediaItem the MediaItem2 to play
*/
- @RestrictTo(LIBRARY_GROUP)
public void setMediaItem2(@NonNull MediaItem2 mediaItem) {
mImpl.setMediaItem2(mediaItem);
}
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoView2Impl.java b/media-widget/src/main/java/androidx/media/widget/VideoView2Impl.java
index aa91162..10db526 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoView2Impl.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoView2Impl.java
@@ -17,21 +17,15 @@
package androidx.media.widget;
import android.content.Context;
-import android.net.Uri;
-import android.support.v4.media.session.MediaControllerCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat;
import androidx.media2.MediaItem2;
-import androidx.media2.MediaMetadata2;
import androidx.media2.SessionToken2;
-import java.util.Map;
-
/**
* Interface for impl classes.
*/
@@ -47,7 +41,7 @@
* @param mediaControlView a media control view2 instance.
* @param intervalMs a time interval in milliseconds until VideoView2 hides MediaControlView2.
*/
- void setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs);
+ void setMediaControlView2(@NonNull MediaControlView2 mediaControlView, long intervalMs);
/**
* Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by
@@ -56,37 +50,13 @@
MediaControlView2 getMediaControlView2();
/**
- * Sets MediaMetadata2 instance. It will replace the previously assigned MediaMetadata2 instance
- * if any.
- *
- * @param metadata a MediaMetadata2 instance.
- */
- void setMediaMetadata(MediaMetadata2 metadata);
-
- /**
- * Returns MediaMetadata2 instance which is retrieved from MediaPlayer inside VideoView2 by
- * default or by {@link #setMediaMetadata} method.
- */
- MediaMetadata2 getMediaMetadata();
-
- /**
- * Returns MediaController instance which is connected with MediaSession that VideoView2 is
- * using. This method should be called when VideoView2 is attached to window, or it throws
- * IllegalStateException, since internal MediaSession instance is not available until
- * this view is attached to window. Please check {@link View#isAttachedToWindow}
- * before calling this method.
- *
- * @throws IllegalStateException if internal MediaSession is not created yet.
- */
- MediaControllerCompat getMediaController();
-
- /**
* Returns {@link SessionToken2} so that developers create their own
* {@link androidx.media2.MediaController2} instance. This method should be called when
* VideoView2 is attached to window, or it throws IllegalStateException.
*
* @throws IllegalStateException if internal MediaSession is not created yet.
*/
+ @NonNull
SessionToken2 getMediaSessionToken2();
/**
@@ -97,32 +67,6 @@
void setAudioAttributes(@NonNull AudioAttributesCompat attributes);
/**
- * Sets video path.
- *
- * @param path the path of the video.
- */
- void setVideoPath(String path);
-
- /**
- * Sets video URI.
- *
- * @param uri the URI of the video.
- */
- void setVideoUri(Uri uri);
-
- /**
- * Sets video URI using specific headers.
- *
- * @param uri the URI of the video.
- * @param headers the headers for the URI request.
- * Note that the cross domain redirection is allowed by default, but that can be
- * changed with key/value pairs through the headers parameter with
- * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
- * to disallow or allow cross domain redirection.
- */
- void setVideoUri(Uri uri, @Nullable Map<String, String> headers);
-
- /**
* Sets {@link MediaItem2} object to render using VideoView2.
*
* @param mediaItem the MediaItem2 to play
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java b/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
index c211e3c..a0dedb3 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
@@ -30,7 +30,6 @@
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.media.session.MediaControllerCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -51,6 +50,7 @@
import androidx.media2.FileMediaItem2;
import androidx.media2.MediaItem2;
import androidx.media2.MediaMetadata2;
+import androidx.media2.MediaPlayer;
import androidx.media2.MediaPlayer2;
import androidx.media2.MediaSession2;
import androidx.media2.RemoteSessionPlayer2;
@@ -60,7 +60,6 @@
import androidx.media2.SessionToken2;
import androidx.media2.SubtitleData2;
import androidx.media2.UriMediaItem2;
-import androidx.media2.XMediaPlayer;
import androidx.media2.subtitle.Cea708CaptionRenderer;
import androidx.media2.subtitle.ClosedCaptionRenderer;
import androidx.media2.subtitle.SubtitleController;
@@ -72,7 +71,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.Executor;
/**
@@ -102,6 +100,7 @@
private VideoView2.OnViewTypeChangedListener mViewTypeChangedListener;
VideoViewInterface mCurrentView;
+ VideoViewInterface mTargetView;
private VideoTextureView mTextureView;
private VideoSurfaceView mSurfaceView;
@@ -121,7 +120,6 @@
private String mMusicArtistText;
boolean mIsMusicMediaType;
private int mPrevWidth;
- private int mPrevHeight;
int mDominantColor;
private int mSizeType;
@@ -142,7 +140,6 @@
int mSelectedSubtitleTrackIndex;
private SubtitleAnchorView mSubtitleAnchorView;
- boolean mSubtitleEnabled;
VideoView2 mInstance;
@@ -268,6 +265,7 @@
mSurfaceView.setVisibility(View.GONE);
mCurrentView = mTextureView;
}
+ mTargetView = mCurrentView;
MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
@@ -284,7 +282,7 @@
* @param intervalMs a time interval in milliseconds until VideoView2 hides MediaControlView2.
*/
@Override
- public void setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs) {
+ public void setMediaControlView2(@NonNull MediaControlView2 mediaControlView, long intervalMs) {
mMediaControlView = mediaControlView;
mMediaControlView.setShowControllerInterval(intervalMs);
@@ -303,42 +301,6 @@
}
/**
- * Sets MediaMetadata2 instance. It will replace the previously assigned MediaMetadata2 instance
- * if any.
- *
- * @param metadata a MediaMetadata2 instance.
- */
- @Override
- public void setMediaMetadata(MediaMetadata2 metadata) {
- if (mMediaItem != null) {
- mMediaItem.setMetadata(metadata);
- }
- }
-
- /**
- * Returns MediaMetadata2 instance which is retrieved from MediaPlayer inside VideoView2 by
- * default or by {@link #setMediaMetadata} method.
- */
- @Override
- public MediaMetadata2 getMediaMetadata() {
- return (mMediaItem != null) ? mMediaItem.getMetadata() : null;
- }
-
- /**
- * Returns MediaController instance which is connected with MediaSession that VideoView2 is
- * using. This method should be called when VideoView2 is attached to window, or it throws
- * IllegalStateException, since internal MediaSession instance is not available until
- * this view is attached to window. Please check {@link View#isAttachedToWindow}
- * before calling this method.
- *
- * @throws IllegalStateException if internal MediaSession is not created yet.
- */
- @Override
- public MediaControllerCompat getMediaController() {
- return null;
- }
-
- /**
* Returns {@link SessionToken2} so that developers create their own
* {@link androidx.media2.MediaController2} instance. This method should be called when
* VideoView2 is attached to window or after {@link #setMediaItem2} is called.
@@ -346,6 +308,7 @@
* @throws IllegalStateException if internal MediaSession is not created yet.
*/
@Override
+ @NonNull
public SessionToken2 getMediaSessionToken2() {
if (mMediaSession == null) {
throw new IllegalStateException("MediaSession2 instance is not available.");
@@ -367,43 +330,6 @@
}
/**
- * Sets video path.
- *
- * @param path the path of the video.
- */
- @Override
- public void setVideoPath(String path) {
- setVideoUri(Uri.parse(path));
- }
-
- /**
- * Sets video URI.
- *
- * @param uri the URI of the video.
- */
- @Override
- public void setVideoUri(Uri uri) {
- setVideoUri(uri, null);
- }
-
- /**
- * Sets video URI using specific headers.
- *
- * @param uri the URI of the video.
- * @param headers the headers for the URI request.
- * Note that the cross domain redirection is allowed by default, but that can be
- * changed with key/value pairs through the headers parameter with
- * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
- * to disallow or allow cross domain redirection.
- */
- @Override
- public void setVideoUri(Uri uri, @Nullable Map<String, String> headers) {
- UriMediaItem2.Builder builder = new UriMediaItem2.Builder(
- mInstance.getContext(), uri, headers, null);
- setMediaItem2(builder.build());
- }
-
- /**
* Sets {@link MediaItem2} object to render using VideoView2.
* @param mediaItem the MediaItem2 to play
*/
@@ -425,7 +351,8 @@
*/
@Override
public void setViewType(@VideoView2.ViewType int viewType) {
- if (viewType == mCurrentView.getViewType()) {
+ if (viewType == mTargetView.getViewType()) {
+ Log.d(TAG, "setViewType with the same type (" + viewType + ") is ignored.");
return;
}
VideoViewInterface targetView;
@@ -438,6 +365,8 @@
} else {
throw new IllegalArgumentException("Unknown view type: " + viewType);
}
+
+ mTargetView = targetView;
((View) targetView).setVisibility(View.VISIBLE);
targetView.takeOver(mCurrentView);
mInstance.requestLayout();
@@ -559,7 +488,6 @@
}
}
mPrevWidth = currWidth;
- mPrevHeight = currHeight;
}
}
}
@@ -598,15 +526,23 @@
@Override
public void onSurfaceTakeOverDone(VideoViewInterface view) {
+ if (view != mTargetView) {
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceTakeOverDone(). view is not targetView. ignore.: " + view);
+ }
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
}
if (mCurrentState != STATE_PLAYING) {
mMediaSession.getPlayer().seekTo(mMediaSession.getPlayer().getCurrentPosition());
}
- mCurrentView = view;
- if (mViewTypeChangedListener != null) {
- mViewTypeChangedListener.onViewTypeChanged(mInstance, view.getViewType());
+ if (view != mCurrentView) {
+ mCurrentView = view;
+ if (mViewTypeChangedListener != null) {
+ mViewTypeChangedListener.onViewTypeChanged(mInstance, view.getViewType());
+ }
}
if (needToStart()) {
@@ -689,7 +625,7 @@
mSubtitleController = new SubtitleController(context);
mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context));
mSubtitleController.registerRenderer(new Cea708CaptionRenderer(context));
- mSubtitleController.setAnchor((SubtitleController.Anchor) mSubtitleAnchorView);
+ mSubtitleController.setAnchor(mSubtitleAnchorView);
// we don't set the target state here either, but preserve the
// target state that was there before.
@@ -760,7 +696,7 @@
// TODO: move this method inside callback to make sure it runs inside the callback thread.
Bundle extractTrackInfoData() {
- List<XMediaPlayer.TrackInfo> trackInfos = mMediaPlayer.getTrackInfo();
+ List<MediaPlayer.TrackInfo> trackInfos = mMediaPlayer.getTrackInfo();
mVideoTrackIndices = new ArrayList<>();
mAudioTrackIndices = new ArrayList<>();
mSubtitleTracks = new SparseArray<>();
@@ -874,6 +810,8 @@
MediaMetadata2.METADATA_KEY_DURATION, mMediaSession.getPlayer().getDuration());
builder.putString(
MediaMetadata2.METADATA_KEY_MEDIA_ID, mMediaItem.getMediaId());
+ builder.putLong(MediaMetadata2.METADATA_KEY_BROWSABLE, MediaMetadata2.BROWSABLE_TYPE_NONE);
+ builder.putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1);
return builder.build();
}
@@ -956,11 +894,11 @@
mCurrentMusicView = newMusicView;
}
- XMediaPlayer.PlayerCallback mMediaPlayerCallback =
- new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback mMediaPlayerCallback =
+ new MediaPlayer.PlayerCallback() {
@Override
public void onVideoSizeChanged(
- XMediaPlayer mp, MediaItem2 dsd, int width, int height) {
+ MediaPlayer mp, MediaItem2 dsd, int width, int height) {
if (DEBUG) {
Log.d(TAG, "onVideoSizeChanged(): size: " + width + "/" + height);
}
@@ -983,7 +921,7 @@
@Override
public void onInfo(
- XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
if (DEBUG) {
Log.d(TAG, "onInfo()");
}
@@ -1005,7 +943,7 @@
@Override
public void onError(
- XMediaPlayer mp, MediaItem2 dsd, int frameworkErr, int implErr) {
+ MediaPlayer mp, MediaItem2 dsd, int frameworkErr, int implErr) {
if (DEBUG) {
Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
}
@@ -1023,7 +961,7 @@
@Override
public void onSubtitleData(
- XMediaPlayer mp, MediaItem2 dsd, SubtitleData2 data) {
+ MediaPlayer mp, MediaItem2 dsd, SubtitleData2 data) {
if (DEBUG) {
Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
+ ", getCurrentPosition: " + mp.getCurrentPosition()
@@ -1089,14 +1027,13 @@
MediaMetadata2 metadata = extractMetadata();
if (metadata != null) {
mMediaItem.setMetadata(metadata);
- mMediaSession.getPlayer().replacePlaylistItem(0, mMediaItem);
}
}
if (mMediaControlView != null) {
mMediaControlView.setEnabled(true);
- Uri uri = (mMediaItem != null && mMediaItem instanceof UriMediaItem2)
+ Uri uri = (mMediaItem instanceof UriMediaItem2)
? ((UriMediaItem2) mMediaItem).getUri() : null;
if (uri != null) {
String scheme = uri.getScheme();
@@ -1133,7 +1070,7 @@
}
}
- private void onCompletion(XMediaPlayer mp, MediaItem2 dsd) {
+ private void onCompletion(MediaPlayer mp, MediaItem2 dsd) {
mCurrentState = STATE_PLAYBACK_COMPLETED;
mTargetState = STATE_PLAYBACK_COMPLETED;
}
@@ -1152,15 +1089,17 @@
SessionCommandGroup2.Builder commandsBuilder = new SessionCommandGroup2.Builder()
.addCommand(SessionCommand2.COMMAND_CODE_PLAYER_PAUSE)
.addCommand(SessionCommand2.COMMAND_CODE_PLAYER_PLAY)
- .addCommand(SessionCommand2.COMMAND_CODE_PLAYER_PREFETCH)
+ .addCommand(SessionCommand2.COMMAND_CODE_PLAYER_PREPARE)
.addCommand(SessionCommand2.COMMAND_CODE_PLAYER_SET_SPEED)
.addCommand(SessionCommand2.COMMAND_CODE_SESSION_FAST_FORWARD)
.addCommand(SessionCommand2.COMMAND_CODE_SESSION_REWIND)
+ .addCommand(SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM)
+ .addCommand(SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM)
.addCommand(SessionCommand2.COMMAND_CODE_PLAYER_SEEK_TO)
.addCommand(SessionCommand2.COMMAND_CODE_VOLUME_SET_VOLUME)
.addCommand(SessionCommand2.COMMAND_CODE_VOLUME_ADJUST_VOLUME)
.addCommand(SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI)
- .addCommand(SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_URI)
+ .addCommand(SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI)
.addCommand(SessionCommand2.COMMAND_CODE_SESSION_SELECT_ROUTE)
.addCommand(SessionCommand2.COMMAND_CODE_PLAYER_GET_PLAYLIST)
.addCommand(SessionCommand2.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA)
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoView2Player.java b/media-widget/src/main/java/androidx/media/widget/VideoView2Player.java
index 35c2f6e..aaa5458 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoView2Player.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoView2Player.java
@@ -20,14 +20,14 @@
import androidx.media2.MediaItem2;
import androidx.media2.MediaMetadata2;
-import androidx.media2.XMediaPlayer;
+import androidx.media2.MediaPlayer;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
-class VideoView2Player extends XMediaPlayer {
+class VideoView2Player extends MediaPlayer {
VideoView2Player(Context context) {
super(context);
}
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoViewInterface.java b/media-widget/src/main/java/androidx/media/widget/VideoViewInterface.java
index 2c7879c..d7eb7c2 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoViewInterface.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoViewInterface.java
@@ -19,7 +19,7 @@
import android.view.View;
import androidx.annotation.NonNull;
-import androidx.media2.XMediaPlayer;
+import androidx.media2.MediaPlayer;
interface VideoViewInterface {
/**
@@ -29,10 +29,10 @@
* @return true if the surface is successfully assigned, false if not. It will fail to assign
* if any of MediaPlayer or surface is unavailable.
*/
- boolean assignSurfaceToMediaPlayer(XMediaPlayer mp);
+ boolean assignSurfaceToMediaPlayer(MediaPlayer mp);
void setSurfaceListener(SurfaceListener l);
int getViewType();
- void setMediaPlayer(XMediaPlayer mp);
+ void setMediaPlayer(MediaPlayer mp);
/**
* Takes over oldView. It means that the MediaPlayer will start rendering on this view.
diff --git a/media/api21/android/support/v4/media/MediaBrowserCompatApi21.java b/media/api21/android/support/v4/media/MediaBrowserCompatApi21.java
deleted file mode 100644
index acd8bf8..0000000
--- a/media/api21/android/support/v4/media/MediaBrowserCompatApi21.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2018 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 android.support.v4.media;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.util.List;
-
-@RequiresApi(21)
-class MediaBrowserCompatApi21 {
- static final String NULL_MEDIA_ITEM_ID =
- "android.support.v4.media.MediaBrowserCompat.NULL_MEDIA_ITEM";
-
- public static Object createConnectionCallback(ConnectionCallback callback) {
- return new ConnectionCallbackProxy<>(callback);
- }
-
- public static Object createBrowser(Context context, ComponentName serviceComponent,
- Object callback, Bundle rootHints) {
- return new MediaBrowser(context, serviceComponent,
- (MediaBrowser.ConnectionCallback) callback, rootHints);
- }
-
- public static void connect(Object browserObj) {
- ((MediaBrowser)browserObj).connect();
- }
-
- public static void disconnect(Object browserObj) {
- ((MediaBrowser)browserObj).disconnect();
-
- }
-
- public static boolean isConnected(Object browserObj) {
- return ((MediaBrowser)browserObj).isConnected();
- }
-
- public static ComponentName getServiceComponent(Object browserObj) {
- return ((MediaBrowser)browserObj).getServiceComponent();
- }
-
- public static String getRoot(Object browserObj) {
- return ((MediaBrowser)browserObj).getRoot();
- }
-
- public static Bundle getExtras(Object browserObj) {
- return ((MediaBrowser)browserObj).getExtras();
- }
-
- public static Object getSessionToken(Object browserObj) {
- return ((MediaBrowser)browserObj).getSessionToken();
- }
-
- public static Object createSubscriptionCallback(SubscriptionCallback callback) {
- return new SubscriptionCallbackProxy<>(callback);
- }
-
- public static void subscribe(
- Object browserObj, String parentId, Object subscriptionCallbackObj) {
- ((MediaBrowser)browserObj).subscribe(parentId,
- (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
- }
-
- public static void unsubscribe(Object browserObj, String parentId) {
- ((MediaBrowser)browserObj).unsubscribe(parentId);
- }
-
- interface ConnectionCallback {
- void onConnected();
- void onConnectionSuspended();
- void onConnectionFailed();
- }
-
- static class ConnectionCallbackProxy<T extends ConnectionCallback>
- extends MediaBrowser.ConnectionCallback {
- protected final T mConnectionCallback;
-
- public ConnectionCallbackProxy(T connectionCallback) {
- mConnectionCallback = connectionCallback;
- }
-
- @Override
- public void onConnected() {
- mConnectionCallback.onConnected();
- }
-
- @Override
- public void onConnectionSuspended() {
- mConnectionCallback.onConnectionSuspended();
- }
-
- @Override
- public void onConnectionFailed() {
- mConnectionCallback.onConnectionFailed();
- }
- }
-
- interface SubscriptionCallback {
- void onChildrenLoaded(@NonNull String parentId, List<?> children);
- void onError(@NonNull String parentId);
- }
-
- static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
- extends MediaBrowser.SubscriptionCallback {
- protected final T mSubscriptionCallback;
-
- public SubscriptionCallbackProxy(T callback) {
- mSubscriptionCallback = callback;
- }
-
- @Override
- public void onChildrenLoaded(@NonNull String parentId,
- List<MediaBrowser.MediaItem> children) {
- mSubscriptionCallback.onChildrenLoaded(parentId, children);
- }
-
- @Override
- public void onError(@NonNull String parentId) {
- mSubscriptionCallback.onError(parentId);
- }
- }
-
- static class MediaItem {
-
- public static int getFlags(Object itemObj) {
- return ((MediaBrowser.MediaItem) itemObj).getFlags();
- }
-
- public static Object getDescription(Object itemObj) {
- return ((MediaBrowser.MediaItem) itemObj).getDescription();
- }
-
- private MediaItem() {
- }
- }
-
- private MediaBrowserCompatApi21() {
- }
-}
diff --git a/media/api21/android/support/v4/media/MediaDescriptionCompatApi21.java b/media/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
deleted file mode 100644
index bed7f01..0000000
--- a/media/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media;
-
-import android.graphics.Bitmap;
-import android.media.MediaDescription;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(21)
-class MediaDescriptionCompatApi21 {
-
- public static String getMediaId(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getMediaId();
- }
-
- public static CharSequence getTitle(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getTitle();
- }
-
- public static CharSequence getSubtitle(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getSubtitle();
- }
-
- public static CharSequence getDescription(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getDescription();
- }
-
- public static Bitmap getIconBitmap(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getIconBitmap();
- }
-
- public static Uri getIconUri(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getIconUri();
- }
-
- public static Bundle getExtras(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getExtras();
- }
-
- public static void writeToParcel(Object descriptionObj, Parcel dest, int flags) {
- ((MediaDescription) descriptionObj).writeToParcel(dest, flags);
- }
-
- public static Object fromParcel(Parcel in) {
- return MediaDescription.CREATOR.createFromParcel(in);
- }
-
- static class Builder {
- public static Object newInstance() {
- return new MediaDescription.Builder();
- }
-
-
- public static void setMediaId(Object builderObj, String mediaId) {
- ((MediaDescription.Builder)builderObj).setMediaId(mediaId);
- }
-
- public static void setTitle(Object builderObj, CharSequence title) {
- ((MediaDescription.Builder)builderObj).setTitle(title);
- }
-
- public static void setSubtitle(Object builderObj, CharSequence subtitle) {
- ((MediaDescription.Builder)builderObj).setSubtitle(subtitle);
- }
-
- public static void setDescription(Object builderObj, CharSequence description) {
- ((MediaDescription.Builder)builderObj).setDescription(description);
- }
-
- public static void setIconBitmap(Object builderObj, Bitmap iconBitmap) {
- ((MediaDescription.Builder)builderObj).setIconBitmap(iconBitmap);
- }
-
- public static void setIconUri(Object builderObj, Uri iconUri) {
- ((MediaDescription.Builder)builderObj).setIconUri(iconUri);
- }
-
- public static void setExtras(Object builderObj, Bundle extras) {
- ((MediaDescription.Builder)builderObj).setExtras(extras);
- }
-
- public static Object build(Object builderObj) {
- return ((MediaDescription.Builder) builderObj).build();
- }
-
- private Builder() {
- }
- }
-
- private MediaDescriptionCompatApi21() {
- }
-}
diff --git a/media/api21/android/support/v4/media/MediaMetadataCompatApi21.java b/media/api21/android/support/v4/media/MediaMetadataCompatApi21.java
deleted file mode 100644
index 5ce873a..0000000
--- a/media/api21/android/support/v4/media/MediaMetadataCompatApi21.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media;
-
-import android.graphics.Bitmap;
-import android.media.MediaMetadata;
-import android.media.Rating;
-import android.os.Parcel;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.Set;
-
-@RequiresApi(21)
-class MediaMetadataCompatApi21 {
- public static Set<String> keySet(Object metadataObj) {
- return ((MediaMetadata)metadataObj).keySet();
- }
-
- public static Bitmap getBitmap(Object metadataObj, String key) {
- return ((MediaMetadata)metadataObj).getBitmap(key);
- }
-
- public static long getLong(Object metadataObj, String key) {
- return ((MediaMetadata)metadataObj).getLong(key);
- }
-
- public static Object getRating(Object metadataObj, String key) {
- return ((MediaMetadata)metadataObj).getRating(key);
- }
-
- public static CharSequence getText(Object metadataObj, String key) {
- return ((MediaMetadata) metadataObj).getText(key);
- }
-
- public static void writeToParcel(Object metadataObj, Parcel dest, int flags) {
- ((MediaMetadata) metadataObj).writeToParcel(dest, flags);
- }
-
- public static Object createFromParcel(Parcel in) {
- return MediaMetadata.CREATOR.createFromParcel(in);
- }
-
- public static class Builder {
- public static Object newInstance() {
- return new MediaMetadata.Builder();
- }
-
- public static void putBitmap(Object builderObj, String key, Bitmap value) {
- ((MediaMetadata.Builder)builderObj).putBitmap(key, value);
- }
-
- public static void putLong(Object builderObj, String key, long value) {
- ((MediaMetadata.Builder)builderObj).putLong(key, value);
- }
-
- public static void putRating(Object builderObj, String key, Object ratingObj) {
- ((MediaMetadata.Builder)builderObj).putRating(key, (Rating)ratingObj);
- }
-
- public static void putText(Object builderObj, String key, CharSequence value) {
- ((MediaMetadata.Builder) builderObj).putText(key, value);
- }
-
- public static void putString(Object builderObj, String key, String value) {
- ((MediaMetadata.Builder) builderObj).putString(key, value);
- }
-
- public static Object build(Object builderObj) {
- return ((MediaMetadata.Builder)builderObj).build();
- }
-
- private Builder() {
- }
- }
-
- private MediaMetadataCompatApi21() {
- }
-}
diff --git a/media/api21/android/support/v4/media/session/MediaControllerCompatApi21.java b/media/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
deleted file mode 100644
index a7fb3fe..0000000
--- a/media/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright 2018 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 android.support.v4.media.session;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.MediaMetadata;
-import android.media.Rating;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.view.KeyEvent;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RequiresApi(21)
-class MediaControllerCompatApi21 {
- public static Object fromToken(Context context, Object sessionToken) {
- return new MediaController(context, (MediaSession.Token) sessionToken);
- }
-
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
- }
-
- public static void registerCallback(Object controllerObj, Object callbackObj, Handler handler) {
- ((MediaController) controllerObj).registerCallback(
- (MediaController.Callback)callbackObj, handler);
- }
-
- public static void unregisterCallback(Object controllerObj, Object callbackObj) {
- ((MediaController) controllerObj)
- .unregisterCallback((MediaController.Callback) callbackObj);
- }
-
- public static void setMediaController(Activity activity, Object controllerObj) {
- activity.setMediaController((MediaController) controllerObj);
- }
-
- public static Object getMediaController(Activity activity) {
- return activity.getMediaController();
- }
-
- public static Object getSessionToken(Object controllerObj) {
- return ((MediaController) controllerObj).getSessionToken();
- }
-
- public static Object getTransportControls(Object controllerObj) {
- return ((MediaController)controllerObj).getTransportControls();
- }
-
- public static Object getPlaybackState(Object controllerObj) {
- return ((MediaController)controllerObj).getPlaybackState();
- }
-
- public static Object getMetadata(Object controllerObj) {
- return ((MediaController)controllerObj).getMetadata();
- }
-
- public static List<Object> getQueue(Object controllerObj) {
- List<MediaSession.QueueItem> queue = ((MediaController) controllerObj).getQueue();
- if (queue == null) {
- return null;
- }
- List<Object> queueObjs = new ArrayList<Object>(queue);
- return queueObjs;
- }
-
- public static CharSequence getQueueTitle(Object controllerObj) {
- return ((MediaController) controllerObj).getQueueTitle();
- }
-
- public static Bundle getExtras(Object controllerObj) {
- return ((MediaController) controllerObj).getExtras();
- }
-
- public static int getRatingType(Object controllerObj) {
- return ((MediaController) controllerObj).getRatingType();
- }
-
- public static long getFlags(Object controllerObj) {
- return ((MediaController) controllerObj).getFlags();
- }
-
- public static Object getPlaybackInfo(Object controllerObj) {
- return ((MediaController) controllerObj).getPlaybackInfo();
- }
-
- public static PendingIntent getSessionActivity(Object controllerObj) {
- return ((MediaController) controllerObj).getSessionActivity();
- }
-
- public static boolean dispatchMediaButtonEvent(Object controllerObj, KeyEvent event) {
- return ((MediaController) controllerObj).dispatchMediaButtonEvent(event);
- }
-
- public static void setVolumeTo(Object controllerObj, int value, int flags) {
- ((MediaController) controllerObj).setVolumeTo(value, flags);
- }
-
- public static void adjustVolume(Object controllerObj, int direction, int flags) {
- ((MediaController) controllerObj).adjustVolume(direction, flags);
- }
-
- public static void sendCommand(Object controllerObj,
- String command, Bundle params, ResultReceiver cb) {
- ((MediaController) controllerObj).sendCommand(command, params, cb);
- }
-
- public static String getPackageName(Object controllerObj) {
- return ((MediaController) controllerObj).getPackageName();
- }
-
- public static class TransportControls {
- public static void play(Object controlsObj) {
- ((MediaController.TransportControls)controlsObj).play();
- }
-
- public static void pause(Object controlsObj) {
- ((MediaController.TransportControls)controlsObj).pause();
- }
-
- public static void stop(Object controlsObj) {
- ((MediaController.TransportControls)controlsObj).stop();
- }
-
- public static void seekTo(Object controlsObj, long pos) {
- ((MediaController.TransportControls)controlsObj).seekTo(pos);
- }
-
- public static void fastForward(Object controlsObj) {
- ((MediaController.TransportControls)controlsObj).fastForward();
- }
-
- public static void rewind(Object controlsObj) {
- ((MediaController.TransportControls)controlsObj).rewind();
- }
-
- public static void skipToNext(Object controlsObj) {
- ((MediaController.TransportControls)controlsObj).skipToNext();
- }
-
- public static void skipToPrevious(Object controlsObj) {
- ((MediaController.TransportControls)controlsObj).skipToPrevious();
- }
-
- public static void setRating(Object controlsObj, Object ratingObj) {
- ((MediaController.TransportControls)controlsObj).setRating((Rating)ratingObj);
- }
-
- public static void playFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
- ((MediaController.TransportControls) controlsObj).playFromMediaId(mediaId, extras);
- }
-
- public static void playFromSearch(Object controlsObj, String query, Bundle extras) {
- ((MediaController.TransportControls) controlsObj).playFromSearch(query, extras);
- }
-
- public static void skipToQueueItem(Object controlsObj, long id) {
- ((MediaController.TransportControls) controlsObj).skipToQueueItem(id);
- }
-
- public static void sendCustomAction(Object controlsObj, String action, Bundle args) {
- ((MediaController.TransportControls) controlsObj).sendCustomAction(action, args);
- }
-
- private TransportControls() {
- }
- }
-
- public static class PlaybackInfo {
- public static int getPlaybackType(Object volumeInfoObj) {
- return ((MediaController.PlaybackInfo)volumeInfoObj).getPlaybackType();
- }
-
- public static AudioAttributes getAudioAttributes(Object volumeInfoObj) {
- return ((MediaController.PlaybackInfo) volumeInfoObj).getAudioAttributes();
- }
-
- public static int getLegacyAudioStream(Object volumeInfoObj) {
- AudioAttributes attrs = getAudioAttributes(volumeInfoObj);
- return toLegacyStreamType(attrs);
- }
-
- public static int getVolumeControl(Object volumeInfoObj) {
- return ((MediaController.PlaybackInfo)volumeInfoObj).getVolumeControl();
- }
-
- public static int getMaxVolume(Object volumeInfoObj) {
- return ((MediaController.PlaybackInfo)volumeInfoObj).getMaxVolume();
- }
-
- public static int getCurrentVolume(Object volumeInfoObj) {
- return ((MediaController.PlaybackInfo)volumeInfoObj).getCurrentVolume();
- }
-
- // This is copied from AudioAttributes.toLegacyStreamType. TODO This
- // either needs to be kept in sync with that one or toLegacyStreamType
- // needs to be made public so it can be used by the support lib.
- private static final int FLAG_SCO = 0x1 << 2;
- private static final int STREAM_BLUETOOTH_SCO = 6;
- private static final int STREAM_SYSTEM_ENFORCED = 7;
- private static int toLegacyStreamType(AudioAttributes aa) {
- // flags to stream type mapping
- if ((aa.getFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
- == AudioAttributes.FLAG_AUDIBILITY_ENFORCED) {
- return STREAM_SYSTEM_ENFORCED;
- }
- if ((aa.getFlags() & FLAG_SCO) == FLAG_SCO) {
- return STREAM_BLUETOOTH_SCO;
- }
-
- // usage to stream type mapping
- switch (aa.getUsage()) {
- case AudioAttributes.USAGE_MEDIA:
- case AudioAttributes.USAGE_GAME:
- case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
- case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
- return AudioManager.STREAM_MUSIC;
- case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
- return AudioManager.STREAM_SYSTEM;
- case AudioAttributes.USAGE_VOICE_COMMUNICATION:
- return AudioManager.STREAM_VOICE_CALL;
- case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
- return AudioManager.STREAM_DTMF;
- case AudioAttributes.USAGE_ALARM:
- return AudioManager.STREAM_ALARM;
- case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
- return AudioManager.STREAM_RING;
- case AudioAttributes.USAGE_NOTIFICATION:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
- case AudioAttributes.USAGE_NOTIFICATION_EVENT:
- return AudioManager.STREAM_NOTIFICATION;
- case AudioAttributes.USAGE_UNKNOWN:
- default:
- return AudioManager.STREAM_MUSIC;
- }
- }
-
- private PlaybackInfo() {
- }
- }
-
- public static interface Callback {
- public void onSessionDestroyed();
- public void onSessionEvent(String event, Bundle extras);
- public void onPlaybackStateChanged(Object stateObj);
- public void onMetadataChanged(Object metadataObj);
- public void onQueueChanged(List<?> queue);
- public void onQueueTitleChanged(CharSequence title);
- public void onExtrasChanged(Bundle extras);
- public void onAudioInfoChanged(int type, int stream, int control, int max, int current);
- }
-
- static class CallbackProxy<T extends Callback> extends MediaController.Callback {
- protected final T mCallback;
-
- public CallbackProxy(T callback) {
- mCallback = callback;
- }
-
- @Override
- public void onSessionDestroyed() {
- mCallback.onSessionDestroyed();
- }
-
- @Override
- public void onSessionEvent(String event, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onSessionEvent(event, extras);
- }
-
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- mCallback.onPlaybackStateChanged(state);
- }
-
- @Override
- public void onMetadataChanged(MediaMetadata metadata) {
- mCallback.onMetadataChanged(metadata);
- }
-
- @Override
- public void onQueueChanged(List<MediaSession.QueueItem> queue) {
- mCallback.onQueueChanged(queue);
- }
-
- @Override
- public void onQueueTitleChanged(CharSequence title) {
- mCallback.onQueueTitleChanged(title);
- }
-
- @Override
- public void onExtrasChanged(Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onExtrasChanged(extras);
- }
-
- @Override
- public void onAudioInfoChanged(MediaController.PlaybackInfo info){
- mCallback.onAudioInfoChanged(info.getPlaybackType(),
- PlaybackInfo.getLegacyAudioStream(info), info.getVolumeControl(),
- info.getMaxVolume(), info.getCurrentVolume());
- }
- }
-
- private MediaControllerCompatApi21() {
- }
-}
diff --git a/media/api21/android/support/v4/media/session/MediaSessionCompatApi21.java b/media/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
deleted file mode 100644
index 7d36c4a..0000000
--- a/media/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media.session;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioAttributes;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.Rating;
-import android.media.VolumeProvider;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Parcelable;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import androidx.annotation.RequiresApi;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-
-@RequiresApi(21)
-class MediaSessionCompatApi21 {
- static final String TAG = "MediaSessionCompatApi21";
-
- public static Object createSession(Context context, String tag) {
- return new MediaSession(context, tag);
- }
-
- public static Object verifySession(Object mediaSession) {
- if (mediaSession instanceof MediaSession) {
- return mediaSession;
- }
- throw new IllegalArgumentException("mediaSession is not a valid MediaSession object");
- }
-
- public static Object verifyToken(Object token) {
- if (token instanceof MediaSession.Token) {
- return token;
- }
- throw new IllegalArgumentException("token is not a valid MediaSession.Token object");
- }
-
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
- }
-
- public static void setCallback(Object sessionObj, Object callbackObj, Handler handler) {
- ((MediaSession) sessionObj).setCallback((MediaSession.Callback) callbackObj, handler);
- }
-
- public static void setFlags(Object sessionObj, int flags) {
- ((MediaSession)sessionObj).setFlags(flags);
- }
-
- public static void setPlaybackToLocal(Object sessionObj, int stream) {
- // TODO update APIs to use support version of AudioAttributes
- AudioAttributes.Builder bob = new AudioAttributes.Builder();
- bob.setLegacyStreamType(stream);
- ((MediaSession) sessionObj).setPlaybackToLocal(bob.build());
- }
-
- public static void setPlaybackToRemote(Object sessionObj, Object volumeProviderObj) {
- ((MediaSession)sessionObj).setPlaybackToRemote((VolumeProvider)volumeProviderObj);
- }
-
- public static void setActive(Object sessionObj, boolean active) {
- ((MediaSession)sessionObj).setActive(active);
- }
-
- public static boolean isActive(Object sessionObj) {
- return ((MediaSession)sessionObj).isActive();
- }
-
- public static void sendSessionEvent(Object sessionObj, String event, Bundle extras) {
- ((MediaSession)sessionObj).sendSessionEvent(event, extras);
- }
-
- public static void release(Object sessionObj) {
- ((MediaSession)sessionObj).release();
- }
-
- public static Parcelable getSessionToken(Object sessionObj) {
- return ((MediaSession)sessionObj).getSessionToken();
- }
-
- public static void setPlaybackState(Object sessionObj, Object stateObj) {
- ((MediaSession)sessionObj).setPlaybackState((PlaybackState)stateObj);
- }
-
- public static void setMetadata(Object sessionObj, Object metadataObj) {
- ((MediaSession)sessionObj).setMetadata((MediaMetadata)metadataObj);
- }
-
- public static void setSessionActivity(Object sessionObj, PendingIntent pi) {
- ((MediaSession) sessionObj).setSessionActivity(pi);
- }
-
- public static void setMediaButtonReceiver(Object sessionObj, PendingIntent pi) {
- ((MediaSession) sessionObj).setMediaButtonReceiver(pi);
- }
-
- public static void setQueue(Object sessionObj, List<Object> queueObjs) {
- if (queueObjs == null) {
- ((MediaSession) sessionObj).setQueue(null);
- return;
- }
- ArrayList<MediaSession.QueueItem> queue = new ArrayList<MediaSession.QueueItem>();
- for (Object itemObj : queueObjs) {
- queue.add((MediaSession.QueueItem) itemObj);
- }
- ((MediaSession) sessionObj).setQueue(queue);
- }
-
- public static void setQueueTitle(Object sessionObj, CharSequence title) {
- ((MediaSession) sessionObj).setQueueTitle(title);
- }
-
- public static void setExtras(Object sessionObj, Bundle extras) {
- ((MediaSession) sessionObj).setExtras(extras);
- }
-
- public static boolean hasCallback(Object sessionObj) {
- Field callbackField = null;
- try {
- callbackField = sessionObj.getClass().getDeclaredField("mCallback");
- if (callbackField != null) {
- callbackField.setAccessible(true);
- return callbackField.get(sessionObj) != null;
- }
- } catch (NoSuchFieldException | IllegalAccessException e) {
- Log.w(TAG, "Failed to get mCallback object.");
- }
- return false;
- }
-
- interface Callback {
- void onCommand(String command, Bundle extras, ResultReceiver cb);
- boolean onMediaButtonEvent(Intent mediaButtonIntent);
- void onPlay();
- void onPlayFromMediaId(String mediaId, Bundle extras);
- void onPlayFromSearch(String search, Bundle extras);
- void onSkipToQueueItem(long id);
- void onPause();
- void onSkipToNext();
- void onSkipToPrevious();
- void onFastForward();
- void onRewind();
- void onStop();
- void onSeekTo(long position);
- void onSetRating(Object ratingObject);
- void onSetRating(Object ratingObject, Bundle extras);
- void onCustomAction(String action, Bundle extras);
- }
-
- static class CallbackProxy<T extends Callback> extends MediaSession.Callback {
- protected final T mCallback;
-
- public CallbackProxy(T callback) {
- mCallback = callback;
- }
-
- @Override
- public void onCommand(String command, Bundle args, ResultReceiver cb) {
- MediaSessionCompat.ensureClassLoader(args);
- mCallback.onCommand(command, args, cb);
- }
-
- @Override
- public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
- return mCallback.onMediaButtonEvent(mediaButtonIntent)
- || super.onMediaButtonEvent(mediaButtonIntent);
- }
-
- @Override
- public void onPlay() {
- mCallback.onPlay();
- }
-
- @Override
- public void onPlayFromMediaId(String mediaId, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onPlayFromMediaId(mediaId, extras);
- }
-
- @Override
- public void onPlayFromSearch(String search, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onPlayFromSearch(search, extras);
- }
-
- @Override
- public void onSkipToQueueItem(long id) {
- mCallback.onSkipToQueueItem(id);
- }
-
- @Override
- public void onPause() {
- mCallback.onPause();
- }
-
- @Override
- public void onSkipToNext() {
- mCallback.onSkipToNext();
- }
-
- @Override
- public void onSkipToPrevious() {
- mCallback.onSkipToPrevious();
- }
-
- @Override
- public void onFastForward() {
- mCallback.onFastForward();
- }
-
- @Override
- public void onRewind() {
- mCallback.onRewind();
- }
-
- @Override
- public void onStop() {
- mCallback.onStop();
- }
-
- @Override
- public void onSeekTo(long pos) {
- mCallback.onSeekTo(pos);
- }
-
- @Override
- public void onSetRating(Rating rating) {
- mCallback.onSetRating(rating);
- }
-
- @Override
- public void onCustomAction(String action, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onCustomAction(action, extras);
- }
- }
-
- static class QueueItem {
-
- public static Object createItem(Object mediaDescription, long id) {
- return new MediaSession.QueueItem((MediaDescription) mediaDescription, id);
- }
-
- public static Object getDescription(Object queueItem) {
- return ((MediaSession.QueueItem) queueItem).getDescription();
- }
-
- public static long getQueueId(Object queueItem) {
- return ((MediaSession.QueueItem) queueItem).getQueueId();
- }
-
- private QueueItem() {
- }
- }
-
- private MediaSessionCompatApi21() {
- }
-}
diff --git a/media/api21/android/support/v4/media/session/PlaybackStateCompatApi21.java b/media/api21/android/support/v4/media/session/PlaybackStateCompatApi21.java
deleted file mode 100644
index f4aa9fc..0000000
--- a/media/api21/android/support/v4/media/session/PlaybackStateCompatApi21.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media.session;
-
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.List;
-
-@RequiresApi(21)
-class PlaybackStateCompatApi21 {
- public static int getState(Object stateObj) {
- return ((PlaybackState)stateObj).getState();
- }
-
- public static long getPosition(Object stateObj) {
- return ((PlaybackState)stateObj).getPosition();
- }
-
- public static long getBufferedPosition(Object stateObj) {
- return ((PlaybackState)stateObj).getBufferedPosition();
- }
-
- public static float getPlaybackSpeed(Object stateObj) {
- return ((PlaybackState)stateObj).getPlaybackSpeed();
- }
-
- public static long getActions(Object stateObj) {
- return ((PlaybackState)stateObj).getActions();
- }
-
- public static CharSequence getErrorMessage(Object stateObj) {
- return ((PlaybackState)stateObj).getErrorMessage();
- }
-
- public static long getLastPositionUpdateTime(Object stateObj) {
- return ((PlaybackState)stateObj).getLastPositionUpdateTime();
- }
-
- public static List<Object> getCustomActions(Object stateObj) {
- return (List)((PlaybackState)stateObj).getCustomActions();
- }
-
- public static long getActiveQueueItemId(Object stateObj) {
- return ((PlaybackState)stateObj).getActiveQueueItemId();
- }
-
- public static Object newInstance(int state, long position, long bufferedPosition,
- float speed, long actions, CharSequence errorMessage, long updateTime,
- List<Object> customActions,
- long activeItemId) {
- PlaybackState.Builder stateObj = new PlaybackState.Builder();
- stateObj.setState(state, position, speed, updateTime);
- stateObj.setBufferedPosition(bufferedPosition);
- stateObj.setActions(actions);
- stateObj.setErrorMessage(errorMessage);
- for (Object customAction : customActions) {
- stateObj.addCustomAction((PlaybackState.CustomAction) customAction);
- }
- stateObj.setActiveQueueItemId(activeItemId);
- return stateObj.build();
- }
-
- static final class CustomAction {
- public static String getAction(Object customActionObj) {
- return ((PlaybackState.CustomAction)customActionObj).getAction();
- }
-
- public static CharSequence getName(Object customActionObj) {
- return ((PlaybackState.CustomAction)customActionObj).getName();
- }
-
- public static int getIcon(Object customActionObj) {
- return ((PlaybackState.CustomAction)customActionObj).getIcon();
- }
- public static Bundle getExtras(Object customActionObj) {
- return ((PlaybackState.CustomAction)customActionObj).getExtras();
- }
-
- public static Object newInstance(String action, CharSequence name,
- int icon, Bundle extras) {
- PlaybackState.CustomAction.Builder customActionObj =
- new PlaybackState.CustomAction.Builder(action, name, icon);
- customActionObj.setExtras(extras);
- return customActionObj.build();
- }
-
- private CustomAction() {
- }
- }
-
- private PlaybackStateCompatApi21() {
- }
-}
diff --git a/media/api21/androidx/media/MediaBrowserServiceCompatApi21.java b/media/api21/androidx/media/MediaBrowserServiceCompatApi21.java
deleted file mode 100644
index c9c1a28..0000000
--- a/media/api21/androidx/media/MediaBrowserServiceCompatApi21.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import android.content.Context;
-import android.content.Intent;
-import android.media.browse.MediaBrowser;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.service.media.MediaBrowserService;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RequiresApi(21)
-class MediaBrowserServiceCompatApi21 {
-
- public static Object createService(Context context, ServiceCompatProxy serviceProxy) {
- return new MediaBrowserServiceAdaptor(context, serviceProxy);
- }
-
- public static void onCreate(Object serviceObj) {
- ((MediaBrowserService) serviceObj).onCreate();
- }
-
- public static IBinder onBind(Object serviceObj, Intent intent) {
- return ((MediaBrowserService) serviceObj).onBind(intent);
- }
-
- public static void setSessionToken(Object serviceObj, Object token) {
- ((MediaBrowserService) serviceObj).setSessionToken((MediaSession.Token) token);
- }
-
- public static void notifyChildrenChanged(Object serviceObj, String parentId) {
- ((MediaBrowserService) serviceObj).notifyChildrenChanged(parentId);
- }
-
- public interface ServiceCompatProxy {
- BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints);
- void onLoadChildren(String parentId, ResultWrapper<List<Parcel>> result);
- }
-
- static class ResultWrapper<T> {
- MediaBrowserService.Result mResultObj;
-
- ResultWrapper(MediaBrowserService.Result result) {
- mResultObj = result;
- }
-
- public void sendResult(T result) {
- if (result instanceof List) {
- mResultObj.sendResult(parcelListToItemList((List<Parcel>)result));
- } else if (result instanceof Parcel) {
- Parcel parcel = (Parcel) result;
- parcel.setDataPosition(0);
- mResultObj.sendResult(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
- parcel.recycle();
- } else {
- // The result is null or an invalid instance.
- mResultObj.sendResult(null);
- }
- }
-
- public void detach() {
- mResultObj.detach();
- }
-
- List<MediaBrowser.MediaItem> parcelListToItemList(List<Parcel> parcelList) {
- if (parcelList == null) {
- return null;
- }
- List<MediaBrowser.MediaItem> items = new ArrayList<>();
- for (Parcel parcel : parcelList) {
- parcel.setDataPosition(0);
- items.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
- parcel.recycle();
- }
- return items;
- }
- }
-
- static class BrowserRoot {
- final String mRootId;
- final Bundle mExtras;
-
- BrowserRoot(String rootId, Bundle extras) {
- mRootId = rootId;
- mExtras = extras;
- }
- }
-
- static class MediaBrowserServiceAdaptor extends MediaBrowserService {
- final ServiceCompatProxy mServiceProxy;
-
- MediaBrowserServiceAdaptor(Context context, ServiceCompatProxy serviceWrapper) {
- attachBaseContext(context);
- mServiceProxy = serviceWrapper;
- }
-
- @Override
- public MediaBrowserService.BrowserRoot onGetRoot(String clientPackageName, int clientUid,
- Bundle rootHints) {
- MediaSessionCompat.ensureClassLoader(rootHints);
- MediaBrowserServiceCompatApi21.BrowserRoot browserRoot = mServiceProxy.onGetRoot(
- clientPackageName, clientUid, rootHints == null ? null : new Bundle(rootHints));
- return browserRoot == null ? null : new MediaBrowserService.BrowserRoot(
- browserRoot.mRootId, browserRoot.mExtras);
- }
-
- @Override
- public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {
- mServiceProxy.onLoadChildren(parentId, new ResultWrapper<List<Parcel>>(result));
- }
- }
-
- private MediaBrowserServiceCompatApi21() {
- }
-}
diff --git a/media/api21/androidx/media/VolumeProviderCompatApi21.java b/media/api21/androidx/media/VolumeProviderCompatApi21.java
deleted file mode 100644
index 6d540b3..0000000
--- a/media/api21/androidx/media/VolumeProviderCompatApi21.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import android.media.VolumeProvider;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(21)
-class VolumeProviderCompatApi21 {
- public static Object createVolumeProvider(int volumeControl, int maxVolume, int currentVolume,
- final Delegate delegate) {
- return new VolumeProvider(volumeControl, maxVolume, currentVolume) {
- @Override
- public void onSetVolumeTo(int volume) {
- delegate.onSetVolumeTo(volume);
- }
-
- @Override
- public void onAdjustVolume(int direction) {
- delegate.onAdjustVolume(direction);
- }
- };
- }
-
- public static void setCurrentVolume(Object volumeProviderObj, int currentVolume) {
- ((VolumeProvider) volumeProviderObj).setCurrentVolume(currentVolume);
- }
-
- public interface Delegate {
- void onSetVolumeTo(int volume);
- void onAdjustVolume(int delta);
- }
-
- private VolumeProviderCompatApi21() {
- }
-}
diff --git a/media/api22/android/support/v4/media/session/MediaSessionCompatApi22.java b/media/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
deleted file mode 100644
index 9a44d8c..0000000
--- a/media/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media.session;
-
-import android.media.session.MediaSession;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(22)
-class MediaSessionCompatApi22 {
-
- public static void setRatingType(Object sessionObj, int type) {
- ((MediaSession) sessionObj).setRatingType(type);
- }
-
- private MediaSessionCompatApi22() {
- }
-}
diff --git a/media/api22/android/support/v4/media/session/PlaybackStateCompatApi22.java b/media/api22/android/support/v4/media/session/PlaybackStateCompatApi22.java
deleted file mode 100644
index 1be825fb..0000000
--- a/media/api22/android/support/v4/media/session/PlaybackStateCompatApi22.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media.session;
-
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.List;
-
-@RequiresApi(22)
-class PlaybackStateCompatApi22 {
- public static Bundle getExtras(Object stateObj) {
- return ((PlaybackState)stateObj).getExtras();
- }
-
- public static Object newInstance(int state, long position, long bufferedPosition,
- float speed, long actions, CharSequence errorMessage, long updateTime,
- List<Object> customActions,
- long activeItemId, Bundle extras) {
- PlaybackState.Builder stateObj = new PlaybackState.Builder();
- stateObj.setState(state, position, speed, updateTime);
- stateObj.setBufferedPosition(bufferedPosition);
- stateObj.setActions(actions);
- stateObj.setErrorMessage(errorMessage);
- for (Object customAction : customActions) {
- stateObj.addCustomAction((PlaybackState.CustomAction) customAction);
- }
- stateObj.setActiveQueueItemId(activeItemId);
- stateObj.setExtras(extras);
- return stateObj.build();
- }
-
- private PlaybackStateCompatApi22() {
- }
-}
diff --git a/media/api23/android/support/v4/media/MediaBrowserCompatApi23.java b/media/api23/android/support/v4/media/MediaBrowserCompatApi23.java
deleted file mode 100644
index 514b1e9..0000000
--- a/media/api23/android/support/v4/media/MediaBrowserCompatApi23.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2018 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 android.support.v4.media;
-
-import android.media.browse.MediaBrowser;
-import android.os.Parcel;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(23)
-class MediaBrowserCompatApi23 {
-
- public static Object createItemCallback(ItemCallback callback) {
- return new ItemCallbackProxy<>(callback);
- }
-
- public static void getItem(Object browserObj, String mediaId, Object itemCallbackObj) {
- ((MediaBrowser) browserObj).getItem(mediaId, ((MediaBrowser.ItemCallback) itemCallbackObj));
- }
-
- interface ItemCallback {
- void onItemLoaded(Parcel itemParcel);
- void onError(@NonNull String itemId);
- }
-
- static class ItemCallbackProxy<T extends ItemCallback> extends MediaBrowser.ItemCallback {
- protected final T mItemCallback;
-
- public ItemCallbackProxy(T callback) {
- mItemCallback = callback;
- }
-
- @Override
- public void onItemLoaded(MediaBrowser.MediaItem item) {
- if (item == null) {
- mItemCallback.onItemLoaded(null);
- } else {
- Parcel parcel = Parcel.obtain();
- item.writeToParcel(parcel, 0);
- mItemCallback.onItemLoaded(parcel);
- }
- }
-
- @Override
- public void onError(@NonNull String itemId) {
- mItemCallback.onError(itemId);
- }
- }
-
- private MediaBrowserCompatApi23() {
- }
-}
diff --git a/media/api23/android/support/v4/media/MediaDescriptionCompatApi23.java b/media/api23/android/support/v4/media/MediaDescriptionCompatApi23.java
deleted file mode 100644
index ee1b1a1..0000000
--- a/media/api23/android/support/v4/media/MediaDescriptionCompatApi23.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.support.v4.media;
-
-import android.media.MediaDescription;
-import android.net.Uri;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(23)
-class MediaDescriptionCompatApi23 {
- public static Uri getMediaUri(Object descriptionObj) {
- return ((MediaDescription) descriptionObj).getMediaUri();
- }
-
- static class Builder {
- public static void setMediaUri(Object builderObj, Uri mediaUri) {
- ((MediaDescription.Builder)builderObj).setMediaUri(mediaUri);
- }
-
- private Builder() {
- }
- }
-
- private MediaDescriptionCompatApi23() {
- }
-}
diff --git a/media/api23/android/support/v4/media/session/MediaControllerCompatApi23.java b/media/api23/android/support/v4/media/session/MediaControllerCompatApi23.java
deleted file mode 100644
index c5137b5..0000000
--- a/media/api23/android/support/v4/media/session/MediaControllerCompatApi23.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2018 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 android.support.v4.media.session;
-
-import android.media.session.MediaController;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(23)
-class MediaControllerCompatApi23 {
-
- public static class TransportControls {
- public static void playFromUri(Object controlsObj, Uri uri, Bundle extras) {
- ((MediaController.TransportControls) controlsObj).playFromUri(uri, extras);
- }
-
- private TransportControls() {
- }
- }
-
- private MediaControllerCompatApi23() {
- }
-}
diff --git a/media/api23/android/support/v4/media/session/MediaSessionCompatApi23.java b/media/api23/android/support/v4/media/session/MediaSessionCompatApi23.java
deleted file mode 100644
index e95e13e..0000000
--- a/media/api23/android/support/v4/media/session/MediaSessionCompatApi23.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media.session;
-
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(23)
-class MediaSessionCompatApi23 {
-
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
- }
-
- public interface Callback extends MediaSessionCompatApi21.Callback {
- public void onPlayFromUri(Uri uri, Bundle extras);
- }
-
- static class CallbackProxy<T extends Callback>
- extends MediaSessionCompatApi21.CallbackProxy<T> {
- public CallbackProxy(T callback) {
- super(callback);
- }
-
- @Override
- public void onPlayFromUri(Uri uri, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onPlayFromUri(uri, extras);
- }
- }
-
- private MediaSessionCompatApi23() {
- }
-}
diff --git a/media/api23/androidx/media/MediaBrowserServiceCompatApi23.java b/media/api23/androidx/media/MediaBrowserServiceCompatApi23.java
deleted file mode 100644
index 0292fb9..0000000
--- a/media/api23/androidx/media/MediaBrowserServiceCompatApi23.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import android.content.Context;
-import android.media.browse.MediaBrowser;
-import android.os.Parcel;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(23)
-class MediaBrowserServiceCompatApi23 {
-
- public static Object createService(Context context, ServiceCompatProxy serviceProxy) {
- return new MediaBrowserServiceAdaptor(context, serviceProxy);
- }
-
- public interface ServiceCompatProxy extends MediaBrowserServiceCompatApi21.ServiceCompatProxy {
- void onLoadItem(String itemId, MediaBrowserServiceCompatApi21.ResultWrapper<Parcel> result);
- }
-
- static class MediaBrowserServiceAdaptor extends
- MediaBrowserServiceCompatApi21.MediaBrowserServiceAdaptor {
- MediaBrowserServiceAdaptor(Context context, ServiceCompatProxy serviceWrapper) {
- super(context, serviceWrapper);
- }
-
- @Override
- public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
- ((ServiceCompatProxy) mServiceProxy).onLoadItem(itemId,
- new MediaBrowserServiceCompatApi21.ResultWrapper<Parcel>(result));
- }
- }
-
- private MediaBrowserServiceCompatApi23() {
- }
-}
diff --git a/media/api24/android/support/v4/media/session/MediaControllerCompatApi24.java b/media/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
deleted file mode 100644
index f130ed0..0000000
--- a/media/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 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 android.support.v4.media.session;
-
-import android.media.session.MediaController;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(24)
-class MediaControllerCompatApi24 {
-
- public static class TransportControls {
- public static void prepare(Object controlsObj) {
- ((MediaController.TransportControls) controlsObj).prepare();
- }
-
- public static void prepareFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
- ((MediaController.TransportControls) controlsObj).prepareFromMediaId(mediaId, extras);
- }
-
- public static void prepareFromSearch(Object controlsObj, String query, Bundle extras) {
- ((MediaController.TransportControls) controlsObj).prepareFromSearch(query, extras);
- }
-
- public static void prepareFromUri(Object controlsObj, Uri uri, Bundle extras) {
- ((MediaController.TransportControls) controlsObj).prepareFromUri(uri, extras);
- }
-
- private TransportControls() {
- }
- }
-
- private MediaControllerCompatApi24() {
- }
-}
diff --git a/media/api24/android/support/v4/media/session/MediaSessionCompatApi24.java b/media/api24/android/support/v4/media/session/MediaSessionCompatApi24.java
deleted file mode 100644
index 9ba0915..0000000
--- a/media/api24/android/support/v4/media/session/MediaSessionCompatApi24.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.support.v4.media.session;
-
-import android.media.session.MediaSession;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.RequiresApi;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-@RequiresApi(24)
-class MediaSessionCompatApi24 {
- private static final String TAG = "MediaSessionCompatApi24";
-
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
- }
-
- public static String getCallingPackage(Object sessionObj) {
- MediaSession session = (MediaSession) sessionObj;
- try {
- Method getCallingPackageMethod = session.getClass().getMethod("getCallingPackage");
- return (String) getCallingPackageMethod.invoke(session);
- } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
- Log.e(TAG, "Cannot execute MediaSession.getCallingPackage()", e);
- }
- return null;
- }
-
- public interface Callback extends MediaSessionCompatApi23.Callback {
- public void onPrepare();
- public void onPrepareFromMediaId(String mediaId, Bundle extras);
- public void onPrepareFromSearch(String query, Bundle extras);
- public void onPrepareFromUri(Uri uri, Bundle extras);
- }
-
- static class CallbackProxy<T extends Callback>
- extends MediaSessionCompatApi23.CallbackProxy<T> {
- public CallbackProxy(T callback) {
- super(callback);
- }
-
- @Override
- public void onPrepare() {
- mCallback.onPrepare();
- }
-
- @Override
- public void onPrepareFromMediaId(String mediaId, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onPrepareFromMediaId(mediaId, extras);
- }
-
- @Override
- public void onPrepareFromSearch(String query, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onPrepareFromSearch(query, extras);
- }
-
- @Override
- public void onPrepareFromUri(Uri uri, Bundle extras) {
- MediaSessionCompat.ensureClassLoader(extras);
- mCallback.onPrepareFromUri(uri, extras);
- }
- }
-
- private MediaSessionCompatApi24() {
- }
-}
diff --git a/media/api26/android/support/v4/media/MediaBrowserCompatApi26.java b/media/api26/android/support/v4/media/MediaBrowserCompatApi26.java
deleted file mode 100644
index 653f849..0000000
--- a/media/api26/android/support/v4/media/MediaBrowserCompatApi26.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2018 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 android.support.v4.media;
-
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.util.List;
-
-@RequiresApi(26)
-class MediaBrowserCompatApi26 {
- static Object createSubscriptionCallback(SubscriptionCallback callback) {
- return new SubscriptionCallbackProxy<>(callback);
- }
-
- public static void subscribe(Object browserObj, String parentId, Bundle options,
- Object subscriptionCallbackObj) {
- ((MediaBrowser) browserObj).subscribe(parentId, options,
- (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
- }
-
- public static void unsubscribe(Object browserObj, String parentId,
- Object subscriptionCallbackObj) {
- ((MediaBrowser) browserObj).unsubscribe(parentId,
- (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
- }
-
- interface SubscriptionCallback extends MediaBrowserCompatApi21.SubscriptionCallback {
- void onChildrenLoaded(@NonNull String parentId, List<?> children, @NonNull Bundle options);
- void onError(@NonNull String parentId, @NonNull Bundle options);
- }
-
- static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
- extends MediaBrowserCompatApi21.SubscriptionCallbackProxy<T> {
- SubscriptionCallbackProxy(T callback) {
- super(callback);
- }
-
- @Override
- public void onChildrenLoaded(@NonNull String parentId,
- List<MediaBrowser.MediaItem> children, @NonNull Bundle options) {
- MediaSessionCompat.ensureClassLoader(options);
- mSubscriptionCallback.onChildrenLoaded(parentId, children, options);
- }
-
- @Override
- public void onError(@NonNull String parentId, @NonNull Bundle options) {
- MediaSessionCompat.ensureClassLoader(options);
- mSubscriptionCallback.onError(parentId, options);
- }
- }
-
- private MediaBrowserCompatApi26() {
- }
-}
diff --git a/media/api26/androidx/media/MediaBrowserServiceCompatApi26.java b/media/api26/androidx/media/MediaBrowserServiceCompatApi26.java
deleted file mode 100644
index dd528c5..0000000
--- a/media/api26/androidx/media/MediaBrowserServiceCompatApi26.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import android.content.Context;
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.service.media.MediaBrowserService;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RequiresApi(26)
-class MediaBrowserServiceCompatApi26 {
- private static final String TAG = "MBSCompatApi26";
-
- public static Object createService(Context context, ServiceCompatProxy serviceProxy) {
- return new MediaBrowserServiceAdaptor(context, serviceProxy);
- }
-
- public static void notifyChildrenChanged(Object serviceObj, String parentId, Bundle options) {
- ((MediaBrowserService) serviceObj).notifyChildrenChanged(parentId, options);
- }
-
- public static Bundle getBrowserRootHints(Object serviceObj) {
- return ((MediaBrowserService) serviceObj).getBrowserRootHints();
- }
-
- public interface ServiceCompatProxy extends MediaBrowserServiceCompatApi23.ServiceCompatProxy {
- void onLoadChildren(String parentId, ResultWrapper result, Bundle options);
- }
-
- static class ResultWrapper {
- MediaBrowserService.Result mResultObj;
-
- ResultWrapper(MediaBrowserService.Result result) {
- mResultObj = result;
- }
-
- public void sendResult(List<Parcel> result) {
- mResultObj.sendResult(parcelListToItemList(result));
- }
-
- public void detach() {
- mResultObj.detach();
- }
-
- List<MediaBrowser.MediaItem> parcelListToItemList(List<Parcel> parcelList) {
- if (parcelList == null) {
- return null;
- }
- List<MediaBrowser.MediaItem> items = new ArrayList<>();
- for (Parcel parcel : parcelList) {
- parcel.setDataPosition(0);
- items.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
- parcel.recycle();
- }
- return items;
- }
- }
-
- static class MediaBrowserServiceAdaptor extends
- MediaBrowserServiceCompatApi23.MediaBrowserServiceAdaptor {
- MediaBrowserServiceAdaptor(Context context, ServiceCompatProxy serviceWrapper) {
- super(context, serviceWrapper);
- }
-
- @Override
- public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result,
- Bundle options) {
- MediaSessionCompat.ensureClassLoader(options);
- ((ServiceCompatProxy) mServiceProxy).onLoadChildren(
- parentId, new ResultWrapper(result), options);
- }
- }
-
- private MediaBrowserServiceCompatApi26() {
- }
-}
diff --git a/media/build.gradle b/media/build.gradle
index a02908f..6bb6fdf 100644
--- a/media/build.gradle
+++ b/media/build.gradle
@@ -20,11 +20,6 @@
android {
sourceSets {
main.java.srcDirs += [
- 'api21',
- 'api22',
- 'api23',
- 'api24',
- 'api26',
]
main.res.srcDirs += 'src/main/res-public'
}
diff --git a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
index e805ab3..8a1c08d 100644
--- a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
@@ -53,6 +53,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.media.browse.MediaBrowser;
import android.os.BadParcelableException;
import android.os.Binder;
import android.os.Build;
@@ -488,11 +489,11 @@
if (itemObj == null || Build.VERSION.SDK_INT < 21) {
return null;
}
- int flags = MediaBrowserCompatApi21.MediaItem.getFlags(itemObj);
- MediaDescriptionCompat description =
- MediaDescriptionCompat.fromMediaDescription(
- MediaBrowserCompatApi21.MediaItem.getDescription(itemObj));
- return new MediaItem(description, flags);
+ MediaBrowser.MediaItem itemFwk = (MediaBrowser.MediaItem) itemObj;
+ int flags = itemFwk.getFlags();
+ MediaDescriptionCompat descriptionCompat =
+ MediaDescriptionCompat.fromMediaDescription(itemFwk.getDescription());
+ return new MediaItem(descriptionCompat, flags);
}
/**
@@ -617,15 +618,14 @@
* Callbacks for connection related events.
*/
public static class ConnectionCallback {
- final Object mConnectionCallbackObj;
+ final MediaBrowser.ConnectionCallback mConnectionCallbackFwk;
ConnectionCallbackInternal mConnectionCallbackInternal;
public ConnectionCallback() {
if (Build.VERSION.SDK_INT >= 21) {
- mConnectionCallbackObj =
- MediaBrowserCompatApi21.createConnectionCallback(new StubApi21());
+ mConnectionCallbackFwk = new ConnectionCallbackApi21();
} else {
- mConnectionCallbackObj = null;
+ mConnectionCallbackFwk = null;
}
}
@@ -669,8 +669,9 @@
void onConnectionFailed();
}
- private class StubApi21 implements MediaBrowserCompatApi21.ConnectionCallback {
- StubApi21() {
+ @RequiresApi(21)
+ private class ConnectionCallbackApi21 extends MediaBrowser.ConnectionCallback {
+ ConnectionCallbackApi21() {
}
@Override
@@ -703,20 +704,18 @@
* Callbacks for subscription related events.
*/
public static abstract class SubscriptionCallback {
- final Object mSubscriptionCallbackObj;
+ final MediaBrowser.SubscriptionCallback mSubscriptionCallbackFwk;
final IBinder mToken;
WeakReference<Subscription> mSubscriptionRef;
public SubscriptionCallback() {
mToken = new Binder();
if (Build.VERSION.SDK_INT >= 26) {
- mSubscriptionCallbackObj =
- MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26());
+ mSubscriptionCallbackFwk = new SubscriptionCallbackApi26();
} else if (Build.VERSION.SDK_INT >= 21) {
- mSubscriptionCallbackObj =
- MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
+ mSubscriptionCallbackFwk = new SubscriptionCallbackApi21();
} else {
- mSubscriptionCallbackObj = null;
+ mSubscriptionCallbackFwk = null;
}
}
@@ -773,12 +772,14 @@
mSubscriptionRef = new WeakReference<>(subscription);
}
- private class StubApi21 implements MediaBrowserCompatApi21.SubscriptionCallback {
- StubApi21() {
+ @RequiresApi(21)
+ private class SubscriptionCallbackApi21 extends MediaBrowser.SubscriptionCallback {
+ SubscriptionCallbackApi21() {
}
@Override
- public void onChildrenLoaded(@NonNull String parentId, List<?> children) {
+ public void onChildrenLoaded(@NonNull String parentId,
+ List<MediaBrowser.MediaItem> children) {
Subscription sub = mSubscriptionRef == null ? null : mSubscriptionRef.get();
if (sub == null) {
SubscriptionCallback.this.onChildrenLoaded(
@@ -828,20 +829,23 @@
}
- private class StubApi26 extends StubApi21
- implements MediaBrowserCompatApi26.SubscriptionCallback {
- StubApi26() {
+ @RequiresApi(26)
+ private class SubscriptionCallbackApi26 extends SubscriptionCallbackApi21 {
+ SubscriptionCallbackApi26() {
}
@Override
- public void onChildrenLoaded(@NonNull String parentId, List<?> children,
+ public void onChildrenLoaded(@NonNull String parentId,
+ List<MediaBrowser.MediaItem> children,
@NonNull Bundle options) {
+ MediaSessionCompat.ensureClassLoader(options);
SubscriptionCallback.this.onChildrenLoaded(
parentId, MediaItem.fromMediaItemList(children), options);
}
@Override
public void onError(@NonNull String parentId, @NonNull Bundle options) {
+ MediaSessionCompat.ensureClassLoader(options);
SubscriptionCallback.this.onError(parentId, options);
}
}
@@ -851,13 +855,13 @@
* Callback for receiving the result of {@link #getItem}.
*/
public static abstract class ItemCallback {
- final Object mItemCallbackObj;
+ final MediaBrowser.ItemCallback mItemCallbackFwk;
public ItemCallback() {
if (Build.VERSION.SDK_INT >= 23) {
- mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
+ mItemCallbackFwk = new ItemCallbackApi23();
} else {
- mItemCallbackObj = null;
+ mItemCallbackFwk = null;
}
}
@@ -877,21 +881,14 @@
public void onError(@NonNull String itemId) {
}
- private class StubApi23 implements MediaBrowserCompatApi23.ItemCallback {
- StubApi23() {
+ @RequiresApi(23)
+ private class ItemCallbackApi23 extends MediaBrowser.ItemCallback {
+ ItemCallbackApi23() {
}
@Override
- public void onItemLoaded(Parcel itemParcel) {
- if (itemParcel == null) {
- ItemCallback.this.onItemLoaded(null);
- } else {
- itemParcel.setDataPosition(0);
- MediaItem item =
- MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(itemParcel);
- itemParcel.recycle();
- ItemCallback.this.onItemLoaded(item);
- }
+ public void onItemLoaded(MediaBrowser.MediaItem item) {
+ ItemCallback.this.onItemLoaded(MediaItem.fromMediaItem(item));
}
@Override
@@ -1620,7 +1617,7 @@
static class MediaBrowserImplApi21 implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl,
ConnectionCallback.ConnectionCallbackInternal {
final Context mContext;
- protected final Object mBrowserObj;
+ protected final MediaBrowser mBrowserFwk;
protected final Bundle mRootHints;
protected final CallbackHandler mHandler = new CallbackHandler(this);
private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
@@ -1637,13 +1634,13 @@
mRootHints = (rootHints != null ? new Bundle(rootHints) : new Bundle());
mRootHints.putInt(EXTRA_CLIENT_VERSION, CLIENT_VERSION_CURRENT);
callback.setInternalConnectionCallback(this);
- mBrowserObj = MediaBrowserCompatApi21.createBrowser(context, serviceComponent,
- callback.mConnectionCallbackObj, mRootHints);
+ mBrowserFwk = new MediaBrowser(context, serviceComponent,
+ callback.mConnectionCallbackFwk, mRootHints);
}
@Override
public void connect() {
- MediaBrowserCompatApi21.connect(mBrowserObj);
+ mBrowserFwk.connect();
}
@Override
@@ -1655,29 +1652,29 @@
Log.i(TAG, "Remote error unregistering client messenger." );
}
}
- MediaBrowserCompatApi21.disconnect(mBrowserObj);
+ mBrowserFwk.disconnect();
}
@Override
public boolean isConnected() {
- return MediaBrowserCompatApi21.isConnected(mBrowserObj);
+ return mBrowserFwk.isConnected();
}
@Override
public ComponentName getServiceComponent() {
- return MediaBrowserCompatApi21.getServiceComponent(mBrowserObj);
+ return mBrowserFwk.getServiceComponent();
}
@NonNull
@Override
public String getRoot() {
- return MediaBrowserCompatApi21.getRoot(mBrowserObj);
+ return mBrowserFwk.getRoot();
}
@Nullable
@Override
public Bundle getExtras() {
- return MediaBrowserCompatApi21.getExtras(mBrowserObj);
+ return mBrowserFwk.getExtras();
}
@NonNull
@@ -1685,7 +1682,7 @@
public MediaSessionCompat.Token getSessionToken() {
if (mMediaSessionToken == null) {
mMediaSessionToken = MediaSessionCompat.Token.fromToken(
- MediaBrowserCompatApi21.getSessionToken(mBrowserObj));
+ mBrowserFwk.getSessionToken());
}
return mMediaSessionToken;
}
@@ -1706,8 +1703,7 @@
if (mServiceBinderWrapper == null) {
// TODO: When MediaBrowser is connected to framework's MediaBrowserService,
// subscribe with options won't work properly.
- MediaBrowserCompatApi21.subscribe(
- mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ mBrowserFwk.subscribe(parentId, callback.mSubscriptionCallbackFwk);
} else {
try {
mServiceBinderWrapper.addSubscription(
@@ -1729,7 +1725,7 @@
if (mServiceBinderWrapper == null) {
if (callback == null) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ mBrowserFwk.unsubscribe(parentId);
} else {
final List<SubscriptionCallback> callbacks = sub.getCallbacks();
final List<Bundle> optionsList = sub.getOptionsList();
@@ -1740,7 +1736,7 @@
}
}
if (callbacks.size() == 0) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ mBrowserFwk.unsubscribe(parentId);
}
}
} else {
@@ -1782,7 +1778,7 @@
if (cb == null) {
throw new IllegalArgumentException("cb is null");
}
- if (!MediaBrowserCompatApi21.isConnected(mBrowserObj)) {
+ if (!mBrowserFwk.isConnected()) {
Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
mHandler.post(new Runnable() {
@Override
@@ -1889,7 +1885,7 @@
@Override
public void onConnected() {
- Bundle extras = MediaBrowserCompatApi21.getExtras(mBrowserObj);
+ Bundle extras = mBrowserFwk.getExtras();
if (extras == null) {
return;
}
@@ -1909,7 +1905,7 @@
BundleCompat.getBinder(extras, EXTRA_SESSION_BINDER));
if (sessionToken != null) {
mMediaSessionToken = MediaSessionCompat.Token.fromToken(
- MediaBrowserCompatApi21.getSessionToken(mBrowserObj), sessionToken);
+ mBrowserFwk.getSessionToken(), sessionToken);
}
}
@@ -1993,7 +1989,7 @@
@Override
public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
if (mServiceBinderWrapper == null) {
- MediaBrowserCompatApi23.getItem(mBrowserObj, mediaId, cb.mItemCallbackObj);
+ mBrowserFwk.getItem(mediaId, cb.mItemCallbackFwk);
} else {
super.getItem(mediaId, cb);
}
@@ -2014,11 +2010,9 @@
// This is to prevent ClassNotFoundException when options has Parcelable in it.
if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
if (options == null) {
- MediaBrowserCompatApi21.subscribe(
- mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ mBrowserFwk.subscribe(parentId, callback.mSubscriptionCallbackFwk);
} else {
- MediaBrowserCompatApi26.subscribe(
- mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ mBrowserFwk.subscribe(parentId, options, callback.mSubscriptionCallbackFwk);
}
} else {
super.subscribe(parentId, options, callback);
@@ -2031,10 +2025,9 @@
// This is to prevent ClassNotFoundException when options has Parcelable in it.
if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
if (callback == null) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ mBrowserFwk.unsubscribe(parentId);
} else {
- MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
- callback.mSubscriptionCallbackObj);
+ mBrowserFwk.unsubscribe(parentId, callback.mSubscriptionCallbackFwk);
}
} else {
super.unsubscribe(parentId, callback);
diff --git a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
index 5d41631..d757bbf 100644
--- a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
@@ -18,6 +18,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.graphics.Bitmap;
+import android.media.MediaDescription;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -185,7 +186,7 @@
/**
* A cached copy of the equivalent framework object.
*/
- private Object mDescriptionObj;
+ private MediaDescription mDescriptionFwk;
MediaDescriptionCompat(String mediaId, CharSequence title, CharSequence subtitle,
CharSequence description, Bitmap icon, Uri iconUri, Bundle extras, Uri mediaUri) {
@@ -308,7 +309,7 @@
dest.writeBundle(mExtras);
dest.writeParcelable(mMediaUri, flags);
} else {
- MediaDescriptionCompatApi21.writeToParcel(getMediaDescription(), dest, flags);
+ ((MediaDescription) getMediaDescription()).writeToParcel(dest, flags);
}
}
@@ -329,16 +330,16 @@
* null if none.
*/
public Object getMediaDescription() {
- if (mDescriptionObj != null || Build.VERSION.SDK_INT < 21) {
- return mDescriptionObj;
+ if (mDescriptionFwk != null || Build.VERSION.SDK_INT < 21) {
+ return mDescriptionFwk;
}
- Object bob = MediaDescriptionCompatApi21.Builder.newInstance();
- MediaDescriptionCompatApi21.Builder.setMediaId(bob, mMediaId);
- MediaDescriptionCompatApi21.Builder.setTitle(bob, mTitle);
- MediaDescriptionCompatApi21.Builder.setSubtitle(bob, mSubtitle);
- MediaDescriptionCompatApi21.Builder.setDescription(bob, mDescription);
- MediaDescriptionCompatApi21.Builder.setIconBitmap(bob, mIcon);
- MediaDescriptionCompatApi21.Builder.setIconUri(bob, mIconUri);
+ MediaDescription.Builder bob = new MediaDescription.Builder();
+ bob.setMediaId(mMediaId);
+ bob.setTitle(mTitle);
+ bob.setSubtitle(mSubtitle);
+ bob.setDescription(mDescription);
+ bob.setIconBitmap(mIcon);
+ bob.setIconUri(mIconUri);
// Media URI was not added until API 23, so add it to the Bundle of extras to
// ensure the data is not lost - this ensures that
// fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) returns
@@ -351,13 +352,13 @@
}
extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri);
}
- MediaDescriptionCompatApi21.Builder.setExtras(bob, extras);
+ bob.setExtras(extras);
if (Build.VERSION.SDK_INT >= 23) {
- MediaDescriptionCompatApi23.Builder.setMediaUri(bob, mMediaUri);
+ bob.setMediaUri(mMediaUri);
}
- mDescriptionObj = MediaDescriptionCompatApi21.Builder.build(bob);
+ mDescriptionFwk = bob.build();
- return mDescriptionObj;
+ return mDescriptionFwk;
}
/**
@@ -375,13 +376,14 @@
public static MediaDescriptionCompat fromMediaDescription(Object descriptionObj) {
if (descriptionObj != null && Build.VERSION.SDK_INT >= 21) {
Builder bob = new Builder();
- bob.setMediaId(MediaDescriptionCompatApi21.getMediaId(descriptionObj));
- bob.setTitle(MediaDescriptionCompatApi21.getTitle(descriptionObj));
- bob.setSubtitle(MediaDescriptionCompatApi21.getSubtitle(descriptionObj));
- bob.setDescription(MediaDescriptionCompatApi21.getDescription(descriptionObj));
- bob.setIconBitmap(MediaDescriptionCompatApi21.getIconBitmap(descriptionObj));
- bob.setIconUri(MediaDescriptionCompatApi21.getIconUri(descriptionObj));
- Bundle extras = MediaDescriptionCompatApi21.getExtras(descriptionObj);
+ MediaDescription description = (MediaDescription) descriptionObj;
+ bob.setMediaId(description.getMediaId());
+ bob.setTitle(description.getTitle());
+ bob.setSubtitle(description.getSubtitle());
+ bob.setDescription(description.getDescription());
+ bob.setIconBitmap(description.getIconBitmap());
+ bob.setIconUri(description.getIconUri());
+ Bundle extras = description.getExtras();
Uri mediaUri = null;
if (extras != null) {
MediaSessionCompat.ensureClassLoader(extras);
@@ -405,10 +407,10 @@
if (mediaUri != null) {
bob.setMediaUri(mediaUri);
} else if (Build.VERSION.SDK_INT >= 23) {
- bob.setMediaUri(MediaDescriptionCompatApi23.getMediaUri(descriptionObj));
+ bob.setMediaUri(description.getMediaUri());
}
MediaDescriptionCompat descriptionCompat = bob.build();
- descriptionCompat.mDescriptionObj = descriptionObj;
+ descriptionCompat.mDescriptionFwk = description;
return descriptionCompat;
} else {
@@ -423,7 +425,7 @@
if (Build.VERSION.SDK_INT < 21) {
return new MediaDescriptionCompat(in);
} else {
- return fromMediaDescription(MediaDescriptionCompatApi21.fromParcel(in));
+ return fromMediaDescription(MediaDescription.CREATOR.createFromParcel(in));
}
}
diff --git a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
index 638f13d..7227eb6 100644
--- a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
@@ -18,6 +18,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.graphics.Bitmap;
+import android.media.MediaMetadata;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -367,7 +368,7 @@
};
final Bundle mBundle;
- private Object mMetadataObj;
+ private MediaMetadata mMetadataFwk;
private MediaDescriptionCompat mDescription;
MediaMetadataCompat(Bundle bundle) {
@@ -611,11 +612,11 @@
public static MediaMetadataCompat fromMediaMetadata(Object metadataObj) {
if (metadataObj != null && Build.VERSION.SDK_INT >= 21) {
Parcel p = Parcel.obtain();
- MediaMetadataCompatApi21.writeToParcel(metadataObj, p, 0);
+ ((MediaMetadata) metadataObj).writeToParcel(p, 0);
p.setDataPosition(0);
MediaMetadataCompat metadata = MediaMetadataCompat.CREATOR.createFromParcel(p);
p.recycle();
- metadata.mMetadataObj = metadataObj;
+ metadata.mMetadataFwk = (MediaMetadata) metadataObj;
return metadata;
} else {
return null;
@@ -633,14 +634,14 @@
* if none.
*/
public Object getMediaMetadata() {
- if (mMetadataObj == null && Build.VERSION.SDK_INT >= 21) {
+ if (mMetadataFwk == null && Build.VERSION.SDK_INT >= 21) {
Parcel p = Parcel.obtain();
writeToParcel(p, 0);
p.setDataPosition(0);
- mMetadataObj = MediaMetadataCompatApi21.createFromParcel(p);
+ mMetadataFwk = MediaMetadata.CREATOR.createFromParcel(p);
p.recycle();
}
- return mMetadataObj;
+ return mMetadataFwk;
}
public static final Parcelable.Creator<MediaMetadataCompat> CREATOR =
diff --git a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
index d6b5685..c4c95dd 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -22,9 +22,15 @@
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
+import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.Rating;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -171,12 +177,12 @@
new MediaControllerExtraData(mediaController));
}
if (android.os.Build.VERSION.SDK_INT >= 21) {
- Object controllerObj = null;
+ MediaController controllerFwk = null;
if (mediaController != null) {
Object sessionTokenObj = mediaController.getSessionToken().getToken();
- controllerObj = MediaControllerCompatApi21.fromToken(activity, sessionTokenObj);
+ controllerFwk = new MediaController(activity, (MediaSession.Token) sessionTokenObj);
}
- MediaControllerCompatApi21.setMediaController(activity, controllerObj);
+ activity.setMediaController(controllerFwk);
}
}
@@ -197,14 +203,14 @@
((ComponentActivity) activity).getExtraData(MediaControllerExtraData.class);
return extraData != null ? extraData.getMediaController() : null;
} else if (android.os.Build.VERSION.SDK_INT >= 21) {
- Object controllerObj = MediaControllerCompatApi21.getMediaController(activity);
- if (controllerObj == null) {
+ MediaController controllerFwk = activity.getMediaController();
+ if (controllerFwk == null) {
return null;
}
- Object sessionTokenObj = MediaControllerCompatApi21.getSessionToken(controllerObj);
+ MediaSession.Token sessionTokenFwk = controllerFwk.getSessionToken();
try {
return new MediaControllerCompat(activity,
- MediaSessionCompat.Token.fromToken(sessionTokenObj));
+ MediaSessionCompat.Token.fromToken(sessionTokenFwk));
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getMediaController.", e);
}
@@ -251,11 +257,7 @@
MediaControllerImpl impl = null;
try {
- if (android.os.Build.VERSION.SDK_INT >= 24) {
- impl = new MediaControllerImplApi24(context, mToken);
- } else if (android.os.Build.VERSION.SDK_INT >= 23) {
- impl = new MediaControllerImplApi23(context, mToken);
- } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
impl = new MediaControllerImplApi21(context, mToken);
} else {
impl = new MediaControllerImplBase(mToken);
@@ -280,11 +282,7 @@
}
mToken = sessionToken;
- if (android.os.Build.VERSION.SDK_INT >= 24) {
- mImpl = new MediaControllerImplApi24(context, sessionToken);
- } else if (android.os.Build.VERSION.SDK_INT >= 23) {
- mImpl = new MediaControllerImplApi23(context, sessionToken);
- } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaControllerImplApi21(context, sessionToken);
} else {
mImpl = new MediaControllerImplBase(sessionToken);
@@ -698,15 +696,16 @@
* registered using {@link #registerCallback}
*/
public static abstract class Callback implements IBinder.DeathRecipient {
- final Object mCallbackObj;
+ final MediaController.Callback mCallbackFwk;
MessageHandler mHandler;
IMediaControllerCallback mIControllerCallback;
public Callback() {
if (android.os.Build.VERSION.SDK_INT >= 21) {
- mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21(this));
+ mCallbackFwk = new MediaControllerCallbackApi21(this);
} else {
- mCallbackObj = mIControllerCallback = new StubCompat(this);
+ mCallbackFwk = null;
+ mIControllerCallback = new StubCompat(this);
}
}
@@ -860,10 +859,11 @@
}
// Callback methods in this class are run on handler which was given to registerCallback().
- private static class StubApi21 implements MediaControllerCompatApi21.Callback {
+ @RequiresApi(21)
+ private static class MediaControllerCallbackApi21 extends MediaController.Callback {
private final WeakReference<MediaControllerCompat.Callback> mCallback;
- StubApi21(MediaControllerCompat.Callback callback) {
+ MediaControllerCallbackApi21(MediaControllerCompat.Callback callback) {
mCallback = new WeakReference<>(callback);
}
@@ -877,6 +877,7 @@
@Override
public void onSessionEvent(String event, Bundle extras) {
+ MediaSessionCompat.ensureClassLoader(extras);
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
if (callback.mIControllerCallback != null
@@ -889,7 +890,7 @@
}
@Override
- public void onPlaybackStateChanged(Object stateObj) {
+ public void onPlaybackStateChanged(PlaybackState stateObj) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
if (callback.mIControllerCallback != null) {
@@ -902,7 +903,7 @@
}
@Override
- public void onMetadataChanged(Object metadataObj) {
+ public void onMetadataChanged(MediaMetadata metadataObj) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
@@ -910,7 +911,7 @@
}
@Override
- public void onQueueChanged(List<?> queue) {
+ public void onQueueChanged(List<MediaSession.QueueItem> queue) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.onQueueChanged(QueueItem.fromQueueItemList(queue));
@@ -927,6 +928,7 @@
@Override
public void onExtrasChanged(Bundle extras) {
+ MediaSessionCompat.ensureClassLoader(extras);
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.onExtrasChanged(extras);
@@ -934,12 +936,15 @@
}
@Override
- public void onAudioInfoChanged(
- int type, int stream, int control, int max, int current) {
+ public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
- callback.onAudioInfoChanged(
- new PlaybackInfo(type, stream, control, max, current));
+ callback.onAudioInfoChanged(new PlaybackInfo(
+ info.getPlaybackType(),
+ PlaybackInfo.toLegacyStreamType(info.getAudioAttributes()),
+ info.getVolumeControl(),
+ info.getMaxVolume(),
+ info.getCurrentVolume()));
}
}
}
@@ -1437,6 +1442,52 @@
public int getCurrentVolume() {
return mCurrentVolume;
}
+
+ // This is copied from AudioAttributes.toLegacyStreamType. TODO This
+ // either needs to be kept in sync with that one or toLegacyStreamType
+ // needs to be made public so it can be used by the support lib.
+ @RequiresApi(21)
+ static int toLegacyStreamType(AudioAttributes aa) {
+ final int flagSco = 0x1 << 2;
+ final int streamBluetoothSco = 6;
+ final int streamSystemEnforced = 7;
+ // flags to stream type mapping
+ if ((aa.getFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ == AudioAttributes.FLAG_AUDIBILITY_ENFORCED) {
+ return streamSystemEnforced;
+ }
+ if ((aa.getFlags() & flagSco) == flagSco) {
+ return streamBluetoothSco;
+ }
+
+ // usage to stream type mapping
+ switch (aa.getUsage()) {
+ case AudioAttributes.USAGE_MEDIA:
+ case AudioAttributes.USAGE_GAME:
+ case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+ case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ return AudioManager.STREAM_MUSIC;
+ case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+ return AudioManager.STREAM_SYSTEM;
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+ return AudioManager.STREAM_VOICE_CALL;
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ return AudioManager.STREAM_DTMF;
+ case AudioAttributes.USAGE_ALARM:
+ return AudioManager.STREAM_ALARM;
+ case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+ return AudioManager.STREAM_RING;
+ case AudioAttributes.USAGE_NOTIFICATION:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+ return AudioManager.STREAM_NOTIFICATION;
+ case AudioAttributes.USAGE_UNKNOWN:
+ default:
+ return AudioManager.STREAM_MUSIC;
+ }
+ }
}
interface MediaControllerImpl {
@@ -1486,7 +1537,7 @@
}
try {
mBinder.asBinder().linkToDeath(callback, 0);
- mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
+ mBinder.registerCallbackListener(callback.mIControllerCallback);
callback.postToHandler(Callback.MessageHandler.MSG_SESSION_READY, null, null);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerCallback.", e);
@@ -1500,8 +1551,7 @@
throw new IllegalArgumentException("callback may not be null.");
}
try {
- mBinder.unregisterCallbackListener(
- (IMediaControllerCallback) callback.mCallbackObj);
+ mBinder.unregisterCallbackListener(callback.mIControllerCallback);
mBinder.asBinder().unlinkToDeath(callback, 0);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterCallback.", e);
@@ -1958,7 +2008,7 @@
@RequiresApi(21)
static class MediaControllerImplApi21 implements MediaControllerImpl {
- protected final Object mControllerObj;
+ protected final MediaController mControllerFwk;
final Object mLock = new Object();
@@ -1972,9 +2022,9 @@
public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
throws RemoteException {
mSessionToken = sessionToken;
- mControllerObj = MediaControllerCompatApi21.fromToken(context,
- mSessionToken.getToken());
- if (mControllerObj == null) throw new RemoteException();
+ mControllerFwk = new MediaController(context,
+ (MediaSession.Token) mSessionToken.getToken());
+ if (mControllerFwk == null) throw new RemoteException();
if (mSessionToken.getExtraBinder() == null) {
requestExtraBinder();
}
@@ -1982,8 +2032,7 @@
@Override
public final void registerCallback(Callback callback, Handler handler) {
- MediaControllerCompatApi21.registerCallback(
- mControllerObj, callback.mCallbackObj, handler);
+ mControllerFwk.registerCallback(callback.mCallbackFwk, handler);
synchronized (mLock) {
if (mSessionToken.getExtraBinder() != null) {
ExtraCallback extraCallback = new ExtraCallback(callback);
@@ -2005,7 +2054,7 @@
@Override
public final void unregisterCallback(Callback callback) {
- MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
+ mControllerFwk.unregisterCallback(callback.mCallbackFwk);
synchronized (mLock) {
if (mSessionToken.getExtraBinder() != null) {
try {
@@ -2026,13 +2075,12 @@
@Override
public boolean dispatchMediaButtonEvent(KeyEvent event) {
- return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
+ return mControllerFwk.dispatchMediaButtonEvent(event);
}
@Override
public TransportControls getTransportControls() {
- Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
- return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
+ return new TransportControlsApi21(mControllerFwk.getTransportControls());
}
@Override
@@ -2044,20 +2092,20 @@
Log.e(TAG, "Dead object in getPlaybackState.", e);
}
}
- Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
- return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
+ PlaybackState stateFwk = mControllerFwk.getPlaybackState();
+ return stateFwk != null ? PlaybackStateCompat.fromPlaybackState(stateFwk) : null;
}
@Override
public MediaMetadataCompat getMetadata() {
- Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
- return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
+ MediaMetadata metadataFwk = mControllerFwk.getMetadata();
+ return metadataFwk != null ? MediaMetadataCompat.fromMediaMetadata(metadataFwk) : null;
}
@Override
public List<QueueItem> getQueue() {
- List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
- return queueObjs != null ? QueueItem.fromQueueItemList(queueObjs) : null;
+ List<MediaSession.QueueItem> queueFwks = mControllerFwk.getQueue();
+ return queueFwks != null ? QueueItem.fromQueueItemList(queueFwks) : null;
}
@Override
@@ -2099,12 +2147,12 @@
@Override
public CharSequence getQueueTitle() {
- return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
+ return mControllerFwk.getQueueTitle();
}
@Override
public Bundle getExtras() {
- return MediaControllerCompatApi21.getExtras(mControllerObj);
+ return mControllerFwk.getExtras();
}
@Override
@@ -2116,7 +2164,7 @@
Log.e(TAG, "Dead object in getRatingType.", e);
}
}
- return MediaControllerCompatApi21.getRatingType(mControllerObj);
+ return mControllerFwk.getRatingType();
}
@Override
@@ -2157,38 +2205,38 @@
@Override
public long getFlags() {
- return MediaControllerCompatApi21.getFlags(mControllerObj);
+ return mControllerFwk.getFlags();
}
@Override
public PlaybackInfo getPlaybackInfo() {
- Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
- return volumeInfoObj != null ? new PlaybackInfo(
- MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
- MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
- MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
- MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
- MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
+ MediaController.PlaybackInfo volumeInfoFwk = mControllerFwk.getPlaybackInfo();
+ return volumeInfoFwk != null ? new PlaybackInfo(
+ volumeInfoFwk.getPlaybackType(),
+ PlaybackInfo.toLegacyStreamType(volumeInfoFwk.getAudioAttributes()),
+ volumeInfoFwk.getVolumeControl(),
+ volumeInfoFwk.getMaxVolume(),
+ volumeInfoFwk.getCurrentVolume()) : null;
}
@Override
public PendingIntent getSessionActivity() {
- return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
+ return mControllerFwk.getSessionActivity();
}
@Override
public void setVolumeTo(int value, int flags) {
- MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
+ mControllerFwk.setVolumeTo(value, flags);
}
@Override
public void adjustVolume(int direction, int flags) {
- MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
+ mControllerFwk.adjustVolume(direction, flags);
}
@Override
public void sendCommand(String command, Bundle params, ResultReceiver cb) {
- MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
+ mControllerFwk.sendCommand(command, params, cb);
}
@Override
@@ -2198,12 +2246,12 @@
@Override
public String getPackageName() {
- return MediaControllerCompatApi21.getPackageName(mControllerObj);
+ return mControllerFwk.getPackageName();
}
@Override
public Object getMediaController() {
- return mControllerObj;
+ return mControllerFwk;
}
private void requestExtraBinder() {
@@ -2300,20 +2348,29 @@
}
}
+ @RequiresApi(21)
static class TransportControlsApi21 extends TransportControls {
- protected final Object mControlsObj;
+ protected final MediaController.TransportControls mControlsFwk;
- public TransportControlsApi21(Object controlsObj) {
- mControlsObj = controlsObj;
+ TransportControlsApi21(MediaController.TransportControls controlsFwk) {
+ mControlsFwk = controlsFwk;
}
@Override
public void prepare() {
+ if (Build.VERSION.SDK_INT >= 24) {
+ mControlsFwk.prepare();
+ return;
+ }
sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
}
@Override
public void prepareFromMediaId(String mediaId, Bundle extras) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ mControlsFwk.prepareFromMediaId(mediaId, extras);
+ return;
+ }
Bundle bundle = new Bundle();
bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
@@ -2322,6 +2379,10 @@
@Override
public void prepareFromSearch(String query, Bundle extras) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ mControlsFwk.prepareFromSearch(query, extras);
+ return;
+ }
Bundle bundle = new Bundle();
bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
@@ -2330,6 +2391,10 @@
@Override
public void prepareFromUri(Uri uri, Bundle extras) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ mControlsFwk.prepareFromUri(uri, extras);
+ return;
+ }
Bundle bundle = new Bundle();
bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
@@ -2338,48 +2403,47 @@
@Override
public void play() {
- MediaControllerCompatApi21.TransportControls.play(mControlsObj);
+ mControlsFwk.play();
}
@Override
public void pause() {
- MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
+ mControlsFwk.pause();
}
@Override
public void stop() {
- MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
+ mControlsFwk.stop();
}
@Override
public void seekTo(long pos) {
- MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
+ mControlsFwk.seekTo(pos);
}
@Override
public void fastForward() {
- MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
+ mControlsFwk.fastForward();
}
@Override
public void rewind() {
- MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
+ mControlsFwk.rewind();
}
@Override
public void skipToNext() {
- MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
+ mControlsFwk.skipToNext();
}
@Override
public void skipToPrevious() {
- MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
+ mControlsFwk.skipToPrevious();
}
@Override
public void setRating(RatingCompat rating) {
- MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
- rating != null ? rating.getRating() : null);
+ mControlsFwk.setRating(rating != null ? (Rating) rating.getRating() : null);
}
@Override
@@ -2413,18 +2477,20 @@
@Override
public void playFromMediaId(String mediaId, Bundle extras) {
- MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
- extras);
+ mControlsFwk.playFromMediaId(mediaId, extras);
}
@Override
public void playFromSearch(String query, Bundle extras) {
- MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
- extras);
+ mControlsFwk.playFromSearch(query, extras);
}
@Override
public void playFromUri(Uri uri, Bundle extras) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ mControlsFwk.playFromUri(uri, extras);
+ return;
+ }
if (uri == null || Uri.EMPTY.equals(uri)) {
throw new IllegalArgumentException(
"You must specify a non-empty Uri for playFromUri.");
@@ -2437,95 +2503,19 @@
@Override
public void skipToQueueItem(long id) {
- MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
+ mControlsFwk.skipToQueueItem(id);
}
@Override
public void sendCustomAction(CustomAction customAction, Bundle args) {
validateCustomAction(customAction.getAction(), args);
- MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
- customAction.getAction(), args);
+ mControlsFwk.sendCustomAction(customAction.getAction(), args);
}
@Override
public void sendCustomAction(String action, Bundle args) {
validateCustomAction(action, args);
- MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
- args);
- }
- }
-
- @RequiresApi(23)
- static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
-
- public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
- throws RemoteException {
- super(context, sessionToken);
- }
-
- @Override
- public TransportControls getTransportControls() {
- Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
- return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
- }
- }
-
- @RequiresApi(23)
- static class TransportControlsApi23 extends TransportControlsApi21 {
-
- public TransportControlsApi23(Object controlsObj) {
- super(controlsObj);
- }
-
- @Override
- public void playFromUri(Uri uri, Bundle extras) {
- MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
- extras);
- }
- }
-
- @RequiresApi(24)
- static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
-
- public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
- throws RemoteException {
- super(context, sessionToken);
- }
-
- @Override
- public TransportControls getTransportControls() {
- Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
- return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
- }
- }
-
- @RequiresApi(24)
- static class TransportControlsApi24 extends TransportControlsApi23 {
-
- public TransportControlsApi24(Object controlsObj) {
- super(controlsObj);
- }
-
- @Override
- public void prepare() {
- MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
- }
-
- @Override
- public void prepareFromMediaId(String mediaId, Bundle extras) {
- MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
- mControlsObj, mediaId, extras);
- }
-
- @Override
- public void prepareFromSearch(String query, Bundle extras) {
- MediaControllerCompatApi24.TransportControls.prepareFromSearch(
- mControlsObj, query, extras);
- }
-
- @Override
- public void prepareFromUri(Uri uri, Bundle extras) {
- MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
+ mControlsFwk.sendCustomAction(action, args);
}
}
}
diff --git a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index 957d0ca..1e063d6 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -22,6 +22,7 @@
import static androidx.media.MediaSessionManager.RemoteUserInfo.UNKNOWN_PID;
import static androidx.media.MediaSessionManager.RemoteUserInfo.UNKNOWN_UID;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -29,12 +30,17 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.media.Rating;
import android.media.RemoteControlClient;
+import android.media.VolumeProvider;
import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.BadParcelableException;
import android.os.Binder;
@@ -76,6 +82,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -529,11 +536,6 @@
private MediaSessionCompat(Context context, MediaSessionImpl impl) {
mImpl = impl;
- if (android.os.Build.VERSION.SDK_INT >= 21
- && !MediaSessionCompatApi21.hasCallback(impl.getMediaSession())) {
- // Set default callback to respond to controllers' extra binder requests.
- setCallback(new Callback() {});
- }
mController = new MediaControllerCompat(context, this);
}
@@ -929,7 +931,13 @@
* Creates an instance from a framework {@link android.media.session.MediaSession} object.
* <p>
* This method is only supported on API 21+. On API 20 and below, it returns null.
- * </p>
+ * <p>
+ * Note: A {@link MediaSessionCompat} object returned from this method may not provide the full
+ * functionality of {@link MediaSessionCompat} until setting a new
+ * {@link MediaSessionCompat.Callback}. To avoid this, when both a {@link MediaSessionCompat}
+ * and a framework {@link android.media.session.MediaSession} are needed, it is recommended
+ * to create a {@link MediaSessionCompat} first and get the framework session through
+ * {@link #getMediaSession()}.
*
* @param context The context to use to create the session.
* @param mediaSession A {@link android.media.session.MediaSession} object.
@@ -1000,20 +1008,16 @@
* and the system. The callback may be set using {@link #setCallback}.
*/
public abstract static class Callback {
- final Object mCallbackObj;
+ final MediaSession.Callback mCallbackFwk;
WeakReference<MediaSessionImpl> mSessionImpl;
private CallbackHandler mCallbackHandler = null;
private boolean mMediaPlayPauseKeyPending;
public Callback() {
- if (android.os.Build.VERSION.SDK_INT >= 24) {
- mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24());
- } else if (android.os.Build.VERSION.SDK_INT >= 23) {
- mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23());
- } else if (android.os.Build.VERSION.SDK_INT >= 21) {
- mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ mCallbackFwk = new MediaSessionCallbackApi21();
} else {
- mCallbackObj = null;
+ mCallbackFwk = null;
}
}
@@ -1378,13 +1382,14 @@
}
@RequiresApi(21)
- private class StubApi21 implements MediaSessionCompatApi21.Callback {
+ private class MediaSessionCallbackApi21 extends MediaSession.Callback {
- StubApi21() {
+ MediaSessionCallbackApi21() {
}
@Override
public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+ ensureClassLoader(extras);
setCurrentControllerInfo();
try {
if (command.equals(MediaControllerCompat.COMMAND_GET_EXTRA_BINDER)) {
@@ -1439,7 +1444,7 @@
setCurrentControllerInfo();
boolean result = Callback.this.onMediaButtonEvent(mediaButtonIntent);
clearCurrentControllerInfo();
- return result;
+ return result || super.onMediaButtonEvent(mediaButtonIntent);
}
@Override
@@ -1451,6 +1456,7 @@
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ ensureClassLoader(extras);
setCurrentControllerInfo();
Callback.this.onPlayFromMediaId(mediaId, extras);
clearCurrentControllerInfo();
@@ -1458,11 +1464,21 @@
@Override
public void onPlayFromSearch(String search, Bundle extras) {
+ ensureClassLoader(extras);
setCurrentControllerInfo();
Callback.this.onPlayFromSearch(search, extras);
clearCurrentControllerInfo();
}
+ @RequiresApi(23)
+ @Override
+ public void onPlayFromUri(Uri uri, Bundle extras) {
+ ensureClassLoader(extras);
+ setCurrentControllerInfo();
+ Callback.this.onPlayFromUri(uri, extras);
+ clearCurrentControllerInfo();
+ }
+
@Override
public void onSkipToQueueItem(long id) {
setCurrentControllerInfo();
@@ -1520,19 +1536,19 @@
}
@Override
- public void onSetRating(Object ratingObj) {
+ public void onSetRating(Rating ratingFwk) {
setCurrentControllerInfo();
- Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
+ Callback.this.onSetRating(RatingCompat.fromRating(ratingFwk));
clearCurrentControllerInfo();
}
- @Override
- public void onSetRating(Object ratingObj, Bundle extras) {
+ public void onSetRating(Rating ratingFwk, Bundle extras) {
// This method will not be called.
}
@Override
public void onCustomAction(String action, Bundle extras) {
+ ensureClassLoader(extras);
setCurrentControllerInfo();
Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
ensureClassLoader(bundle);
@@ -1569,13 +1585,23 @@
clearCurrentControllerInfo();
}
- // Since we can't get ControllerInfo in API 21-23, just provide a default (unknown) one.
void setCurrentControllerInfo() {
- MediaSessionImpl sessionImpl = mSessionImpl != null ? mSessionImpl.get() : null;
- if (sessionImpl != null) {
- sessionImpl.setCurrentControllerInfo(
- new RemoteUserInfo(LEGACY_CONTROLLER, UNKNOWN_PID, UNKNOWN_UID));
+ if (Build.VERSION.SDK_INT >= 28) {
+ // From API 28, this method has no effect since
+ // MediaSessionImplApi28#getCurrentControllerInfo() returns controller info from
+ // framework.
+ return;
}
+ MediaSessionImpl sessionImpl = mSessionImpl != null ? mSessionImpl.get() : null;
+ if (sessionImpl == null) {
+ return;
+ }
+ String packageName = sessionImpl.getCallingPackage();
+ if (TextUtils.isEmpty(packageName)) {
+ packageName = LEGACY_CONTROLLER;
+ }
+ sessionImpl.setCurrentControllerInfo(new RemoteUserInfo(
+ packageName, UNKNOWN_PID, UNKNOWN_UID));
}
void clearCurrentControllerInfo() {
@@ -1584,28 +1610,8 @@
sessionImpl.setCurrentControllerInfo(null);
}
}
- }
- @RequiresApi(23)
- private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback {
-
- StubApi23() {
- }
-
- @Override
- public void onPlayFromUri(Uri uri, Bundle extras) {
- setCurrentControllerInfo();
- Callback.this.onPlayFromUri(uri, extras);
- clearCurrentControllerInfo();
- }
- }
-
- @RequiresApi(24)
- private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
-
- StubApi24() {
- }
-
+ @RequiresApi(24)
@Override
public void onPrepare() {
setCurrentControllerInfo();
@@ -1613,45 +1619,32 @@
clearCurrentControllerInfo();
}
+ @RequiresApi(24)
@Override
public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ ensureClassLoader(extras);
setCurrentControllerInfo();
Callback.this.onPrepareFromMediaId(mediaId, extras);
clearCurrentControllerInfo();
}
+ @RequiresApi(24)
@Override
public void onPrepareFromSearch(String query, Bundle extras) {
+ ensureClassLoader(extras);
setCurrentControllerInfo();
Callback.this.onPrepareFromSearch(query, extras);
clearCurrentControllerInfo();
}
+ @RequiresApi(24)
@Override
public void onPrepareFromUri(Uri uri, Bundle extras) {
+ ensureClassLoader(extras);
setCurrentControllerInfo();
Callback.this.onPrepareFromUri(uri, extras);
clearCurrentControllerInfo();
}
-
- // Note: This is only for on API 24~27. From API 28, this method has no effect since
- // MediaSessionImplApi28#getCurrentControllerInfo() returns controller info from
- // framework.
- @Override
- void setCurrentControllerInfo() {
- if (Build.VERSION.SDK_INT >= 28) {
- return;
- }
- MediaSessionImpl sessionImpl = mSessionImpl != null ? mSessionImpl.get() : null;
- if (sessionImpl != null) {
- String packageName = sessionImpl.getCallingPackage();
- if (TextUtils.isEmpty(packageName)) {
- packageName = LEGACY_CONTROLLER;
- }
- sessionImpl.setCurrentControllerInfo(
- new RemoteUserInfo(packageName, UNKNOWN_PID, UNKNOWN_UID));
- }
- }
}
}
@@ -1711,7 +1704,11 @@
@RestrictTo(LIBRARY_GROUP)
public static Token fromToken(Object token, IMediaSession extraBinder) {
if (token != null && android.os.Build.VERSION.SDK_INT >= 21) {
- return new Token(MediaSessionCompatApi21.verifyToken(token), extraBinder);
+ if (!(token instanceof MediaSession.Token)) {
+ throw new IllegalArgumentException(
+ "token is not a valid MediaSession.Token object");
+ }
+ return new Token(token, extraBinder);
}
return null;
}
@@ -1873,7 +1870,7 @@
private final MediaDescriptionCompat mDescription;
private final long mId;
- private Object mItem;
+ private MediaSession.QueueItem mItemFwk;
/**
* Creates a new {@link MediaSessionCompat.QueueItem}.
@@ -1886,7 +1883,10 @@
this(null, description, id);
}
- private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
+ private QueueItem(
+ MediaSession.QueueItem queueItem,
+ MediaDescriptionCompat description,
+ long id) {
if (description == null) {
throw new IllegalArgumentException("Description cannot be null.");
}
@@ -1895,7 +1895,7 @@
}
mDescription = description;
mId = id;
- mItem = queueItem;
+ mItemFwk = queueItem;
}
QueueItem(Parcel in) {
@@ -1939,12 +1939,13 @@
* {@link android.media.session.MediaSession.QueueItem} or null.
*/
public Object getQueueItem() {
- if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
- return mItem;
+ if (mItemFwk != null || android.os.Build.VERSION.SDK_INT < 21) {
+ return mItemFwk;
}
- mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
+ mItemFwk = new MediaSession.QueueItem(
+ (MediaDescription) mDescription.getMediaDescription(),
mId);
- return mItem;
+ return mItemFwk;
}
/**
@@ -1961,11 +1962,12 @@
if (queueItem == null || Build.VERSION.SDK_INT < 21) {
return null;
}
- Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
+ MediaSession.QueueItem queueItemObj = (MediaSession.QueueItem) queueItem;
+ Object descriptionObj = queueItemObj.getDescription();
MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
descriptionObj);
- long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
- return new QueueItem(queueItem, description, id);
+ long id = queueItemObj.getQueueId();
+ return new QueueItem(queueItemObj, description, id);
}
/**
@@ -3461,7 +3463,7 @@
@RequiresApi(21)
static class MediaSessionImplApi21 implements MediaSessionImpl {
- final Object mSessionObj;
+ final MediaSession mSessionFwk;
final Token mToken;
final Object mLock = new Object();
@@ -3481,56 +3483,60 @@
RemoteUserInfo mRemoteUserInfo;
MediaSessionImplApi21(Context context, String tag, VersionedParcelable token2) {
- mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
- mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj),
- new ExtraSession(), token2);
+ mSessionFwk = new MediaSession(context, tag);
+ mToken = new Token(mSessionFwk.getSessionToken(), new ExtraSession(), token2);
// For backward compatibility, these flags are always set.
setFlags(FLAG_HANDLES_MEDIA_BUTTONS | FLAG_HANDLES_TRANSPORT_CONTROLS);
}
MediaSessionImplApi21(Object mediaSession) {
- mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
- mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj),
- new ExtraSession());
+ if (!(mediaSession instanceof MediaSession)) {
+ throw new IllegalArgumentException(
+ "mediaSession is not a valid MediaSession object");
+ }
+ mSessionFwk = (MediaSession) mediaSession;
+ mToken = new Token(mSessionFwk.getSessionToken(), new ExtraSession());
// For backward compatibility, these flags are always set.
setFlags(FLAG_HANDLES_MEDIA_BUTTONS | FLAG_HANDLES_TRANSPORT_CONTROLS);
}
@Override
public void setCallback(Callback callback, Handler handler) {
- MediaSessionCompatApi21.setCallback(mSessionObj,
- callback == null ? null : callback.mCallbackObj, handler);
+ mSessionFwk.setCallback(callback == null ? null : callback.mCallbackFwk, handler);
if (callback != null) {
callback.setSessionImpl(this, handler);
}
}
+ @SuppressLint("WrongConstant")
@Override
public void setFlags(@SessionFlags int flags) {
// For backward compatibility, always set these deprecated flags.
- MediaSessionCompatApi21.setFlags(mSessionObj,
+ mSessionFwk.setFlags(
flags | FLAG_HANDLES_MEDIA_BUTTONS | FLAG_HANDLES_TRANSPORT_CONTROLS);
}
@Override
public void setPlaybackToLocal(int stream) {
- MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
+ // TODO update APIs to use support version of AudioAttributes
+ AudioAttributes.Builder bob = new AudioAttributes.Builder();
+ bob.setLegacyStreamType(stream);
+ mSessionFwk.setPlaybackToLocal(bob.build());
}
@Override
public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
- MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
- volumeProvider.getVolumeProvider());
+ mSessionFwk.setPlaybackToRemote((VolumeProvider) volumeProvider.getVolumeProvider());
}
@Override
public void setActive(boolean active) {
- MediaSessionCompatApi21.setActive(mSessionObj, active);
+ mSessionFwk.setActive(active);
}
@Override
public boolean isActive() {
- return MediaSessionCompatApi21.isActive(mSessionObj);
+ return mSessionFwk.isActive();
}
@Override
@@ -3546,13 +3552,13 @@
}
mExtraControllerCallbacks.finishBroadcast();
}
- MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
+ mSessionFwk.sendSessionEvent(event, extras);
}
@Override
public void release() {
mDestroyed = true;
- MediaSessionCompatApi21.release(mSessionObj);
+ mSessionFwk.release();
}
@Override
@@ -3572,8 +3578,8 @@
}
}
mExtraControllerCallbacks.finishBroadcast();
- MediaSessionCompatApi21.setPlaybackState(mSessionObj,
- state == null ? null : state.getPlaybackState());
+ mSessionFwk.setPlaybackState(
+ state == null ? null : (PlaybackState) state.getPlaybackState());
}
@Override
@@ -3584,36 +3590,37 @@
@Override
public void setMetadata(MediaMetadataCompat metadata) {
mMetadata = metadata;
- MediaSessionCompatApi21.setMetadata(mSessionObj,
- metadata == null ? null : metadata.getMediaMetadata());
+ mSessionFwk.setMetadata(
+ metadata == null ? null : (MediaMetadata) metadata.getMediaMetadata());
}
@Override
public void setSessionActivity(PendingIntent pi) {
- MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
+ mSessionFwk.setSessionActivity(pi);
}
@Override
public void setMediaButtonReceiver(PendingIntent mbr) {
- MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
+ mSessionFwk.setMediaButtonReceiver(mbr);
}
@Override
public void setQueue(List<QueueItem> queue) {
mQueue = queue;
- List<Object> queueObjs = null;
- if (queue != null) {
- queueObjs = new ArrayList<>();
- for (QueueItem item : queue) {
- queueObjs.add(item.getQueueItem());
- }
+ if (queue == null) {
+ mSessionFwk.setQueue(null);
+ return;
}
- MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
+ ArrayList<MediaSession.QueueItem> queueItemFwks = new ArrayList<>();
+ for (QueueItem item : queue) {
+ queueItemFwks.add((MediaSession.QueueItem) item.getQueueItem());
+ }
+ mSessionFwk.setQueue(queueItemFwks);
}
@Override
public void setQueueTitle(CharSequence title) {
- MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
+ mSessionFwk.setQueueTitle(title);
}
@Override
@@ -3621,7 +3628,7 @@
if (android.os.Build.VERSION.SDK_INT < 22) {
mRatingType = type;
} else {
- MediaSessionCompatApi22.setRatingType(mSessionObj, type);
+ mSessionFwk.setRatingType(type);
}
}
@@ -3675,12 +3682,12 @@
@Override
public void setExtras(Bundle extras) {
- MediaSessionCompatApi21.setExtras(mSessionObj, extras);
+ mSessionFwk.setExtras(extras);
}
@Override
public Object getMediaSession() {
- return mSessionObj;
+ return mSessionFwk;
}
@Override
@@ -3702,7 +3709,14 @@
if (android.os.Build.VERSION.SDK_INT < 24) {
return null;
} else {
- return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
+ try {
+ Method getCallingPackageMethod = mSessionFwk.getClass().getMethod(
+ "getCallingPackage");
+ return (String) getCallingPackageMethod.invoke(mSessionFwk);
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot execute MediaSession.getCallingPackage()", e);
+ }
+ return null;
}
}
@@ -4027,7 +4041,7 @@
@Override
public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
android.media.session.MediaSessionManager.RemoteUserInfo info =
- ((MediaSession) mSessionObj).getCurrentControllerInfo();
+ ((MediaSession) mSessionFwk).getCurrentControllerInfo();
return new RemoteUserInfo(info);
}
}
diff --git a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
index bed04f3..8911fd7 100644
--- a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -17,6 +17,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.media.session.PlaybackState;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -549,7 +550,7 @@
final long mActiveItemId;
final Bundle mExtras;
- private Object mStateObj;
+ private PlaybackState mStateFwk;
PlaybackStateCompat(int state, long position, long bufferedPosition,
float rate, long actions, int errorCode, CharSequence errorMessage, long updateTime,
@@ -797,34 +798,35 @@
*/
public static PlaybackStateCompat fromPlaybackState(Object stateObj) {
if (stateObj != null && Build.VERSION.SDK_INT >= 21) {
- List<Object> customActionObjs = PlaybackStateCompatApi21.getCustomActions(stateObj);
+ PlaybackState stateFwk = (PlaybackState) stateObj;
+ List<PlaybackState.CustomAction> customActionFwks = stateFwk.getCustomActions();
List<PlaybackStateCompat.CustomAction> customActions = null;
- if (customActionObjs != null) {
- customActions = new ArrayList<>(customActionObjs.size());
- for (Object customActionObj : customActionObjs) {
- customActions.add(CustomAction.fromCustomAction(customActionObj));
+ if (customActionFwks != null) {
+ customActions = new ArrayList<>(customActionFwks.size());
+ for (Object customActionFwk : customActionFwks) {
+ customActions.add(CustomAction.fromCustomAction(customActionFwk));
}
}
Bundle extras;
if (Build.VERSION.SDK_INT >= 22) {
- extras = PlaybackStateCompatApi22.getExtras(stateObj);
+ extras = stateFwk.getExtras();
} else {
extras = null;
}
- PlaybackStateCompat state = new PlaybackStateCompat(
- PlaybackStateCompatApi21.getState(stateObj),
- PlaybackStateCompatApi21.getPosition(stateObj),
- PlaybackStateCompatApi21.getBufferedPosition(stateObj),
- PlaybackStateCompatApi21.getPlaybackSpeed(stateObj),
- PlaybackStateCompatApi21.getActions(stateObj),
+ PlaybackStateCompat stateCompat = new PlaybackStateCompat(
+ stateFwk.getState(),
+ stateFwk.getPosition(),
+ stateFwk.getBufferedPosition(),
+ stateFwk.getPlaybackSpeed(),
+ stateFwk.getActions(),
ERROR_CODE_UNKNOWN_ERROR,
- PlaybackStateCompatApi21.getErrorMessage(stateObj),
- PlaybackStateCompatApi21.getLastPositionUpdateTime(stateObj),
+ stateFwk.getErrorMessage(),
+ stateFwk.getLastPositionUpdateTime(),
customActions,
- PlaybackStateCompatApi21.getActiveQueueItemId(stateObj),
+ stateFwk.getActiveQueueItemId(),
extras);
- state.mStateObj = stateObj;
- return state;
+ stateCompat.mStateFwk = stateFwk;
+ return stateCompat;
} else {
return null;
}
@@ -839,27 +841,23 @@
* @return An equivalent {@link android.media.session.PlaybackState} object, or null if none.
*/
public Object getPlaybackState() {
- if (mStateObj == null && Build.VERSION.SDK_INT >= 21) {
- List<Object> customActions = null;
- if (mCustomActions != null) {
- customActions = new ArrayList<>(mCustomActions.size());
- for (PlaybackStateCompat.CustomAction customAction : mCustomActions) {
- customActions.add(customAction.getCustomAction());
- }
+ if (mStateFwk == null && Build.VERSION.SDK_INT >= 21) {
+ PlaybackState.Builder builder = new PlaybackState.Builder();
+ builder.setState(mState, mPosition, mSpeed, mUpdateTime);
+ builder.setBufferedPosition(mBufferedPosition);
+ builder.setActions(mActions);
+ builder.setErrorMessage(mErrorMessage);
+ for (PlaybackStateCompat.CustomAction customAction : mCustomActions) {
+ builder.addCustomAction(
+ (PlaybackState.CustomAction) customAction.getCustomAction());
}
+ builder.setActiveQueueItemId(mActiveItemId);
if (Build.VERSION.SDK_INT >= 22) {
- mStateObj = PlaybackStateCompatApi22.newInstance(mState, mPosition,
- mBufferedPosition,
- mSpeed, mActions, mErrorMessage, mUpdateTime,
- customActions, mActiveItemId, mExtras);
- } else {
- //noinspection AndroidLintNewApi - NewApi lint fails to handle nested checks.
- mStateObj = PlaybackStateCompatApi21.newInstance(mState, mPosition,
- mBufferedPosition, mSpeed, mActions, mErrorMessage, mUpdateTime,
- customActions, mActiveItemId);
+ builder.setExtras(mExtras);
}
+ mStateFwk = builder.build();
}
- return mStateObj;
+ return mStateFwk;
}
public static final Parcelable.Creator<PlaybackStateCompat> CREATOR =
@@ -887,7 +885,7 @@
private final int mIcon;
private final Bundle mExtras;
- private Object mCustomActionObj;
+ private PlaybackState.CustomAction mCustomActionFwk;
/**
* Use {@link PlaybackStateCompat.CustomAction.Builder#build()}.
@@ -935,13 +933,16 @@
return null;
}
- PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction(
- PlaybackStateCompatApi21.CustomAction.getAction(customActionObj),
- PlaybackStateCompatApi21.CustomAction.getName(customActionObj),
- PlaybackStateCompatApi21.CustomAction.getIcon(customActionObj),
- PlaybackStateCompatApi21.CustomAction.getExtras(customActionObj));
- customAction.mCustomActionObj = customActionObj;
- return customAction;
+ PlaybackState.CustomAction customActionFwk =
+ (PlaybackState.CustomAction) customActionObj;
+ PlaybackStateCompat.CustomAction customActionCompat =
+ new PlaybackStateCompat.CustomAction(
+ customActionFwk.getAction(),
+ customActionFwk.getName(),
+ customActionFwk.getIcon(),
+ customActionFwk.getExtras());
+ customActionCompat.mCustomActionFwk = customActionFwk;
+ return customActionCompat;
}
/**
@@ -955,13 +956,14 @@
* or null if none.
*/
public Object getCustomAction() {
- if (mCustomActionObj != null || Build.VERSION.SDK_INT < 21) {
- return mCustomActionObj;
+ if (mCustomActionFwk != null || Build.VERSION.SDK_INT < 21) {
+ return mCustomActionFwk;
}
- mCustomActionObj = PlaybackStateCompatApi21.CustomAction.newInstance(mAction,
- mName, mIcon, mExtras);
- return mCustomActionObj;
+ PlaybackState.CustomAction.Builder builder = new PlaybackState.CustomAction.Builder(
+ mAction, mName, mIcon);
+ builder.setExtras(mExtras);
+ return builder.build();
}
public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR
diff --git a/media/api21/androidx/media/AudioAttributesImplApi21.java b/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
similarity index 98%
rename from media/api21/androidx/media/AudioAttributesImplApi21.java
rename to media/src/main/java/androidx/media/AudioAttributesImplApi21.java
index 6c67cc4..4df1234 100644
--- a/media/api21/androidx/media/AudioAttributesImplApi21.java
+++ b/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
@@ -171,6 +171,9 @@
//////////////////////////////////////////////////////////////////////
// Other public methods
+ /**
+ * Create AudioAttributesImpl from Bundle
+ */
public static AudioAttributesImpl fromBundle(Bundle bundle) {
if (bundle == null) {
return null;
diff --git a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
index 78b9394..6341950 100644
--- a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
+++ b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
@@ -55,6 +55,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaSession;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -293,22 +295,20 @@
}
@RequiresApi(21)
- class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl,
- MediaBrowserServiceCompatApi21.ServiceCompatProxy {
+ class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl {
final List<Bundle> mRootExtrasList = new ArrayList<>();
- Object mServiceObj;
+ MediaBrowserService mServiceFwk;
Messenger mMessenger;
@Override
public void onCreate() {
- mServiceObj = MediaBrowserServiceCompatApi21.createService(
- MediaBrowserServiceCompat.this, this);
- MediaBrowserServiceCompatApi21.onCreate(mServiceObj);
+ mServiceFwk = new MediaBrowserServiceApi21(MediaBrowserServiceCompat.this);
+ mServiceFwk.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
- return MediaBrowserServiceCompatApi21.onBind(mServiceObj, intent);
+ return mServiceFwk.onBind(intent);
}
@Override
@@ -326,7 +326,7 @@
}
mRootExtrasList.clear();
}
- MediaBrowserServiceCompatApi21.setSessionToken(mServiceObj, token.getToken());
+ mServiceFwk.setSessionToken((MediaSession.Token) token.getToken());
}
});
}
@@ -344,8 +344,7 @@
notifyChildrenChangedForCompat(remoteUserInfo, parentId, options);
}
- @Override
- public MediaBrowserServiceCompatApi21.BrowserRoot onGetRoot(
+ public BrowserRoot onGetRoot(
String clientPackageName, int clientUid, Bundle rootHints) {
Bundle rootExtras = null;
if (rootHints != null && rootHints.getInt(EXTRA_CLIENT_VERSION, 0) != 0) {
@@ -377,39 +376,37 @@
} else if (root.getExtras() != null) {
rootExtras.putAll(root.getExtras());
}
- return new MediaBrowserServiceCompatApi21.BrowserRoot(
- root.getRootId(), rootExtras);
+ return new BrowserRoot(root.getRootId(), rootExtras);
}
- @Override
public void onLoadChildren(String parentId,
- final MediaBrowserServiceCompatApi21.ResultWrapper<List<Parcel>> resultWrapper) {
- final Result<List<MediaBrowserCompat.MediaItem>> result
- = new Result<List<MediaBrowserCompat.MediaItem>>(parentId) {
- @Override
- void onResultSent(List<MediaBrowserCompat.MediaItem> list) {
- List<Parcel> parcelList = null;
- if (list != null) {
- parcelList = new ArrayList<>();
- for (MediaBrowserCompat.MediaItem item : list) {
- Parcel parcel = Parcel.obtain();
- item.writeToParcel(parcel, 0);
- parcelList.add(parcel);
+ final ResultWrapper<List<Parcel>> resultWrapper) {
+ final Result<List<MediaBrowserCompat.MediaItem>> result =
+ new Result<List<MediaBrowserCompat.MediaItem>>(parentId) {
+ @Override
+ void onResultSent(List<MediaBrowserCompat.MediaItem> list) {
+ List<Parcel> parcelList = null;
+ if (list != null) {
+ parcelList = new ArrayList<>();
+ for (MediaBrowserCompat.MediaItem item : list) {
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcelList.add(parcel);
+ }
+ }
+ resultWrapper.sendResult(parcelList);
}
- }
- resultWrapper.sendResult(parcelList);
- }
- @Override
- public void detach() {
- resultWrapper.detach();
- }
- };
+ @Override
+ public void detach() {
+ resultWrapper.detach();
+ }
+ };
MediaBrowserServiceCompat.this.onLoadChildren(parentId, result);
}
void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ mServiceFwk.notifyChildrenChanged(parentId);
}
void notifyChildrenChangedForCompat(final String parentId, final Bundle options) {
@@ -473,85 +470,114 @@
}
return mCurConnection.browserInfo;
}
+
+ class MediaBrowserServiceApi21 extends MediaBrowserService {
+ MediaBrowserServiceApi21(Context context) {
+ attachBaseContext(context);
+ }
+
+ @Override
+ public MediaBrowserService.BrowserRoot onGetRoot(String clientPackageName,
+ int clientUid, Bundle rootHints) {
+ MediaSessionCompat.ensureClassLoader(rootHints);
+ MediaBrowserServiceCompat.BrowserRoot browserRootCompat =
+ MediaBrowserServiceImplApi21.this.onGetRoot(clientPackageName, clientUid,
+ rootHints == null ? null : new Bundle(rootHints));
+ return browserRootCompat == null ? null : new MediaBrowserService.BrowserRoot(
+ browserRootCompat.mRootId, browserRootCompat.mExtras);
+ }
+
+ @Override
+ public void onLoadChildren(String parentId,
+ Result<List<MediaBrowser.MediaItem>> result) {
+ MediaBrowserServiceImplApi21.this.onLoadChildren(parentId,
+ new ResultWrapper<List<Parcel>>(result));
+ }
+ }
}
@RequiresApi(23)
- class MediaBrowserServiceImplApi23 extends MediaBrowserServiceImplApi21 implements
- MediaBrowserServiceCompatApi23.ServiceCompatProxy {
+ class MediaBrowserServiceImplApi23 extends MediaBrowserServiceImplApi21 {
@Override
public void onCreate() {
- mServiceObj = MediaBrowserServiceCompatApi23.createService(
- MediaBrowserServiceCompat.this, this);
- MediaBrowserServiceCompatApi21.onCreate(mServiceObj);
+ mServiceFwk = new MediaBrowserServiceApi23(MediaBrowserServiceCompat.this);
+ mServiceFwk.onCreate();
}
- @Override
- public void onLoadItem(String itemId,
- final MediaBrowserServiceCompatApi21.ResultWrapper<Parcel> resultWrapper) {
- final Result<MediaBrowserCompat.MediaItem> result
- = new Result<MediaBrowserCompat.MediaItem>(itemId) {
- @Override
- void onResultSent(MediaBrowserCompat.MediaItem item) {
- if (item == null) {
- resultWrapper.sendResult(null);
- } else {
- Parcel parcelItem = Parcel.obtain();
- item.writeToParcel(parcelItem, 0);
- resultWrapper.sendResult(parcelItem);
- }
- }
+ public void onLoadItem(String itemId, final ResultWrapper<Parcel> resultWrapper) {
+ final Result<MediaBrowserCompat.MediaItem> result =
+ new Result<MediaBrowserCompat.MediaItem>(itemId) {
+ @Override
+ void onResultSent(MediaBrowserCompat.MediaItem item) {
+ if (item == null) {
+ resultWrapper.sendResult(null);
+ } else {
+ Parcel parcelItem = Parcel.obtain();
+ item.writeToParcel(parcelItem, 0);
+ resultWrapper.sendResult(parcelItem);
+ }
+ }
- @Override
- public void detach() {
- resultWrapper.detach();
- }
- };
+ @Override
+ public void detach() {
+ resultWrapper.detach();
+ }
+ };
MediaBrowserServiceCompat.this.onLoadItem(itemId, result);
}
+
+ class MediaBrowserServiceApi23 extends MediaBrowserServiceApi21 {
+ MediaBrowserServiceApi23(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
+ MediaBrowserServiceImplApi23.this.onLoadItem(itemId,
+ new ResultWrapper<Parcel>(result));
+ }
+ }
}
@RequiresApi(26)
- class MediaBrowserServiceImplApi26 extends MediaBrowserServiceImplApi23 implements
- MediaBrowserServiceCompatApi26.ServiceCompatProxy {
+ class MediaBrowserServiceImplApi26 extends MediaBrowserServiceImplApi23 {
@Override
public void onCreate() {
- mServiceObj = MediaBrowserServiceCompatApi26.createService(
- MediaBrowserServiceCompat.this, this);
- MediaBrowserServiceCompatApi21.onCreate(mServiceObj);
+ mServiceFwk = new MediaBrowserServiceApi26(MediaBrowserServiceCompat.this);
+ mServiceFwk.onCreate();
}
- @Override
public void onLoadChildren(String parentId,
- final MediaBrowserServiceCompatApi26.ResultWrapper resultWrapper,
+ final ResultWrapper<List<Parcel>> resultWrapper,
final Bundle options) {
- final Result<List<MediaBrowserCompat.MediaItem>> result
- = new Result<List<MediaBrowserCompat.MediaItem>>(parentId) {
- @Override
- void onResultSent(List<MediaBrowserCompat.MediaItem> list) {
- if (list == null) {
- resultWrapper.sendResult(null);
- return;
- }
- if ((getFlags() & RESULT_FLAG_OPTION_NOT_HANDLED) != 0) {
- // If onLoadChildren(options) is not overridden, the list we get here is not
- // paginated. Therefore, we need to manually cut the list. In other words,
- // we need to apply options here.
- list = applyOptions(list, options);
- }
- List<Parcel> parcelList = new ArrayList<>();
- for (MediaBrowserCompat.MediaItem item : list) {
- Parcel parcel = Parcel.obtain();
- item.writeToParcel(parcel, 0);
- parcelList.add(parcel);
- }
- resultWrapper.sendResult(parcelList);
- }
+ final Result<List<MediaBrowserCompat.MediaItem>> result =
+ new Result<List<MediaBrowserCompat.MediaItem>>(parentId) {
+ @Override
+ void onResultSent(List<MediaBrowserCompat.MediaItem> list) {
+ if (list == null) {
+ resultWrapper.sendResult(null);
+ return;
+ }
+ if ((getFlags() & RESULT_FLAG_OPTION_NOT_HANDLED) != 0) {
+ // If onLoadChildren(options) is not overridden, the list we get
+ // here is not paginated. Therefore, we need to manually cut
+ // the list. In other words, we need to apply options here.
+ list = applyOptions(list, options);
+ }
+ List<Parcel> parcelList = new ArrayList<>();
+ for (MediaBrowserCompat.MediaItem item : list) {
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcelList.add(parcel);
+ }
+ resultWrapper.sendResult(parcelList);
+ }
- @Override
- public void detach() {
- resultWrapper.detach();
- }
- };
+ @Override
+ public void detach() {
+ resultWrapper.detach();
+ }
+ };
MediaBrowserServiceCompat.this.onLoadChildren(parentId, result, options);
}
@@ -562,18 +588,31 @@
return mCurConnection.rootHints == null ? null
: new Bundle(mCurConnection.rootHints);
}
- return MediaBrowserServiceCompatApi26.getBrowserRootHints(mServiceObj);
+ return mServiceFwk.getBrowserRootHints();
}
@Override
void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
if (options != null) {
- MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
- options);
+ mServiceFwk.notifyChildrenChanged(parentId, options);
} else {
super.notifyChildrenChangedForFramework(parentId, options);
}
}
+
+ class MediaBrowserServiceApi26 extends MediaBrowserServiceApi23 {
+ MediaBrowserServiceApi26(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result,
+ Bundle options) {
+ MediaSessionCompat.ensureClassLoader(options);
+ MediaBrowserServiceImplApi26.this.onLoadChildren(parentId,
+ new ResultWrapper<List<Parcel>>(result), options);
+ }
+ }
}
@RequiresApi(28)
@@ -584,9 +623,9 @@
if (mCurConnection != null) {
return mCurConnection.browserInfo;
}
- android.media.session.MediaSessionManager.RemoteUserInfo userInfoObj =
- ((MediaBrowserService) mServiceObj).getCurrentBrowserInfo();
- return new RemoteUserInfo(userInfoObj);
+ android.media.session.MediaSessionManager.RemoteUserInfo userInfoFwk =
+ mServiceFwk.getCurrentBrowserInfo();
+ return new RemoteUserInfo(userInfoFwk);
}
}
@@ -1158,6 +1197,46 @@
}
}
+ @RequiresApi(21)
+ static class ResultWrapper<T> {
+ MediaBrowserService.Result mResultFwk;
+
+ ResultWrapper(MediaBrowserService.Result result) {
+ mResultFwk = result;
+ }
+
+ public void sendResult(T result) {
+ if (result instanceof List) {
+ mResultFwk.sendResult(parcelListToItemList((List<Parcel>) result));
+ } else if (result instanceof Parcel) {
+ Parcel parcel = (Parcel) result;
+ parcel.setDataPosition(0);
+ mResultFwk.sendResult(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
+ parcel.recycle();
+ } else {
+ // The result is null or an invalid instance.
+ mResultFwk.sendResult(null);
+ }
+ }
+
+ public void detach() {
+ mResultFwk.detach();
+ }
+
+ List<MediaBrowser.MediaItem> parcelListToItemList(List<Parcel> parcelList) {
+ if (parcelList == null) {
+ return null;
+ }
+ List<MediaBrowser.MediaItem> items = new ArrayList<>();
+ for (Parcel parcel : parcelList) {
+ parcel.setDataPosition(0);
+ items.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
+ parcel.recycle();
+ }
+ return items;
+ }
+ }
+
/**
* Attaches to the base context. This method is added to change the visibility of
* {@link Service#attachBaseContext(Context)}.
diff --git a/media/src/main/java/androidx/media/MediaSessionManagerImplApi28.java b/media/src/main/java/androidx/media/MediaSessionManagerImplApi28.java
index b2f72f52..a276db3 100644
--- a/media/src/main/java/androidx/media/MediaSessionManagerImplApi28.java
+++ b/media/src/main/java/androidx/media/MediaSessionManagerImplApi28.java
@@ -33,10 +33,16 @@
@Override
public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) {
- if (userInfo instanceof RemoteUserInfoImplApi28) {
- return mObject.isTrustedForMediaControl(((RemoteUserInfoImplApi28) userInfo).mObject);
- }
- return false;
+ // Don't use framework's isTrustedForMediaControl().
+ // In P, framework's isTrustedForMediaControl() does the sanity check whether the UID, PID,
+ // and package name match. In MediaSession2/MediaController2, Context#getPackageName() is
+ // used by MediaController2 to tell MediaSession2 the package name.
+ // However, UID, PID and Context#getPackageName() may not match if a activity/service runs
+ // on the another app's process by specifying android:process in the AndroidManifest.xml.
+ // In that case, sanity check will always fail.
+ // Alternative way is to use Context#getOpPackageName() for sending the package name,
+ // but it's hidden so we cannot use it.
+ return super.isTrustedForMediaControl(userInfo);
}
static final class RemoteUserInfoImplApi28 implements MediaSessionManager.RemoteUserInfoImpl {
diff --git a/media/src/main/java/androidx/media/MediaSessionManagerImplBase.java b/media/src/main/java/androidx/media/MediaSessionManagerImplBase.java
index c4e5e82..de0c5ad 100644
--- a/media/src/main/java/androidx/media/MediaSessionManagerImplBase.java
+++ b/media/src/main/java/androidx/media/MediaSessionManagerImplBase.java
@@ -55,24 +55,18 @@
@Override
public boolean isTrustedForMediaControl(
@NonNull MediaSessionManager.RemoteUserInfoImpl userInfo) {
- ApplicationInfo applicationInfo;
try {
- applicationInfo = mContext.getPackageManager().getApplicationInfo(
+ ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
userInfo.getPackageName(), 0);
+ if (applicationInfo == null) {
+ return false;
+ }
} catch (PackageManager.NameNotFoundException e) {
if (DEBUG) {
Log.d(TAG, "Package " + userInfo.getPackageName() + " doesn't exist");
}
return false;
}
-
- if (applicationInfo.uid != userInfo.getUid()) {
- if (DEBUG) {
- Log.d(TAG, "Package name " + userInfo.getPackageName()
- + " doesn't match with the uid " + userInfo.getUid());
- }
- return false;
- }
return isPermissionGranted(userInfo, PERMISSION_STATUS_BAR_SERVICE)
|| isPermissionGranted(userInfo, PERMISSION_MEDIA_CONTENT_CONTROL)
|| userInfo.getUid() == Process.SYSTEM_UID
diff --git a/media/src/main/java/androidx/media/VolumeProviderCompat.java b/media/src/main/java/androidx/media/VolumeProviderCompat.java
index 4e8beff..4ddc486 100644
--- a/media/src/main/java/androidx/media/VolumeProviderCompat.java
+++ b/media/src/main/java/androidx/media/VolumeProviderCompat.java
@@ -18,6 +18,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.media.VolumeProvider;
import android.os.Build;
import android.support.v4.media.session.MediaSessionCompat;
@@ -68,7 +69,7 @@
private int mCurrentVolume;
private Callback mCallback;
- private Object mVolumeProviderObj;
+ private VolumeProvider mVolumeProviderFwk;
/**
* Create a new volume provider for handling volume events. You must specify
@@ -121,9 +122,9 @@
*/
public final void setCurrentVolume(int currentVolume) {
mCurrentVolume = currentVolume;
- Object volumeProviderObj = getVolumeProvider();
- if (volumeProviderObj != null && Build.VERSION.SDK_INT >= 21) {
- VolumeProviderCompatApi21.setCurrentVolume(volumeProviderObj, currentVolume);
+ if (Build.VERSION.SDK_INT >= 21) {
+ VolumeProvider volumeProviderFwk = (VolumeProvider) getVolumeProvider();
+ volumeProviderFwk.setCurrentVolume(currentVolume);
}
if (mCallback != null) {
mCallback.onVolumeChanged(this);
@@ -165,23 +166,20 @@
* @return An equivalent {@link android.media.VolumeProvider} object, or null if none.
*/
public Object getVolumeProvider() {
- if (mVolumeProviderObj == null && Build.VERSION.SDK_INT >= 21) {
- mVolumeProviderObj = VolumeProviderCompatApi21.createVolumeProvider(
- mControlType, mMaxVolume, mCurrentVolume,
- new VolumeProviderCompatApi21.Delegate() {
+ if (mVolumeProviderFwk == null && Build.VERSION.SDK_INT >= 21) {
+ mVolumeProviderFwk = new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume) {
+ @Override
+ public void onSetVolumeTo(int volume) {
+ VolumeProviderCompat.this.onSetVolumeTo(volume);
+ }
- @Override
- public void onSetVolumeTo(int volume) {
- VolumeProviderCompat.this.onSetVolumeTo(volume);
- }
-
- @Override
- public void onAdjustVolume(int direction) {
- VolumeProviderCompat.this.onAdjustVolume(direction);
- }
- });
+ @Override
+ public void onAdjustVolume(int direction) {
+ VolumeProviderCompat.this.onAdjustVolume(direction);
+ }
+ };
}
- return mVolumeProviderObj;
+ return mVolumeProviderFwk;
}
/**
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaController2ProviderService.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaController2ProviderService.java
index 427ec59..cdeab4b 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaController2ProviderService.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaController2ProviderService.java
@@ -31,9 +31,9 @@
import androidx.media.test.lib.TestUtils.SyncHandler;
import androidx.media2.MediaBrowser2;
import androidx.media2.MediaController2;
-import androidx.media2.MediaItem2;
import androidx.media2.MediaLibraryService2.LibraryParams;
import androidx.media2.MediaMetadata2;
+import androidx.media2.MediaUtils2;
import androidx.media2.Rating2;
import androidx.media2.SessionCommand2;
import androidx.media2.SessionCommandGroup2;
@@ -95,7 +95,7 @@
@Override
public void create(final boolean isBrowser, final String controllerId,
ParcelImpl tokenParcelable, boolean waitForConnection) throws RemoteException {
- final SessionToken2 token = ParcelUtils.fromParcelable(tokenParcelable);
+ final SessionToken2 token = MediaUtils2.fromParcelable(tokenParcelable);
final TestControllerCallback callback = new TestControllerCallback();
try {
@@ -140,7 +140,7 @@
@Override
public ParcelImpl getConnectedSessionToken(String controllerId) throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- return (ParcelImpl) ParcelUtils.toParcelable(controller2.getConnectedSessionToken());
+ return MediaUtils2.toParcelable(controller2.getConnectedSessionToken());
}
@Override
@@ -156,9 +156,9 @@
}
@Override
- public void prefetch(String controllerId) throws RemoteException {
+ public void prepare(String controllerId) throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.prefetch();
+ controller2.prepare();
}
@Override
@@ -174,58 +174,56 @@
}
@Override
- public void setPlaylist(String controllerId, List<Bundle> list, Bundle metadata)
+ public void setPlaylist(String controllerId, List<String> list, ParcelImpl metadata)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.setPlaylist(
- MediaTestUtils.mediaItem2ListFromBundleList(list),
- MediaMetadata2.fromBundle(metadata));
+ controller2.setPlaylist(list, (MediaMetadata2) MediaUtils2.fromParcelable(metadata));
}
@Override
- public void createAndSetDummyPlaylist(String controllerId, int size, Bundle metadata)
+ public void createAndSetDummyPlaylist(String controllerId, int size, ParcelImpl metadata)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- List<MediaItem2> list = new ArrayList<>();
- MediaItem2.Builder builder = new MediaItem2.Builder(0 /* flags */);
+ List<String> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
// Make media ID of each item same with its index.
- list.add(builder.setMediaId(TestUtils.getMediaIdInDummyList(i)).build());
+ list.add(TestUtils.getMediaIdInDummyList(i));
}
- controller2.setPlaylist(list, MediaMetadata2.fromBundle(metadata));
+ controller2.setPlaylist(list, (MediaMetadata2) MediaUtils2.fromParcelable(metadata));
}
@Override
- public void setMediaItem(String controllerId, Bundle item) throws RemoteException {
+ public void setMediaItem(String controllerId, String mediaId) throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.setMediaItem(MediaItem2.fromBundle(item));
+ controller2.setMediaItem(mediaId);
}
@Override
- public void updatePlaylistMetadata(String controllerId, Bundle metadata)
+ public void updatePlaylistMetadata(String controllerId, ParcelImpl metadata)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.updatePlaylistMetadata(MediaMetadata2.fromBundle(metadata));
+ controller2.updatePlaylistMetadata(
+ (MediaMetadata2) MediaUtils2.fromParcelable(metadata));
}
@Override
- public void addPlaylistItem(String controllerId, int index, Bundle item)
+ public void addPlaylistItem(String controllerId, int index, String mediaId)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.addPlaylistItem(index, MediaItem2.fromBundle(item));
+ controller2.addPlaylistItem(index, mediaId);
}
@Override
- public void removePlaylistItem(String controllerId, Bundle item) throws RemoteException {
+ public void removePlaylistItem(String controllerId, int index) throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.removePlaylistItem(MediaItem2.fromBundle(item));
+ controller2.removePlaylistItem(index);
}
@Override
- public void replacePlaylistItem(String controllerId, int index, Bundle item)
+ public void replacePlaylistItem(String controllerId, int index, String mediaId)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.replacePlaylistItem(index, MediaItem2.fromBundle(item));
+ controller2.replacePlaylistItem(index, mediaId);
}
@Override
@@ -241,9 +239,9 @@
}
@Override
- public void skipToPlaylistItem(String controllerId, Bundle item) throws RemoteException {
+ public void skipToPlaylistItem(String controllerId, int index) throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.skipToPlaylistItem(MediaItem2.fromBundle(item));
+ controller2.skipToPlaylistItem(index);
}
@Override
@@ -291,6 +289,18 @@
}
@Override
+ public void skipForward(String controllerId) throws RemoteException {
+ MediaController2 controller2 = mMediaController2Map.get(controllerId);
+ controller2.skipForward();
+ }
+
+ @Override
+ public void skipBackward(String controllerId) throws RemoteException {
+ MediaController2 controller2 = mMediaController2Map.get(controllerId);
+ controller2.skipBackward();
+ }
+
+ @Override
public void playFromMediaId(String controllerId, String mediaId, Bundle extras)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
@@ -312,24 +322,24 @@
}
@Override
- public void prefetchFromMediaId(String controllerId, String mediaId, Bundle extras)
+ public void prepareFromMediaId(String controllerId, String mediaId, Bundle extras)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.prefetchFromMediaId(mediaId, extras);
+ controller2.prepareFromMediaId(mediaId, extras);
}
@Override
- public void prefetchFromSearch(String controllerId, String query, Bundle extras)
+ public void prepareFromSearch(String controllerId, String query, Bundle extras)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.prefetchFromSearch(query, extras);
+ controller2.prepareFromSearch(query, extras);
}
@Override
- public void prefetchFromUri(String controllerId, Uri uri, Bundle extras)
+ public void prepareFromUri(String controllerId, Uri uri, Bundle extras)
throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
- controller2.prefetchFromUri(uri, extras);
+ controller2.prepareFromUri(uri, extras);
}
@Override
@@ -371,14 +381,14 @@
public void getLibraryRoot(String controllerId, ParcelImpl libraryParams)
throws RemoteException {
MediaBrowser2 browser2 = (MediaBrowser2) mMediaController2Map.get(controllerId);
- browser2.getLibraryRoot((LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ browser2.getLibraryRoot((LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
@Override
public void subscribe(String controllerId, String parentId, ParcelImpl libraryParams)
throws RemoteException {
MediaBrowser2 browser2 = (MediaBrowser2) mMediaController2Map.get(controllerId);
- browser2.subscribe(parentId, (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ browser2.subscribe(parentId, (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
@Override
@@ -392,7 +402,7 @@
ParcelImpl libraryParams) throws RemoteException {
MediaBrowser2 browser2 = (MediaBrowser2) mMediaController2Map.get(controllerId);
browser2.getChildren(parentId, page, pageSize,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
@Override
@@ -405,7 +415,7 @@
public void search(String controllerId, String query, ParcelImpl libraryParams)
throws RemoteException {
MediaBrowser2 browser2 = (MediaBrowser2) mMediaController2Map.get(controllerId);
- browser2.search(query, (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ browser2.search(query, (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
@Override
@@ -413,7 +423,7 @@
ParcelImpl libraryParams) throws RemoteException {
MediaBrowser2 browser2 = (MediaBrowser2) mMediaController2Map.get(controllerId);
browser2.getSearchResult(query, page, pageSize,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
private class TestControllerCallback extends MediaBrowser2.BrowserCallback {
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaTestUtils.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaTestUtils.java
index e38077f..0bcb0f4 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaTestUtils.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/MediaTestUtils.java
@@ -61,19 +61,21 @@
// }
/**
- * Create a playlist for testing purpose
+ * Create a list of {@link FileMediaItem2} for testing purpose.
* <p>
* Caller's method name will be used for prefix of each media item's media id.
*
* @param size list size
* @return the newly created playlist
*/
- public static List<MediaItem2> createPlaylist(int size) {
+ public static List<MediaItem2> createFileMediaItems(int size) {
final List<MediaItem2> list = new ArrayList<>();
String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
for (int i = 0; i < size; i++) {
list.add(new FileMediaItem2.Builder(new FileDescriptor())
- .setMediaId(caller + "_item_" + (size + 1))
+ .setMetadata(new MediaMetadata2.Builder()
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID,
+ caller + "_item_" + (i + 1)).build())
.build());
}
return list;
@@ -85,7 +87,7 @@
* @return the newly created media item
* @see #createMetadata()
*/
- public static MediaItem2 createMediaItemWithMetadata() {
+ public static MediaItem2 createFileMediaItemWithMetadata() {
return new FileMediaItem2.Builder(new FileDescriptor())
.setMetadata(createMetadata())
.build();
@@ -115,17 +117,6 @@
return result;
}
- public static List<MediaItem2> playlistFromParcelableList(List<Parcelable> parcelables) {
- if (parcelables == null) {
- return null;
- }
- List<MediaItem2> result = new ArrayList<>();
- for (Parcelable item : parcelables) {
- result.add(MediaItem2.fromBundle((Bundle) item));
- }
- return result;
- }
-
public static List<Bundle> mediaItem2ListToBundleList(List<MediaItem2> list) {
if (list == null) {
return null;
@@ -137,17 +128,6 @@
return result;
}
- public static List<MediaItem2> mediaItem2ListFromBundleList(List<Bundle> list) {
- if (list == null) {
- return null;
- }
- List<MediaItem2> result = new ArrayList<>();
- for (int i = 0; i < list.size(); i++) {
- result.add(MediaItem2.fromBundle(list.get(i)));
- }
- return result;
- }
-
public static LibraryParams createLibraryParams() {
String callingTestName = Thread.currentThread().getStackTrace()[3].getMethodName();
@@ -156,41 +136,68 @@
return new LibraryParams.Builder().setExtras(extras).build();
}
- public static void assertLibraryParamsEquals(LibraryParams a, LibraryParams b) {
+ // Note: It's not assertEquals() to avoid issue with the static import of JUnit's assertEquals.
+ // Otherwise, this API hides the statically imported JUnit's assertEquals and compile will fail.
+ public static void assertEqualLibraryParams(LibraryParams a, LibraryParams b) {
if (a == null || b == null) {
assertEquals(a, b);
} else {
+ assertEquals(a.isRecent(), b.isRecent());
+ assertEquals(a.isOffline(), b.isOffline());
+ assertEquals(a.isSuggested(), b.isSuggested());
assertTrue(TestUtils.equals(a.getExtras(), b.getExtras()));
}
}
- public static void assertLibraryParamsWithBundle(LibraryParams a, Bundle b) {
- if (a == null || b == null) {
- assertEquals(a, b);
+ public static void assertEqualLibraryParams(LibraryParams params, Bundle rootExtras) {
+ if (params == null || rootExtras == null) {
+ assertEquals(params, rootExtras);
} else {
- assertEquals(a.isRecent(), b.getBoolean(BrowserRoot.EXTRA_RECENT));
- assertEquals(a.isOffline(), b.getBoolean(BrowserRoot.EXTRA_OFFLINE));
- assertEquals(a.isSuggested(), b.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
- assertTrue(TestUtils.contains(b, a.getExtras()));
+ assertEquals(params.isRecent(), rootExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
+ assertEquals(params.isOffline(), rootExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
+ assertEquals(params.isSuggested(), rootExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
+ assertTrue(TestUtils.contains(rootExtras, params.getExtras()));
}
}
- public static void assertMediaItemWithId(String expectedId, MediaItem2 item) {
+ public static void assertMediaItemHasId(MediaItem2 item, String expectedId) {
assertNotNull(item);
assertNotNull(item.getMetadata());
assertEquals(expectedId, item.getMetadata().getString(
MediaMetadata2.METADATA_KEY_MEDIA_ID));
}
- public static void assertPaginatedListWithIds(List<String> fullIdList, int page, int pageSize,
- List<MediaItem2> paginatedList) {
+ public static void assertPaginatedListHasIds(List<MediaItem2> paginatedList,
+ List<String> fullIdList, int page, int pageSize) {
int fromIndex = page * pageSize;
int toIndex = Math.min((page + 1) * pageSize, fullIdList.size());
// Compare the given results with originals.
for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
int relativeIndex = originalIndex - fromIndex;
- assertMediaItemWithId(fullIdList.get(originalIndex),
- paginatedList.get(relativeIndex));
+ assertMediaItemHasId(paginatedList.get(relativeIndex), fullIdList.get(originalIndex)
+ );
}
}
+
+ public static void assertEqualMediaIds(MediaItem2 a, MediaItem2 b) {
+ assertEquals(a.getMetadata().getString(MediaMetadata2.METADATA_KEY_MEDIA_ID),
+ b.getMetadata().getString(MediaMetadata2.METADATA_KEY_MEDIA_ID));
+ }
+
+ public static void assertEqualMediaIds(List<MediaItem2> a, List<MediaItem2> b) {
+ assertEquals(a.size(), b.size());
+ for (int i = 0; i < a.size(); i++) {
+ assertEqualMediaIds(a.get(i), b.get(i));
+ }
+ }
+
+ public static void assertNotMediaItemSubclass(List<MediaItem2> list) {
+ for (MediaItem2 item : list) {
+ assertNotMediaItemSubclass(item);
+ }
+ }
+
+ public static void assertNotMediaItemSubclass(MediaItem2 item) {
+ assertEquals(MediaItem2.class, item.getClass());
+ }
}
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSession2.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSession2.java
index c568ac3..a33ebf3 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSession2.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSession2.java
@@ -54,6 +54,7 @@
import androidx.media2.MediaSession2;
import androidx.media2.MediaSession2.CommandButton;
import androidx.media2.MediaSession2.ControllerInfo;
+import androidx.media2.MediaUtils2;
import androidx.media2.SessionCommand2;
import androidx.media2.SessionCommandGroup2;
import androidx.media2.SessionPlayer2;
@@ -169,7 +170,7 @@
bundle.putBundle(KEY_MEDIA_ITEM, currentItem.toBundle());
}
if (metadata != null) {
- bundle.putBundle(KEY_METADATA, metadata.toBundle());
+ ParcelUtils.putVersionedParcelable(bundle, KEY_METADATA, metadata);
}
return bundle;
}
@@ -187,7 +188,7 @@
public SessionToken2 getToken() {
SessionToken2 token = null;
try {
- token = ParcelUtils.fromParcelable(mBinder.getToken(mSessionId));
+ token = MediaUtils2.fromParcelable(mBinder.getToken(mSessionId));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to get session token. sessionId=" + mSessionId);
}
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSessionCompat.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSessionCompat.java
index 6f46722..5bf0ad0 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSessionCompat.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/RemoteMediaSessionCompat.java
@@ -125,6 +125,18 @@
}
}
+ /**
+ * Since we cannot pass VolumeProviderCompat directly,
+ * we pass volumeControl, maxVolume, currentVolume instead.
+ */
+ public void setPlaybackToRemote(int volumeControl, int maxVolume, int currentVolume) {
+ try {
+ mBinder.setPlaybackToRemote(mSessionTag, volumeControl, maxVolume, currentVolume);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to call setPlaybackToRemote()");
+ }
+ }
+
public void setPlaybackState(PlaybackStateCompat state) {
try {
mBinder.setPlaybackState(mSessionTag,
@@ -202,6 +214,14 @@
}
}
+ public void sendSessionEvent(String event, Bundle extras) {
+ try {
+ mBinder.sendSessionEvent(mSessionTag, event, extras);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to call sendSessionEvent()");
+ }
+ }
+
////////////////////////////////////////////////////////////////////////////////
// Non-public methods
////////////////////////////////////////////////////////////////////////////////
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowser2CallbackTest.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowser2CallbackTest.java
index e29d308..55c8697 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowser2CallbackTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowser2CallbackTest.java
@@ -16,10 +16,9 @@
package androidx.media.test.client.tests;
-import static androidx.media.test.client.MediaTestUtils.assertLibraryParamsEquals;
-import static androidx.media.test.client.MediaTestUtils.assertLibraryParamsWithBundle;
-import static androidx.media.test.client.MediaTestUtils.assertMediaItemWithId;
-import static androidx.media.test.client.MediaTestUtils.assertPaginatedListWithIds;
+import static androidx.media.test.client.MediaTestUtils.assertEqualLibraryParams;
+import static androidx.media.test.client.MediaTestUtils.assertMediaItemHasId;
+import static androidx.media.test.client.MediaTestUtils.assertPaginatedListHasIds;
import static androidx.media.test.client.MediaTestUtils.createLibraryParams;
import static androidx.media.test.lib.CommonConstants.MOCK_MEDIA_LIBRARY_SERVICE;
import static androidx.media.test.lib.MediaBrowser2Constants.CUSTOM_ACTION_ASSERT_PARAMS;
@@ -172,7 +171,7 @@
BrowserResult result = createBrowser().getItem(mediaId)
.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertEquals(RESULT_CODE_SUCCESS, result.getResultCode());
- assertMediaItemWithId(mediaId, result.getMediaItem());
+ assertMediaItemHasId(result.getMediaItem(), mediaId);
}
@Test
@@ -202,9 +201,9 @@
assertEquals(RESULT_CODE_SUCCESS, result.getResultCode());
assertNull(result.getLibraryParams());
- MediaTestUtils.assertPaginatedListWithIds(
- MediaBrowser2Constants.GET_CHILDREN_RESULT,
- page, pageSize, result.getMediaItems());
+ MediaTestUtils.assertPaginatedListHasIds(
+ result.getMediaItems(), MediaBrowser2Constants.GET_CHILDREN_RESULT,
+ page, pageSize);
}
@Test
@@ -269,7 +268,7 @@
public void onSearchResultChanged(MediaBrowser2 browser,
String queryOut, int itemCount, LibraryParams params) {
assertEquals(query, queryOut);
- assertLibraryParamsEquals(testParams, params);
+ assertEqualLibraryParams(testParams, params);
assertEquals(MediaBrowser2Constants.SEARCH_RESULT_COUNT, itemCount);
latchForSearch.countDown();
}
@@ -286,8 +285,8 @@
result = browser.getSearchResult(query, page, pageSize, testParams)
.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertEquals(RESULT_CODE_SUCCESS, result.getResultCode());
- assertPaginatedListWithIds(MediaBrowser2Constants.SEARCH_RESULT, page, pageSize,
- result.getMediaItems());
+ assertPaginatedListHasIds(result.getMediaItems(),
+ MediaBrowser2Constants.SEARCH_RESULT, page, pageSize);
}
@Test
@@ -305,7 +304,7 @@
public void onSearchResultChanged(
MediaBrowser2 browser, String queryOut, int itemCount, LibraryParams params) {
assertEquals(query, queryOut);
- assertLibraryParamsEquals(testParams, params);
+ assertEqualLibraryParams(testParams, params);
assertEquals(MediaBrowser2Constants.LONG_LIST_COUNT, itemCount);
latch.countDown();
}
@@ -339,7 +338,7 @@
public void onSearchResultChanged(
MediaBrowser2 browser, String queryOut, int itemCount, LibraryParams params) {
assertEquals(query, queryOut);
- assertTrue(TestUtils.equals(testParams, params));
+ assertEqualLibraryParams(testParams, params);
assertEquals(MediaBrowser2Constants.SEARCH_RESULT_COUNT, itemCount);
latch.countDown();
}
@@ -365,7 +364,7 @@
public void onSearchResultChanged(
MediaBrowser2 browser, String queryOut, int itemCount, LibraryParams params) {
assertEquals(query, queryOut);
- assertLibraryParamsEquals(testParams, params);
+ assertEqualLibraryParams(testParams, params);
assertEquals(0, itemCount);
latch.countDown();
}
@@ -391,7 +390,7 @@
int itemCount, LibraryParams params) {
assertEquals(expectedParentId, parentId);
assertEquals(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, itemCount);
- assertLibraryParamsWithBundle(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
+ MediaTestUtils.assertEqualLibraryParams(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
latch.countDown();
}
};
@@ -419,7 +418,7 @@
int itemCount, LibraryParams params) {
assertEquals(expectedParentId, parentId);
assertEquals(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, itemCount);
- assertLibraryParamsWithBundle(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
+ assertEqualLibraryParams(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
latch.countDown();
}
};
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowserCompatTestWithMediaLibraryService2.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowserCompatTestWithMediaLibraryService2.java
index b5cd7e3..cf5fe1a 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowserCompatTestWithMediaLibraryService2.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaBrowserCompatTestWithMediaLibraryService2.java
@@ -410,7 +410,7 @@
public void testSubscribe() throws InterruptedException {
// prepareLooper();
// final String testParentId = "testSubscribeId";
-// final List<MediaItem2> testList = TestUtils.createPlaylist(3);
+// final List<MediaItem2> testList = TestUtils.createMediaItems(3);
//
// final CountDownLatch latch = new CountDownLatch(1);
// final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
@@ -458,7 +458,7 @@
// final String testParentId = "testSubscribe_withExtras";
// final Bundle testExtras = new Bundle();
// testExtras.putString(testParentId, testParentId);
-// final List<MediaItem2> testList = TestUtils.createPlaylist(3);
+// final List<MediaItem2> testList = TestUtils.createMediaItems(3);
//
// final CountDownLatch latch = new CountDownLatch(1);
// final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
@@ -508,7 +508,7 @@
public void testSubscribe_withPagination() throws InterruptedException {
// prepareLooper();
// final String testParentId = "testSubscribe_pagination_ID";
-// final List<MediaItem2> testList = TestUtils.createPlaylist(3);
+// final List<MediaItem2> testList = TestUtils.createMediaItems(3);
// final int testPage = 2;
// final int testPageSize = 3;
// final Bundle testExtras = new Bundle();
@@ -619,7 +619,7 @@
// final String testUnsubscribedParentId = "testNotifyChildrenChanged22";
// final Bundle testExtras = new Bundle();
// testExtras.putString(testSubscribedParentId, testSubscribedParentId);
-// final List<MediaItem2> testList = TestUtils.createPlaylist(3);
+// final List<MediaItem2> testList = TestUtils.createMediaItems(3);
//
// final CountDownLatch subscribeLatch = new CountDownLatch(1);
// final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2CallbackTest.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2CallbackTest.java
index 222e94e..4fb8047 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2CallbackTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2CallbackTest.java
@@ -19,6 +19,8 @@
import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+import static androidx.media.test.client.MediaTestUtils.assertEqualMediaIds;
+import static androidx.media.test.client.MediaTestUtils.assertNotMediaItemSubclass;
import static androidx.media.test.lib.CommonConstants.DEFAULT_TEST_NAME;
import static androidx.media.test.lib.CommonConstants.INDEX_FOR_NULL_ITEM;
import static androidx.media.test.lib.CommonConstants.INDEX_FOR_UNKONWN_ITEM;
@@ -214,7 +216,7 @@
public void testControllerCallback_sessionUpdatePlayer() throws InterruptedException {
prepareLooper();
final int testState = SessionPlayer2.PLAYER_STATE_PLAYING;
- final List<MediaItem2> testPlaylist = MediaTestUtils.createPlaylist(3);
+ final List<MediaItem2> testPlaylist = MediaTestUtils.createFileMediaItems(3);
final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder()
.setLegacyStreamType(AudioManager.STREAM_RING).build();
final CountDownLatch latch = new CountDownLatch(3);
@@ -231,7 +233,8 @@
public void onPlaylistChanged(MediaController2 controller,
List<MediaItem2> list, MediaMetadata2 metadata) {
assertEquals(mController, controller);
- assertEquals(testPlaylist, list);
+ assertNotMediaItemSubclass(list);
+ assertEqualMediaIds(testPlaylist, list);
assertNull(metadata);
latch.countDown();
}
@@ -258,7 +261,7 @@
public void testOnCurrentMediaItemChanged() throws Exception {
prepareLooper();
final int listSize = 5;
- final List<MediaItem2> list = MediaTestUtils.createPlaylist(listSize);
+ final List<MediaItem2> list = MediaTestUtils.createFileMediaItems(listSize);
mRemoteSession2.getMockPlayer().setPlaylistWithDummyItem(list);
final int currentItemIndex = 3;
@@ -274,6 +277,7 @@
// No check needed..
break;
case 2:
+ assertNotMediaItemSubclass(item);
assertEquals(currentItem.getMediaId(), item.getMediaId());
break;
case 1:
@@ -389,7 +393,7 @@
@Test
public void testOnPlaylistChanged() throws InterruptedException {
prepareLooper();
- final List<MediaItem2> testList = MediaTestUtils.createPlaylist(2);
+ final List<MediaItem2> testList = MediaTestUtils.createFileMediaItems(2);
final AtomicReference<List<MediaItem2>> listFromCallback = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
final MediaController2.ControllerCallback callback =
@@ -398,11 +402,8 @@
public void onPlaylistChanged(MediaController2 controller,
List<MediaItem2> playlist, MediaMetadata2 metadata) {
assertNotNull(playlist);
- assertEquals(testList.size(), playlist.size());
- for (int i = 0; i < playlist.size(); i++) {
- assertEquals(
- testList.get(i).getMediaId(), playlist.get(i).getMediaId());
- }
+ assertNotMediaItemSubclass(playlist);
+ assertEqualMediaIds(testList, playlist);
listFromCallback.set(playlist);
latch.countDown();
}
@@ -589,7 +590,7 @@
prepareLooper();
final CountDownLatch latch = new CountDownLatch(1);
- final List<MediaItem2> testPlaylist = MediaTestUtils.createPlaylist(3);
+ final List<MediaItem2> testPlaylist = MediaTestUtils.createFileMediaItems(3);
final int targetItemIndex = 0;
final int testBufferingState = SessionPlayer2.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
final long testBufferingPosition = 500;
@@ -600,6 +601,7 @@
public void onBufferingStateChanged(MediaController2 controller, MediaItem2 item,
int state) {
controller.setTimeDiff(0L);
+ assertNotMediaItemSubclass(item);
assertEquals(testPlaylist.get(targetItemIndex).getMediaId(), item.getMediaId());
assertEquals(testBufferingState, state);
assertEquals(testBufferingState, controller.getBufferingState());
@@ -680,7 +682,7 @@
public void testOnCustomCommand() throws InterruptedException {
prepareLooper();
final SessionCommand2 testCommand = new SessionCommand2(
- SessionCommand2.COMMAND_CODE_PLAYER_PREFETCH);
+ SessionCommand2.COMMAND_CODE_PLAYER_PREPARE);
final Bundle testArgs = TestUtils.createTestBundle();
final CountDownLatch latch = new CountDownLatch(2);
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2LegacyTest.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2LegacyTest.java
index 05bd360..9f5ebbc 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2LegacyTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2LegacyTest.java
@@ -18,11 +18,14 @@
import static android.support.mediacompat.testlib.util.IntentUtil.SERVICE_PACKAGE_NAME;
+import static androidx.media.test.client.MediaTestUtils.assertEqualMediaIds;
+import static androidx.media.test.client.MediaTestUtils.assertMediaItemHasId;
import static androidx.media.test.lib.CommonConstants.DEFAULT_TEST_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.app.PendingIntent;
@@ -30,26 +33,30 @@
import android.content.Intent;
import android.media.AudioManager;
import android.os.Build;
+import android.os.Bundle;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+import androidx.media.VolumeProviderCompat;
import androidx.media.test.client.MediaTestUtils;
import androidx.media.test.client.RemoteMediaSessionCompat;
import androidx.media.test.lib.MockActivity;
+import androidx.media.test.lib.TestUtils;
import androidx.media2.MediaController2;
import androidx.media2.MediaController2.ControllerCallback;
import androidx.media2.MediaItem2;
import androidx.media2.MediaMetadata2;
import androidx.media2.MediaUtils2;
+import androidx.media2.RemoteSessionPlayer2;
+import androidx.media2.SessionCommand2;
import androidx.media2.SessionPlayer2;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import java.util.List;
@@ -59,9 +66,12 @@
/**
* Tests {@link MediaController2} interacting with {@link MediaSessionCompat}.
+ *
+ * TODO: Pull out callback tests to a separate file (i.e. MediaController2LegacyCallbackTest).
*/
@SmallTest
public class MediaController2LegacyTest extends MediaSession2TestBase {
+ private static final String TAG = "MediaController2LegacyTest";
AudioManager mAudioManager;
RemoteMediaSessionCompat mSession;
@@ -139,13 +149,57 @@
}
/**
+ * This also tests {@link ControllerCallback#onRepeatModeChanged(MediaController2, int)}.
+ */
+ @Test
+ public void testGetRepeatMode() throws Exception {
+ prepareLooper();
+ final int testRepeatMode = SessionPlayer2.REPEAT_MODE_GROUP;
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onRepeatModeChanged(MediaController2 controller, int repeatMode) {
+ assertEquals(testRepeatMode, repeatMode);
+ latch.countDown();
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+
+ mSession.setRepeatMode(testRepeatMode);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(testRepeatMode, mController.getRepeatMode());
+ }
+
+ /**
+ * This also tests {@link ControllerCallback#onShuffleModeChanged(MediaController2, int)}.
+ */
+ @Test
+ public void testGetShuffleMode() throws Exception {
+ prepareLooper();
+ final int testShuffleMode = SessionPlayer2.SHUFFLE_MODE_GROUP;
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onShuffleModeChanged(MediaController2 controller, int shuffleMode) {
+ assertEquals(testShuffleMode, shuffleMode);
+ latch.countDown();
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+
+ mSession.setShuffleMode(testShuffleMode);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(testShuffleMode, mController.getShuffleMode());
+ }
+
+ /**
* This also tests {@link ControllerCallback#onPlaylistChanged(
* MediaController2, List, MediaMetadata2)}.
*/
@Test
public void testGetPlaylist() throws Exception {
prepareLooper();
- final List<MediaItem2> testList = MediaTestUtils.createPlaylist(2);
+ final List<MediaItem2> testList = MediaTestUtils.createFileMediaItems(2);
final List<QueueItem> testQueue = MediaUtils2.convertToQueueItemList(testList);
final AtomicReference<List<MediaItem2>> listFromCallback = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
@@ -194,7 +248,81 @@
}
@Test
- @Ignore("b/110738672")
+ public void testGetCurrentMediaItemAfterConnected() throws Exception {
+ prepareLooper();
+ mController = createController(mSession.getSessionToken(), true, null);
+ assertNull(mController.getCurrentMediaItem());
+ }
+
+ @Test
+ public void testGetCurrentMediaItemAfterConnected_metadata() throws Exception {
+ prepareLooper();
+ final String testMediaId = "testGetCurrentMediaItemWhenConnected_metadata";
+ MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
+ .putText(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testMediaId)
+ .build();
+ mSession.setMetadata(metadata);
+
+ mController = createController(mSession.getSessionToken(), true, null);
+ assertEquals(testMediaId, mController.getCurrentMediaItem().getMediaId());
+ }
+
+ @Test
+ public void testControllerCallback_onCurrentMediaItemChanged_byMetadataChange()
+ throws Exception {
+ prepareLooper();
+ final String testMediaId = "testControllerCallback_onCurrentMediaItemChanged_bySetMetadata";
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onCurrentMediaItemChanged(MediaController2 controller, MediaItem2 item) {
+ assertMediaItemHasId(item, testMediaId);
+ latch.countDown();
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+ MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
+ .putText(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testMediaId)
+ .build();
+ mSession.setMetadata(metadata);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testControllerCallback_onCurrentMediaItemChanged_byActiveQueueItemChange()
+ throws Exception {
+ prepareLooper();
+ final List<MediaItem2> testList = MediaTestUtils.createFileMediaItems(2);
+ final List<QueueItem> testQueue = MediaUtils2.convertToQueueItemList(testList);
+ mSession.setQueue(testQueue);
+
+ PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+ // Set the current active queue item to index 'oldItemIndex'.
+ final int oldItemIndex = 0;
+ builder.setActiveQueueItemId(testQueue.get(oldItemIndex).getQueueId());
+ mSession.setPlaybackState(builder.build());
+
+ final int newItemIndex = 1;
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onCurrentMediaItemChanged(MediaController2 controller, MediaItem2 item) {
+ assertEqualMediaIds(testList.get(newItemIndex), item);
+ latch.countDown();
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+
+ // The new playbackState will tell the controller that the active queue item is changed to
+ // 'newItemIndex'.
+ builder.setActiveQueueItemId(testQueue.get(newItemIndex).getQueueId());
+ mSession.setPlaybackState(builder.build());
+
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
public void testControllerCallback_onSeekCompleted() throws Exception {
prepareLooper();
final long testSeekPosition = 400;
@@ -222,13 +350,11 @@
}
@Test
- @Ignore("b/110738672")
- public void testControllerCallbackBufferingCompleted() throws Exception {
+ public void testControllerCallback_onBufferingCompleted() throws Exception {
prepareLooper();
- final List<MediaItem2> testPlaylist = MediaTestUtils.createPlaylist(1);
- final List<QueueItem> testQueue = MediaUtils2.convertToQueueItemList(testPlaylist);
+ final List<MediaItem2> testPlaylist = MediaTestUtils.createFileMediaItems(1);
final MediaMetadataCompat metadata = MediaUtils2.convertToMediaMetadataCompat(
- testQueue.get(0).getDescription());
+ testPlaylist.get(0).getMetadata());
final int testBufferingState = SessionPlayer2.BUFFERING_STATE_COMPLETE;
final long testBufferingPosition = 500;
@@ -261,14 +387,12 @@
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
- @FlakyTest(bugId = 111433579)
@Test
- public void testControllerCallbackBufferingStarved() throws Exception {
+ public void testControllerCallback_onBufferingStarved() throws Exception {
prepareLooper();
- final List<MediaItem2> testPlaylist = MediaTestUtils.createPlaylist(1);
- final List<QueueItem> testQueue = MediaUtils2.convertToQueueItemList(testPlaylist);
+ final List<MediaItem2> testPlaylist = MediaTestUtils.createFileMediaItems(1);
final MediaMetadataCompat metadata = MediaUtils2.convertToMediaMetadataCompat(
- testQueue.get(0).getDescription());
+ testPlaylist.get(0).getMetadata());
final int testBufferingState = SessionPlayer2.BUFFERING_STATE_BUFFERING_AND_STARVED;
final long testBufferingPosition = 0;
@@ -302,7 +426,6 @@
}
@Test
- @Ignore("b/110738672")
public void testControllerCallback_onPlayerStateChanged() throws Exception {
prepareLooper();
final int testPlayerState = SessionPlayer2.PLAYER_STATE_PLAYING;
@@ -331,13 +454,117 @@
}
@Test
+ public void testControllerCallback_onPlaybackSpeedChanged() throws Exception {
+ prepareLooper();
+ final float testSpeed = 3.0f;
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onPlaybackSpeedChanged(MediaController2 controller, float speed) {
+ assertEquals(testSpeed, speed, 0.0f);
+ latch.countDown();
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+ mSession.setPlaybackState(new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, 0 /* position */,
+ testSpeed /* playbackSpeed */)
+ .build());
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testControllerCallback_onPlaybackInfoChanged_byPlaybackTypeChangeToRemote()
+ throws Exception {
+ prepareLooper();
+ final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ final int maxVolume = 100;
+ final int currentVolume = 45;
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onPlaybackInfoChanged(MediaController2 controller,
+ MediaController2.PlaybackInfo info) {
+ // Here, we are intentionally avoid using assertEquals(), since this callback
+ // can be called many times which of them have inaccurate values.
+ Log.d(TAG, "Given playbackType=" + info.getPlaybackType()
+ + " controlType=" + info.getControlType()
+ + " maxVolume=" + info.getMaxVolume()
+ + " currentVolume=" + info.getCurrentVolume()
+ + " audioAttrs=" + info.getAudioAttributes());
+ if (MediaController2.PlaybackInfo.PLAYBACK_TYPE_REMOTE == info.getPlaybackType()
+ && volumeControlType == info.getControlType()
+ && maxVolume == info.getMaxVolume()
+ && currentVolume == info.getCurrentVolume()) {
+ latch.countDown();
+ }
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+ mSession.setPlaybackToRemote(volumeControlType, maxVolume, currentVolume);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testControllerCallback_onPlaybackInfoChanged_byPlaybackTypeChangeToLocal()
+ throws Exception {
+ prepareLooper();
+ mSession.setPlaybackToRemote(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 100, 45);
+
+ final int testLocalStreamType = AudioManager.STREAM_ALARM;
+ final int maxVolume = mAudioManager.getStreamMaxVolume(testLocalStreamType);
+ final int currentVolume = mAudioManager.getStreamVolume(testLocalStreamType);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onPlaybackInfoChanged(MediaController2 controller,
+ MediaController2.PlaybackInfo info) {
+ assertEquals(MediaController2.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+ info.getPlaybackType());
+ assertEquals(RemoteSessionPlayer2.VOLUME_CONTROL_ABSOLUTE, info.getControlType());
+ assertEquals(maxVolume, info.getMaxVolume());
+ assertEquals(currentVolume, info.getCurrentVolume());
+ latch.countDown();
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+ mSession.setPlaybackToLocal(testLocalStreamType);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testControllerCallback_onCustomCommand() throws Exception {
+ prepareLooper();
+ final String event = "testControllerCallback_onCustomCommand";
+ final Bundle extras = TestUtils.createTestBundle();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public MediaController2.ControllerResult onCustomCommand(MediaController2 controller,
+ SessionCommand2 command, Bundle args) {
+ assertEquals(event, command.getCustomCommand());
+ assertTrue(TestUtils.equals(extras, args));
+ latch.countDown();
+ return null;
+ }
+ };
+ mController = createController(mSession.getSessionToken(), true, callback);
+ mSession.sendSessionEvent(event, extras);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
public void testControllerCallback_onConnected() throws Exception {
prepareLooper();
mController = createController(mSession.getSessionToken());
}
@Test
- public void testControllerCallback_releaseSession() throws Exception {
+ public void testControllerCallback_onDisconnected() throws Exception {
prepareLooper();
mController = createController(mSession.getSessionToken());
mSession.release();
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test.java
index f572560..3f224e1 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test.java
@@ -21,6 +21,8 @@
import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC;
import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED;
+import static androidx.media.test.client.MediaTestUtils.assertEqualMediaIds;
+import static androidx.media.test.client.MediaTestUtils.assertNotMediaItemSubclass;
import static androidx.media.test.lib.CommonConstants.DEFAULT_TEST_NAME;
import static androidx.media.test.lib.MediaSession2Constants.TEST_GET_SESSION_ACTIVITY;
@@ -254,7 +256,7 @@
final long bufferedPosition = 900000;
final float speed = 0.5f;
final long timeDiff = 102;
- final MediaItem2 currentMediaItem = MediaTestUtils.createMediaItemWithMetadata();
+ final MediaItem2 currentMediaItem = MediaTestUtils.createFileMediaItemWithMetadata();
Bundle config = RemoteMediaSession2.createMockPlayerConnectorConfig(
state, bufferingState, position, bufferedPosition, speed, null /* audioAttrs */,
@@ -267,7 +269,8 @@
assertEquals(bufferedPosition, controller.getBufferedPosition());
assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
assertEquals(position + (long) (speed * timeDiff), controller.getCurrentPosition());
- assertEquals(currentMediaItem, controller.getCurrentMediaItem());
+ assertNotMediaItemSubclass(controller.getCurrentMediaItem());
+ assertEqualMediaIds(currentMediaItem, controller.getCurrentMediaItem());
}
@Test
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaControllerCompatCallbackTestWithMediaSession2.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaControllerCompatCallbackTestWithMediaSession2.java
index 79c2da86..53023b2 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaControllerCompatCallbackTestWithMediaSession2.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaControllerCompatCallbackTestWithMediaSession2.java
@@ -132,7 +132,7 @@
final int testState = SessionPlayer2.PLAYER_STATE_PLAYING;
final int testBufferingPosition = 1500;
final float testSpeed = 1.5f;
- final List<MediaItem2> testPlaylist = MediaTestUtils.createPlaylist(3);
+ final List<MediaItem2> testPlaylist = MediaTestUtils.createFileMediaItems(3);
final String testPlaylistTitle = "testPlaylistTitle";
final MediaMetadata2 testPlaylistMetadata = new MediaMetadata2.Builder()
.putText(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE, testPlaylistTitle).build();
@@ -374,7 +374,7 @@
@Test
public void testBufferingStateChange() throws Exception {
prepareLooper();
- final List<MediaItem2> testPlaylist = MediaTestUtils.createPlaylist(3);
+ final List<MediaItem2> testPlaylist = MediaTestUtils.createFileMediaItems(3);
final int testItemIndex = 0;
final int testBufferingState = SessionPlayer2.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
final long testBufferingPosition = 500;
@@ -420,7 +420,7 @@
.setMetadata(metadata)
.build();
- List<MediaItem2> playlist = MediaTestUtils.createPlaylist(5);
+ List<MediaItem2> playlist = MediaTestUtils.createFileMediaItems(5);
final int testItemIndex = 3;
playlist.set(testItemIndex, currentMediaItem);
mSession.getMockPlayer().setPlaylistWithDummyItem(playlist);
@@ -441,7 +441,7 @@
@Test
public void testPlaylistAndPlaylistMetadataChange() throws Exception {
prepareLooper();
- final List<MediaItem2> playlist = MediaTestUtils.createPlaylist(5);
+ final List<MediaItem2> playlist = MediaTestUtils.createFileMediaItems(5);
final String playlistTitle = "playlistTitle";
MediaMetadata2 playlistMetadata = new MediaMetadata2.Builder()
.putText(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE, playlistTitle).build();
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaItem2Test.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaItem2Test.java
new file mode 100644
index 0000000..222f0e1
--- /dev/null
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaItem2Test.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018 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 androidx.media.test.client.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.media.test.lib.TestUtils;
+import androidx.media2.MediaItem2;
+import androidx.media2.MediaMetadata2;
+import androidx.media2.MediaUtils2;
+import androidx.media2.UriMediaItem2;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.versionedparcelable.ParcelImpl;
+import androidx.versionedparcelable.ParcelUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link MediaItem2}.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaItem2Test {
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ @Test
+ public void testSubclass_sameProcess() {
+ final UriMediaItem2 testUriItem = createUriMediaItem2();
+ final ParcelImpl parcel = MediaUtils2.toParcelable(testUriItem);
+
+ final MediaItem2 testRemoteItem = MediaUtils2.fromParcelable(parcel);
+ assertEquals(testUriItem, testRemoteItem);
+ }
+
+ @Test
+ public void testSubclass_acrossProcessWithMediaUtils() {
+ final UriMediaItem2 testUriItem = createUriMediaItem2();
+
+ // Mocks the binder call across the processes by using writeParcelable/readParcelable
+ // which only happens between processes. Code snippets are copied from
+ // VersionedParcelIntegTest#parcelCopy.
+ final Parcel p = Parcel.obtain();
+ p.writeParcelable(MediaUtils2.toParcelable(testUriItem), 0);
+ p.setDataPosition(0);
+ final MediaItem2 testRemoteItem = MediaUtils2.fromParcelable(
+ (ParcelImpl) p.readParcelable(MediaItem2.class.getClassLoader()));
+
+ assertFalse(testRemoteItem instanceof UriMediaItem2);
+ assertEquals(testUriItem.getStartPosition(), testRemoteItem.getStartPosition());
+ assertEquals(testUriItem.getEndPosition(), testRemoteItem.getEndPosition());
+ TestUtils.equals(testUriItem.getMetadata().toBundle(),
+ testRemoteItem.getMetadata().toBundle());
+ }
+
+ @Test
+ public void testSubclass_acrossProcessWithParcelUtils() {
+ final UriMediaItem2 testUriItem = createUriMediaItem2();
+
+ // Mocks the binder call across the processes by using writeParcelable/readParcelable
+ // which only happens between processes. Code snippets are copied from
+ // VersionedParcelIntegTest#parcelCopy.
+ try {
+ final Parcel p = Parcel.obtain();
+ p.writeParcelable(ParcelUtils.toParcelable(testUriItem), 0);
+ p.setDataPosition(0);
+ final MediaItem2 testRemoteItem = ParcelUtils.fromParcelable(
+ (ParcelImpl) p.readParcelable(MediaItem2.class.getClassLoader()));
+ fail("Write to parcel should fail for subclass of MediaItem2");
+ } catch (Exception e) {
+ }
+ }
+
+ private UriMediaItem2 createUriMediaItem2() {
+ final MediaMetadata2 testMetadata = new MediaMetadata2.Builder()
+ .putString("MediaItem2Test", "MediaItem2Test").build();
+ return new UriMediaItem2.Builder(mContext, Uri.parse("test://test"))
+ .setMetadata(testMetadata)
+ .setStartPosition(1)
+ .setEndPosition(1000)
+ .build();
+ }
+}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSession2ProviderService.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSession2ProviderService.java
index 1b3d89b..0d323a3 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSession2ProviderService.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSession2ProviderService.java
@@ -56,6 +56,7 @@
import androidx.media2.MediaMetadata2;
import androidx.media2.MediaSession2;
import androidx.media2.MediaSession2.ControllerInfo;
+import androidx.media2.MediaUtils2;
import androidx.media2.SessionCommand2;
import androidx.media2.SessionCommandGroup2;
import androidx.media2.SessionPlayer2;
@@ -176,7 +177,7 @@
public ParcelImpl getToken(String sessionId) throws RemoteException {
MediaSession2 session2 = mSession2Map.get(sessionId);
return session2 != null
- ? (ParcelImpl) ParcelUtils.toParcelable(session2.getToken()) : null;
+ ? MediaUtils2.toParcelable(session2.getToken()) : null;
}
@Override
@@ -216,8 +217,7 @@
config.getParcelableArrayList(KEY_PLAYLIST), false /* createItem */);
localPlayer.mCurrentMediaItem = MediaItem2.fromBundle(
config.getBundle(KEY_MEDIA_ITEM));
- localPlayer.mMetadata = MediaMetadata2.fromBundle(
- config.getBundle(KEY_METADATA));
+ localPlayer.mMetadata = ParcelUtils.getVersionedParcelable(config, KEY_METADATA);
player = localPlayer;
}
player.setAudioAttributes(
@@ -393,8 +393,10 @@
List<MediaItem2> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
- list.add(new MediaItem2.Builder(0)
- .setMediaId(TestUtils.getMediaIdInDummyList(i))
+ list.add(new MediaItem2.Builder()
+ .setMetadata(new MediaMetadata2.Builder()
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID,
+ TestUtils.getMediaIdInDummyList(i)).build())
.build());
}
player.mPlaylist = list;
@@ -411,7 +413,6 @@
MediaItem2 item = MediaItem2.fromBundle(bundle);
list.add(new FileMediaItem2.Builder(new FileDescriptor())
.setMetadata(item.getMetadata())
- .setMediaId(item.getMediaId())
.build());
}
player.mPlaylist = list;
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSessionCompatProviderService.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSessionCompatProviderService.java
index 09aaa9a..ba7cf78 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSessionCompatProviderService.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaSessionCompatProviderService.java
@@ -35,6 +35,7 @@
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
+import androidx.media.VolumeProviderCompat;
import androidx.media.test.lib.TestUtils.SyncHandler;
import java.util.HashMap;
@@ -119,6 +120,24 @@
}
@Override
+ public void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume,
+ int currentVolume) throws RemoteException {
+ MediaSessionCompat session = mSessionMap.get(sessionTag);
+ session.setPlaybackToRemote(new VolumeProviderCompat(
+ volumeControl, maxVolume, currentVolume) {
+ @Override
+ public void onSetVolumeTo(int volume) {
+ setCurrentVolume(volume);
+ }
+
+ @Override
+ public void onAdjustVolume(int direction) {
+ setCurrentVolume(getCurrentVolume() + direction);
+ }
+ });
+ }
+
+ @Override
public void release(String sessionTag) throws RemoteException {
MediaSessionCompat session = mSessionMap.get(sessionTag);
session.release();
@@ -183,5 +202,12 @@
MediaSessionCompat session = mSessionMap.get(sessionTag);
session.setRatingType(type);
}
+
+ @Override
+ public void sendSessionEvent(String sessionTag, String event, Bundle extras)
+ throws RemoteException {
+ MediaSessionCompat session = mSessionMap.get(sessionTag);
+ session.sendSessionEvent(event, extras);
+ }
}
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaTestUtils.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaTestUtils.java
index b8af663..91f7776 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaTestUtils.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MediaTestUtils.java
@@ -79,15 +79,29 @@
final List<MediaItem2> list = new ArrayList<>();
String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
for (int i = 0; i < size; i++) {
- list.add(new FileMediaItem2.Builder(new FileDescriptor())
- .setMediaId(caller + "_item_" + (size + 1))
- .build());
+ list.add(createMediaItem(caller + "_item_" + (size + 1)));
}
return list;
}
public static MediaItem2 createMediaItem(String id) {
- return new FileMediaItem2.Builder(new FileDescriptor()).setMediaId(id).build();
+ return new FileMediaItem2.Builder(new FileDescriptor())
+ .setMetadata(new MediaMetadata2.Builder()
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, id)
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE,
+ MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
+ .build())
+ .build();
+ }
+
+ public static List<String> createMediaIds(int size) {
+ final List<String> list = new ArrayList<>();
+ String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
+ for (int i = 0; i < size; i++) {
+ list.add(caller + "_item_" + (size + 1));
+ }
+ return list;
}
/**
@@ -112,18 +126,10 @@
public static MediaMetadata2 createMetadata() {
String mediaId = Thread.currentThread().getStackTrace()[1].getMethodName();
return new MediaMetadata2.Builder()
- .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId).build();
- }
-
- public static List<Bundle> playlistToBundleList(List<MediaItem2> playlist) {
- if (playlist == null) {
- return null;
- }
- List<Bundle> result = new ArrayList<>();
- for (int i = 0; i < playlist.size(); i++) {
- result.add(playlist.get(i).toBundle());
- }
- return result;
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE, MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
+ .build();
}
public static List<MediaItem2> playlistFromParcelableList(List<Parcelable> parcelables,
@@ -138,7 +144,6 @@
MediaItem2 item = MediaItem2.fromBundle((Bundle) itemBundle);
result.add(new FileMediaItem2.Builder(new FileDescriptor())
.setMetadata(item.getMetadata())
- .setMediaId(item.getMediaId())
.build());
}
} else {
@@ -181,7 +186,7 @@
return new LibraryParams.Builder().setExtras(extras).build();
}
- public static void assertLibraryParamsEquals(LibraryParams a, LibraryParams b) {
+ public static void assertEqualLibraryParams(LibraryParams a, LibraryParams b) {
if (a == null || b == null) {
assertEquals(a, b);
} else {
@@ -189,14 +194,14 @@
}
}
- public static void assertLibraryParamsWithBundle(LibraryParams a, Bundle b) {
- if (a == null || b == null) {
- assertEquals(a, b);
+ public static void assertEqualLibraryParams(LibraryParams params, Bundle rootExtras) {
+ if (params == null || rootExtras == null) {
+ assertEquals(params, rootExtras);
} else {
- assertEquals(a.isRecent(), b.getBoolean(BrowserRoot.EXTRA_RECENT));
- assertEquals(a.isOffline(), b.getBoolean(BrowserRoot.EXTRA_OFFLINE));
- assertEquals(a.isSuggested(), b.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
- assertTrue(TestUtils.contains(b, a.getExtras()));
+ assertEquals(params.isRecent(), rootExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
+ assertEquals(params.isOffline(), rootExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
+ assertEquals(params.isSuggested(), rootExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
+ assertTrue(TestUtils.contains(rootExtras, params.getExtras()));
}
}
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MockMediaLibraryService2.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MockMediaLibraryService2.java
index 35d37f0..8a226a8 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MockMediaLibraryService2.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/MockMediaLibraryService2.java
@@ -46,11 +46,14 @@
.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
import static androidx.media.test.lib.MediaBrowser2Constants
.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
-import static androidx.media.test.service.MediaTestUtils.assertLibraryParamsEquals;
+import static androidx.media.test.service.MediaTestUtils.assertEqualLibraryParams;
import static androidx.media2.MediaLibraryService2.LibraryResult.RESULT_CODE_BAD_VALUE;
import static androidx.media2.MediaLibraryService2.LibraryResult.RESULT_CODE_SUCCESS;
-import static androidx.media2.MediaMetadata2.FLAG_BROWSABLE;
+import static androidx.media2.MediaMetadata2.BROWSABLE_TYPE_MIXED;
+import static androidx.media2.MediaMetadata2.BROWSABLE_TYPE_NONE;
+import static androidx.media2.MediaMetadata2.METADATA_KEY_BROWSABLE;
import static androidx.media2.MediaMetadata2.METADATA_KEY_MEDIA_ID;
+import static androidx.media2.MediaMetadata2.METADATA_KEY_PLAYABLE;
import android.app.Service;
import android.content.Context;
@@ -83,9 +86,11 @@
* ID of the session that this service will create.
*/
public static final String ID = "TestLibrary";
- public static final MediaItem2 ROOT_ITEM = new MediaItem2.Builder(FLAG_BROWSABLE)
+ public static final MediaItem2 ROOT_ITEM = new MediaItem2.Builder()
.setMetadata(new MediaMetadata2.Builder()
.putString(METADATA_KEY_MEDIA_ID, ROOT_ID)
+ .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
+ .putLong(METADATA_KEY_PLAYABLE, 0)
.build()).build();
public static final LibraryParams ROOT_PARAMS = new LibraryParams.Builder()
.setExtras(ROOT_EXTRAS).build();
@@ -210,10 +215,16 @@
getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize), null);
} else if (PARENT_ID_LONG_LIST.equals(parentId)) {
List<MediaItem2> list = new ArrayList<>(LONG_LIST_COUNT);
- MediaItem2.Builder builder = new MediaItem2.Builder(0);
+ MediaItem2.Builder builder = new MediaItem2.Builder();
for (int i = 0; i < LONG_LIST_COUNT; i++) {
list.add(builder
- .setMediaId(TestUtils.getMediaIdInDummyList(i))
+ .setMetadata(new MediaMetadata2.Builder()
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID,
+ TestUtils.getMediaIdInDummyList(i))
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE,
+ MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
+ .build())
.build());
}
return new LibraryResult(RESULT_CODE_SUCCESS, list, null);
@@ -260,11 +271,9 @@
getPaginatedResult(SEARCH_RESULT, page, pageSize), null);
} else if (SEARCH_QUERY_LONG_LIST.equals(query)) {
List<MediaItem2> list = new ArrayList<>(LONG_LIST_COUNT);
- MediaItem2.Builder builder = new MediaItem2.Builder(0);
+ MediaItem2.Builder builder = new MediaItem2.Builder();
for (int i = 0; i < LONG_LIST_COUNT; i++) {
- list.add(builder
- .setMediaId(TestUtils.getMediaIdInDummyList(i))
- .build());
+ list.add(createMediaItem(TestUtils.getMediaIdInDummyList(i)));
}
return new LibraryResult(RESULT_CODE_SUCCESS, list, null);
} else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
@@ -330,7 +339,7 @@
private void assertLibraryParams(LibraryParams params) {
synchronized (MockMediaLibraryService2.class) {
if (sAssertLibraryParams) {
- assertLibraryParamsEquals(sExpectedParams, params);
+ assertEqualLibraryParams(sExpectedParams, params);
}
}
}
@@ -367,9 +376,10 @@
private MediaItem2 createMediaItem(String mediaId) {
MediaMetadata2 metadata = new MediaMetadata2.Builder()
.putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
.build();
- return new MediaItem2.Builder(MediaItem2.FLAG_PLAYABLE)
- .setMediaId(mediaId)
+ return new MediaItem2.Builder()
.setMetadata(metadata)
.build();
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaBrowser2.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaBrowser2.java
index baf50de..a81a4d75 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaBrowser2.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaBrowser2.java
@@ -24,9 +24,8 @@
import androidx.annotation.Nullable;
import androidx.media2.MediaBrowser2;
import androidx.media2.MediaLibraryService2.LibraryParams;
+import androidx.media2.MediaUtils2;
import androidx.media2.SessionToken2;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
/**
* Represents remote {@link MediaBrowser2} the client app's MediaController2Service.
@@ -51,8 +50,7 @@
public void getLibraryRoot(@Nullable LibraryParams params) {
try {
- mBinder.getLibraryRoot(mControllerId,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ mBinder.getLibraryRoot(mControllerId, MediaUtils2.toParcelable(params));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call getLibraryRoot()");
}
@@ -60,8 +58,7 @@
public void subscribe(@NonNull String parentId, @Nullable LibraryParams params) {
try {
- mBinder.subscribe(mControllerId, parentId,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ mBinder.subscribe(mControllerId, parentId, MediaUtils2.toParcelable(params));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call subscribe()");
}
@@ -79,7 +76,7 @@
@Nullable LibraryParams params) {
try {
mBinder.getChildren(mControllerId, parentId, page, pageSize,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call getChildren()");
}
@@ -95,7 +92,7 @@
public void search(@NonNull String query, @Nullable LibraryParams params) {
try {
- mBinder.search(mControllerId, query, (ParcelImpl) ParcelUtils.toParcelable(params));
+ mBinder.search(mControllerId, query, MediaUtils2.toParcelable(params));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call search()");
}
@@ -105,7 +102,7 @@
@Nullable LibraryParams params) {
try {
mBinder.getSearchResult(mControllerId, query, page, pageSize,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call getSearchResult()");
}
@@ -125,7 +122,7 @@
void create(SessionToken2 token, boolean waitForConnection) {
try {
mBinder.create(true /* isBrowser */, mControllerId,
- (ParcelImpl) ParcelUtils.toParcelable(token), waitForConnection);
+ MediaUtils2.toParcelable(token), waitForConnection);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to create default browser with given token.");
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaController2.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaController2.java
index c64c83d..6781f63 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaController2.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaController2.java
@@ -37,13 +37,11 @@
import androidx.annotation.Nullable;
import androidx.media.test.lib.TestUtils;
import androidx.media2.MediaController2;
-import androidx.media2.MediaItem2;
import androidx.media2.MediaMetadata2;
+import androidx.media2.MediaUtils2;
import androidx.media2.Rating2;
import androidx.media2.SessionCommand2;
import androidx.media2.SessionToken2;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
import java.util.List;
import java.util.UUID;
@@ -93,7 +91,7 @@
public SessionToken2 getConnectedSessionToken() {
try {
- return ParcelUtils.fromParcelable(mBinder.getConnectedSessionToken(mControllerId));
+ return MediaUtils2.fromParcelable(mBinder.getConnectedSessionToken(mControllerId));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call getConnectedSessionToken()");
return null;
@@ -116,11 +114,11 @@
}
}
- public void prefetch() {
+ public void prepare() {
try {
- mBinder.prefetch(mControllerId);
+ mBinder.prepare(mControllerId);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetch()");
+ Log.e(TAG, "Failed to call prepare()");
}
}
@@ -140,10 +138,9 @@
}
}
- public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
+ public void setPlaylist(@NonNull List<String> list, @Nullable MediaMetadata2 metadata) {
try {
- mBinder.setPlaylist(mControllerId, MediaTestUtils.playlistToBundleList(list),
- metadata == null ? null : metadata.toBundle());
+ mBinder.setPlaylist(mControllerId, list, MediaUtils2.toParcelable(metadata));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call setPlaylist()");
}
@@ -158,15 +155,15 @@
public void createAndSetDummyPlaylist(int size, @Nullable MediaMetadata2 metadata) {
try {
mBinder.createAndSetDummyPlaylist(mControllerId, size,
- metadata == null ? null : metadata.toBundle());
+ MediaUtils2.toParcelable(metadata));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call createAndSetDummyPlaylist()");
}
}
- public void setMediaItem(@NonNull MediaItem2 item) {
+ public void setMediaItem(@NonNull String mediaId) {
try {
- mBinder.setMediaItem(mControllerId, item.toBundle());
+ mBinder.setMediaItem(mControllerId, mediaId);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call setMediaItem()");
}
@@ -174,32 +171,31 @@
public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
try {
- mBinder.updatePlaylistMetadata(mControllerId,
- metadata == null ? null : metadata.toBundle());
+ mBinder.updatePlaylistMetadata(mControllerId, MediaUtils2.toParcelable(metadata));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call updatePlaylistMetadata()");
}
}
- public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
+ public void addPlaylistItem(int index, @NonNull String mediaId) {
try {
- mBinder.addPlaylistItem(mControllerId, index, item.toBundle());
+ mBinder.addPlaylistItem(mControllerId, index, mediaId);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call addPlaylistItem()");
}
}
- public void removePlaylistItem(@NonNull MediaItem2 item) {
+ public void removePlaylistItem(int index) {
try {
- mBinder.removePlaylistItem(mControllerId, item.toBundle());
+ mBinder.removePlaylistItem(mControllerId, index);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call removePlaylistItem()");
}
}
- public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
+ public void replacePlaylistItem(int index, @NonNull String media) {
try {
- mBinder.replacePlaylistItem(mControllerId, index, item.toBundle());
+ mBinder.replacePlaylistItem(mControllerId, index, media);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call replacePlaylistItem()");
}
@@ -221,9 +217,9 @@
}
}
- public void skipToPlaylistItem(@NonNull MediaItem2 item) {
+ public void skipToPlaylistItem(int index) {
try {
- mBinder.skipToPlaylistItem(mControllerId, item.toBundle());
+ mBinder.skipToPlaylistItem(mControllerId, index);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call skipToPlaylistItem()");
}
@@ -285,6 +281,22 @@
}
}
+ public void skipForward() {
+ try {
+ mBinder.skipForward(mControllerId);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to call skipForward()");
+ }
+ }
+
+ public void skipBackward() {
+ try {
+ mBinder.skipBackward(mControllerId);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to call skipBackward()");
+ }
+ }
+
public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
try {
mBinder.playFromMediaId(mControllerId, mediaId, extras);
@@ -309,34 +321,33 @@
}
}
- public void prefetchFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
+ public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
try {
- mBinder.prefetchFromMediaId(mControllerId, mediaId, extras);
+ mBinder.prepareFromMediaId(mControllerId, mediaId, extras);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetchFromMediaId()");
+ Log.e(TAG, "Failed to call prepareFromMediaId()");
}
}
- public void prefetchFromSearch(@NonNull String query, @Nullable Bundle extras) {
+ public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) {
try {
- mBinder.prefetchFromSearch(mControllerId, query, extras);
+ mBinder.prepareFromSearch(mControllerId, query, extras);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetchFromSearch()");
+ Log.e(TAG, "Failed to call prepareFromSearch()");
}
}
- public void prefetchFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
+ public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
try {
- mBinder.prefetchFromUri(mControllerId, uri, extras);
+ mBinder.prepareFromUri(mControllerId, uri, extras);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetchFromUri()");
+ Log.e(TAG, "Failed to call prepareFromUri()");
}
}
public void setRating(@NonNull String mediaId, @NonNull Rating2 rating) {
try {
- mBinder.setRating(mControllerId, mediaId,
- (ParcelImpl) ParcelUtils.toParcelable(rating));
+ mBinder.setRating(mControllerId, mediaId, MediaUtils2.toParcelable(rating));
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call setRating()");
}
@@ -425,7 +436,7 @@
void create(SessionToken2 token, boolean waitForConnection) {
try {
mBinder.create(false /* isBrowser */, mControllerId,
- (ParcelImpl) ParcelUtils.toParcelable(token), waitForConnection);
+ MediaUtils2.toParcelable(token), waitForConnection);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to create default controller with given token.");
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaControllerCompat.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaControllerCompat.java
index ceb683b..4b59b66 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaControllerCompat.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/RemoteMediaControllerCompat.java
@@ -155,7 +155,7 @@
try {
mBinder.prepare(mControllerId);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetch()");
+ Log.e(TAG, "Failed to call prepare()");
}
}
@@ -163,7 +163,7 @@
try {
mBinder.prepareFromMediaId(mControllerId, mediaId, extras);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetchFromMediaId()");
+ Log.e(TAG, "Failed to call prepareFromMediaId()");
}
}
@@ -171,7 +171,7 @@
try {
mBinder.prepareFromSearch(mControllerId, query, extras);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetchFromSearch()");
+ Log.e(TAG, "Failed to call prepareFromSearch()");
}
}
@@ -179,7 +179,7 @@
try {
mBinder.prepareFromUri(mControllerId, uri, extras);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call prefetchFromUri()");
+ Log.e(TAG, "Failed to call prepareFromUri()");
}
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/TestServiceRegistry.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/TestServiceRegistry.java
index 3cfdde4..55953f0 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/TestServiceRegistry.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/TestServiceRegistry.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.fail;
import android.os.Handler;
+import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.media.test.lib.TestUtils.SyncHandler;
@@ -35,6 +36,9 @@
* It only support only one service at a time.
*/
public class TestServiceRegistry {
+ private static final String TAG = "TestServiceRegistry";
+ private static final boolean DEBUG = true;
+
@GuardedBy("TestServiceRegistry.class")
private static TestServiceRegistry sInstance;
@GuardedBy("TestServiceRegistry.class")
@@ -113,6 +117,18 @@
fail("Previous service instance is still running. Clean up manually to ensure"
+ " previously running service doesn't break current test");
}
+ if (DEBUG) {
+ Log.d(TAG, "setServiceInstance(): service=" + service);
+ if (service != null) {
+ Log.d(TAG, "setServiceInstance(): service=" + service + ", session size="
+ + service.getSessions().size());
+ for (MediaSession2 session : service.getSessions()) {
+ Log.d(TAG, " session id=" + session.getId());
+ }
+ } else {
+ Log.d(TAG, "setServiceInstance, service=" + service);
+ }
+ }
mService = service;
if (mSessionServiceCallback != null) {
mSessionServiceCallback.onCreated();
@@ -129,6 +145,13 @@
public void cleanUp() {
synchronized (TestServiceRegistry.class) {
if (mService != null) {
+ if (DEBUG) {
+ Log.d(TAG, "cleanUp(): service=" + mService + ", session size="
+ + mService.getSessions().size());
+ for (MediaSession2 session : mService.getSessions()) {
+ Log.d(TAG, " session id=" + session.getId());
+ }
+ }
// TODO(jaewan): Remove this, and override SessionService#onDestroy() to do this
List<MediaSession2> sessions = mService.getSessions();
for (int i = 0; i < sessions.size(); i++) {
@@ -139,6 +162,8 @@
// So stopSelf() isn't really needed, but just for sure.
mService.stopSelf();
mService = null;
+ } else if (DEBUG) {
+ Log.d(TAG, "cleanUp(): service=" + mService);
}
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaBrowserServiceCompatCallbackTestWithMediaBrowser2.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaBrowserServiceCompatCallbackTestWithMediaBrowser2.java
index d0669db..acee00e 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaBrowserServiceCompatCallbackTestWithMediaBrowser2.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaBrowserServiceCompatCallbackTestWithMediaBrowser2.java
@@ -18,7 +18,7 @@
import static android.support.mediacompat.testlib.util.IntentUtil.CLIENT_PACKAGE_NAME;
-import static androidx.media.test.service.MediaTestUtils.assertLibraryParamsWithBundle;
+import static androidx.media.test.service.MediaTestUtils.assertEqualLibraryParams;
import static androidx.media.test.service.MediaTestUtils.createLibraryParams;
import static org.junit.Assert.assertEquals;
@@ -88,7 +88,7 @@
Bundle rootHints) {
assertEquals(CLIENT_PACKAGE_NAME, clientPackageName);
if (rootHints.keySet().contains(testMediaId)) {
- assertLibraryParamsWithBundle(testParams, rootHints);
+ assertEqualLibraryParams(testParams, rootHints);
// This should happen because getLibraryRoot() is called with testExtras.
latch.countDown();
}
@@ -185,7 +185,7 @@
public void onLoadChildren(String parentId, Result<List<MediaItem>> result,
Bundle option) {
assertEquals(testParentId, parentId);
- assertLibraryParamsWithBundle(testParams, option);
+ assertEqualLibraryParams(testParams, option);
result.sendResult(null);
subscribeLatch.countDown();
}
@@ -210,7 +210,7 @@
@Override
public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
assertEquals(testQuery, query);
- assertLibraryParamsWithBundle(testParams, extras);
+ assertEqualLibraryParams(testParams, extras);
result.sendResult(testFullSearchResult);
latch.countDown();
}
@@ -234,7 +234,7 @@
@Override
public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
assertEquals(testQuery, query);
- assertLibraryParamsWithBundle(testParams, extras);
+ assertEqualLibraryParams(testParams, extras);
assertEquals(testPage, extras.getInt(MediaBrowserCompat.EXTRA_PAGE));
assertEquals(testPageSize, extras.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
result.sendResult(null);
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaLibrarySessionCallbackTest.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaLibrarySessionCallbackTest.java
index 92cfd89..fab1480 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaLibrarySessionCallbackTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaLibrarySessionCallbackTest.java
@@ -16,7 +16,7 @@
package androidx.media.test.service.tests;
-import static androidx.media.test.service.MediaTestUtils.assertLibraryParamsEquals;
+import static androidx.media.test.service.MediaTestUtils.assertEqualLibraryParams;
import static androidx.media2.MediaLibraryService2.LibraryResult.RESULT_CODE_SUCCESS;
import static org.junit.Assert.assertEquals;
@@ -55,7 +55,6 @@
public class MediaLibrarySessionCallbackTest extends MediaSession2TestBase {
MockPlayer mPlayer;
- RemoteMediaBrowser2 mBrowser2;
@Before
@Override
@@ -84,7 +83,7 @@
MediaSession2.ControllerInfo controller, String parentId,
LibraryParams params) {
assertEquals(testParentId, parentId);
- assertLibraryParamsEquals(testParams, params);
+ assertEqualLibraryParams(testParams, params);
latch.countDown();
return RESULT_CODE_SUCCESS;
}
@@ -97,8 +96,8 @@
service, mPlayer, sHandlerExecutor, sessionCallback)
.setId("testOnSubscribe")
.build()) {
- mBrowser2 = createRemoteBrowser2(session.getToken());
- mBrowser2.subscribe(testParentId, testParams);
+ RemoteMediaBrowser2 browser = createRemoteBrowser2(session.getToken());
+ browser.subscribe(testParentId, testParams);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -127,8 +126,8 @@
service, mPlayer, sHandlerExecutor, sessionCallback)
.setId("testOnUnsubscribe")
.build()) {
- mBrowser2 = createRemoteBrowser2(session.getToken());
- mBrowser2.unsubscribe(testParentId);
+ RemoteMediaBrowser2 browser = createRemoteBrowser2(session.getToken());
+ browser.unsubscribe(testParentId);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaMetadata2Test.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaMetadata2Test.java
index 77458d6..0db3899 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaMetadata2Test.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaMetadata2Test.java
@@ -19,6 +19,8 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.os.Build;
import android.os.Bundle;
@@ -40,22 +42,35 @@
public class MediaMetadata2Test {
@Test
public void testBuilder() {
- final Bundle extras = new Bundle();
- extras.putString("MediaMetadata2Test", "testBuilder");
final String title = "title";
final long discNumber = 10;
final Rating2 rating = new ThumbRating2(true);
Builder builder = new Builder();
- builder.setExtras(extras);
builder.putString(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE, title);
builder.putLong(MediaMetadata2.METADATA_KEY_DISC_NUMBER, discNumber);
builder.putRating(MediaMetadata2.METADATA_KEY_USER_RATING, rating);
MediaMetadata2 metadata = builder.build();
- assertTrue(TestUtils.equals(extras, metadata.getExtras()));
assertEquals(title, metadata.getString(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE));
assertEquals(discNumber, metadata.getLong(MediaMetadata2.METADATA_KEY_DISC_NUMBER));
assertEquals(rating, metadata.getRating(MediaMetadata2.METADATA_KEY_USER_RATING));
}
+
+ @Test
+ public void testSetExtra() {
+ final Bundle extras = new Bundle();
+ extras.putString("MediaMetadata2Test", "testBuilder");
+
+ Builder builder = new Builder();
+ try {
+ builder.putLong(MediaMetadata2.METADATA_KEY_EXTRAS, 1);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ builder.setExtras(extras);
+ MediaMetadata2 metadata = builder.build();
+ assertTrue(TestUtils.equals(extras, metadata.getExtras()));
+ }
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2CallbackTest.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2CallbackTest.java
index 57bc51c..c5437b7 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2CallbackTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2CallbackTest.java
@@ -36,6 +36,7 @@
import androidx.media.test.service.MockPlayer;
import androidx.media.test.service.RemoteMediaController2;
import androidx.media2.MediaItem2;
+import androidx.media2.MediaMetadata2;
import androidx.media2.MediaSession2;
import androidx.media2.MediaSession2.ControllerInfo;
import androidx.media2.Rating2;
@@ -67,7 +68,6 @@
private static final String TAG = "MediaSession2CallbackTest";
MockPlayer mPlayer;
- RemoteMediaController2 mController2;
@Before
@Override
@@ -92,16 +92,16 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnCommandRequest")
.build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.pause();
+ controller.pause();
assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertFalse(mPlayer.mPauseCalled);
assertEquals(1, callback.commands.size());
assertEquals(SessionCommand2.COMMAND_CODE_PLAYER_PAUSE,
(long) callback.commands.get(0).getCommandCode());
- mController2.play();
+ controller.play();
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mPlayCalled);
assertFalse(mPlayer.mPauseCalled);
@@ -117,15 +117,15 @@
prepareLooper();
mPlayer = new MockPlayer(1);
- final List<MediaItem2> list = MediaTestUtils.createPlaylist(3);
+ final List<String> list = MediaTestUtils.createMediaIds(3);
final List<MediaItem2> convertedList = MediaTestUtils.createPlaylist(list.size());
final MockOnCommandCallback callback = new MockOnCommandCallback() {
@Override
public MediaItem2 onCreateMediaItem(MediaSession2 session,
- ControllerInfo controller, MediaItem2 item) {
+ ControllerInfo controller, String mediaId) {
for (int i = 0; i < list.size(); i++) {
- if (Objects.equals(item, list.get(i))) {
+ if (Objects.equals(mediaId, list.get(i))) {
return convertedList.get(i);
}
}
@@ -137,15 +137,19 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnCreateMediaItem")
.build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.setPlaylist(list, null);
+ controller.setPlaylist(list, null);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
List<MediaItem2> playerList = mPlayer.getPlaylist();
assertEquals(convertedList.size(), playerList.size());
for (int i = 0; i < playerList.size(); i++) {
- assertEquals(convertedList.get(i), playerList.get(i));
+ String expected = convertedList.get(i).getMetadata().getString(
+ MediaMetadata2.METADATA_KEY_MEDIA_ID);
+ String actual = playerList.get(i).getMetadata().getString(
+ MediaMetadata2.METADATA_KEY_MEDIA_ID);
+ assertEquals(expected, actual);
}
}
}
@@ -186,8 +190,8 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnCustomCommand")
.build()) {
- mController2 = createRemoteController2(session.getToken());
- mController2.sendCustomCommand(testCommand, testArgs);
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
+ controller.sendCustomCommand(testCommand, testArgs);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -207,8 +211,8 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnFastForward").build()) {
- mController2 = createRemoteController2(session.getToken());
- mController2.fastForward();
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
+ controller.fastForward();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -228,8 +232,50 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnRewind").build()) {
- mController2 = createRemoteController2(session.getToken());
- mController2.rewind();
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
+ controller.rewind();
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ @Test
+ public void testOnSkipForward() throws InterruptedException {
+ prepareLooper();
+ final CountDownLatch latch = new CountDownLatch(1);
+ final MediaSession2.SessionCallback callback = new MediaSession2.SessionCallback() {
+ @Override
+ public int onSkipForward(MediaSession2 session, ControllerInfo controller) {
+ assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
+ latch.countDown();
+ return RESULT_CODE_SUCCESS;
+ }
+ };
+ try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, callback)
+ .setId("testOnSkipForward").build()) {
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
+ controller.skipForward();
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ @Test
+ public void testOnSkipBackward() throws InterruptedException {
+ prepareLooper();
+ final CountDownLatch latch = new CountDownLatch(1);
+ final MediaSession2.SessionCallback callback = new MediaSession2.SessionCallback() {
+ @Override
+ public int onSkipBackward(MediaSession2 session, ControllerInfo controller) {
+ assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
+ latch.countDown();
+ return RESULT_CODE_SUCCESS;
+ }
+ };
+ try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, callback)
+ .setId("testOnSkipBackward").build()) {
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
+ controller.skipBackward();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -254,9 +300,9 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnPlayFromSearch").build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.playFromSearch(testQuery, testExtras);
+ controller.playFromSearch(testQuery, testExtras);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -282,9 +328,9 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnPlayFromUri")
.build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.playFromUri(testUri, testExtras);
+ controller.playFromUri(testUri, testExtras);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -309,9 +355,9 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnPlayFromMediaId").build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.playFromMediaId(testMediaId, testExtras);
+ controller.playFromMediaId(testMediaId, testExtras);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -324,7 +370,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final MediaSession2.SessionCallback callback = new MediaSession2.SessionCallback() {
@Override
- public int onPrefetchFromSearch(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
String query, Bundle extras) {
assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
assertEquals(testQuery, query);
@@ -336,9 +382,9 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnPrepareFromSearch").build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.prefetchFromSearch(testQuery, testExtras);
+ controller.prepareFromSearch(testQuery, testExtras);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -351,7 +397,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final MediaSession2.SessionCallback callback = new MediaSession2.SessionCallback() {
@Override
- public int onPrefetchFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
+ public int onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
Bundle extras) {
assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
assertEquals(testUri, uri);
@@ -363,9 +409,9 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnPrepareFromUri").build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.prefetchFromUri(testUri, testExtras);
+ controller.prepareFromUri(testUri, testExtras);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -378,7 +424,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final MediaSession2.SessionCallback callback = new MediaSession2.SessionCallback() {
@Override
- public int onPrefetchFromMediaId(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
String mediaId, Bundle extras) {
assertEquals(CLIENT_PACKAGE_NAME, controller.getPackageName());
assertEquals(testMediaId, mediaId);
@@ -390,9 +436,9 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnPrepareFromMediaId").build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.prefetchFromMediaId(testMediaId, testExtras);
+ controller.prepareFromMediaId(testMediaId, testExtras);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -420,9 +466,9 @@
try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnSetRating").build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
- mController2.setRating(testMediaId, testRating);
+ controller.setRating(testMediaId, testRating);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -443,10 +489,10 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnSubscribeRoutesInfo")
.build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
callback.resetLatchCount(1);
- mController2.subscribeRoutesInfo();
+ controller.subscribeRoutesInfo();
assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -467,10 +513,10 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnUnsubscribeRoutesInfo")
.build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
callback.resetLatchCount(1);
- mController2.unsubscribeRoutesInfo();
+ controller.unsubscribeRoutesInfo();
assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -493,10 +539,10 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testOnSelectRoute")
.build()) {
- mController2 = createRemoteController2(session.getToken());
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
callback.resetLatchCount(1);
- mController2.selectRoute(testRoute);
+ controller.selectRoute(testRoute);
assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -519,7 +565,7 @@
return super.onConnect(session, controller);
}
}).build()) {
- mController2 = createRemoteController2(
+ RemoteMediaController2 controller = createRemoteController2(
session.getToken(), false /* waitForConnection */);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -540,8 +586,8 @@
latch.countDown();
}
}).build()) {
- mController2 = createRemoteController2(session.getToken());
- mController2.close();
+ RemoteMediaController2 controller = createRemoteController2(session.getToken());
+ controller.close();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2LegacyCallbackTest.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2LegacyCallbackTest.java
index b619dfe..bc56b7d 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2LegacyCallbackTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2LegacyCallbackTest.java
@@ -113,6 +113,12 @@
}
return null;
}
+
+ @Override
+ public MediaItem2 onCreateMediaItem(MediaSession2 session,
+ ControllerInfo controller, String mediaId) {
+ return MediaTestUtils.createMediaItem(mediaId);
+ }
})
.setSessionActivity(mIntent)
.setId(TAG).build();
@@ -128,6 +134,10 @@
if (mSession != null) {
mSession.close();
}
+ if (mController != null) {
+ mController.cleanUp();
+ mController = null;
+ }
}
@Test
@@ -650,7 +660,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@Override
- public int onPrefetchFromSearch(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
String query, Bundle extras) {
assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
assertEquals(request, query);
@@ -678,7 +688,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@Override
- public int onPrefetchFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
+ public int onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
Bundle extras) {
assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
assertEquals(request, uri);
@@ -706,7 +716,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@Override
- public int onPrefetchFromMediaId(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
String mediaId, Bundle extras) {
assertEquals(EXPECTED_CONTROLLER_PACKAGE_NAME, controller.getPackageName());
assertEquals(request, mediaId);
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2_PermissionTest.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2_PermissionTest.java
index 252236d..d361b6a 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2_PermissionTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSession2_PermissionTest.java
@@ -35,11 +35,13 @@
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI;
-import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID;
-import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH;
-import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_URI;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_REWIND;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SET_RATING;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SKIP_BACKWARD;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SKIP_FORWARD;
import static androidx.media2.SessionCommand2.COMMAND_CODE_VOLUME_ADJUST_VOLUME;
import static androidx.media2.SessionCommand2.COMMAND_CODE_VOLUME_SET_VOLUME;
@@ -222,13 +224,12 @@
@Test
public void testSkipToPlaylistItem() throws InterruptedException {
prepareLooper();
- final MediaItem2 testItem = MediaTestUtils.createMediaItemWithMetadata();
testOnCommandRequest(
COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
new PermissionTestTask() {
@Override
public void run(RemoteMediaController2 controller) {
- controller.skipToPlaylistItem(testItem);
+ controller.skipToPlaylistItem(0);
}
});
}
@@ -236,7 +237,7 @@
@Test
public void testSetPlaylist() throws InterruptedException {
prepareLooper();
- final List<MediaItem2> list = MediaTestUtils.createPlaylist(2);
+ final List<String> list = MediaTestUtils.createMediaIds(2);
testOnCommandRequest(COMMAND_CODE_PLAYER_SET_PLAYLIST, new PermissionTestTask() {
@Override
public void run(RemoteMediaController2 controller) {
@@ -248,11 +249,11 @@
@Test
public void testSetMediaItem() throws InterruptedException {
prepareLooper();
- final MediaItem2 item = MediaTestUtils.createMediaItemWithMetadata();
+ final String testMediaId = "testSetMediaItem";
testOnCommandRequest(COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, new PermissionTestTask() {
@Override
public void run(RemoteMediaController2 controller) {
- controller.setMediaItem(item);
+ controller.setMediaItem(testMediaId);
}
});
}
@@ -272,11 +273,11 @@
@Test
public void testAddPlaylistItem() throws InterruptedException {
prepareLooper();
- final MediaItem2 testItem = MediaTestUtils.createMediaItemWithMetadata();
+ final String testMediaId = "testAddPlaylistItem";
testOnCommandRequest(COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM, new PermissionTestTask() {
@Override
public void run(RemoteMediaController2 controller) {
- controller.addPlaylistItem(0, testItem);
+ controller.addPlaylistItem(0, testMediaId);
}
});
}
@@ -289,7 +290,7 @@
new PermissionTestTask() {
@Override
public void run(RemoteMediaController2 controller) {
- controller.removePlaylistItem(testItem);
+ controller.removePlaylistItem(0);
}
});
}
@@ -297,12 +298,12 @@
@Test
public void testReplacePlaylistItem() throws InterruptedException {
prepareLooper();
- final MediaItem2 testItem = MediaTestUtils.createMediaItemWithMetadata();
+ final String testMediaId = "testReplacePlaylistItem";
testOnCommandRequest(COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
new PermissionTestTask() {
@Override
public void run(RemoteMediaController2 controller) {
- controller.replacePlaylistItem(0, testItem);
+ controller.replacePlaylistItem(0, testMediaId);
}
});
}
@@ -364,6 +365,40 @@
}
@Test
+ public void testSkipForward() throws InterruptedException {
+ prepareLooper();
+ createSessionWithAllowedActions(
+ createCommandGroupWith(COMMAND_CODE_SESSION_SKIP_FORWARD));
+ createRemoteController2(mSession.getToken()).skipForward();
+
+ assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mCallback.mOnSkipForwardCalled);
+
+ createSessionWithAllowedActions(
+ createCommandGroupWithout(COMMAND_CODE_SESSION_SKIP_FORWARD));
+ createRemoteController2(mSession.getToken()).skipForward();
+ assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertFalse(mCallback.mOnSkipForwardCalled);
+ }
+
+ @Test
+ public void testSkipBackward() throws InterruptedException {
+ prepareLooper();
+ createSessionWithAllowedActions(
+ createCommandGroupWith(COMMAND_CODE_SESSION_SKIP_BACKWARD));
+ createRemoteController2(mSession.getToken()).skipBackward();
+
+ assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mCallback.mOnSkipBackwardCalled);
+
+ createSessionWithAllowedActions(
+ createCommandGroupWithout(COMMAND_CODE_SESSION_SKIP_BACKWARD));
+ createRemoteController2(mSession.getToken()).skipBackward();
+ assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertFalse(mCallback.mOnSkipBackwardCalled);
+ }
+
+ @Test
public void testPlayFromMediaId() throws InterruptedException {
prepareLooper();
final String mediaId = "testPlayFromMediaId";
@@ -428,8 +463,8 @@
prepareLooper();
final String mediaId = "testPrepareFromMediaId";
createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID));
- createRemoteController2(mSession.getToken()).prefetchFromMediaId(mediaId, null);
+ createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID));
+ createRemoteController2(mSession.getToken()).prepareFromMediaId(mediaId, null);
assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
@@ -437,8 +472,8 @@
assertNull(mCallback.mExtras);
createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID));
- createRemoteController2(mSession.getToken()).prefetchFromMediaId(mediaId, null);
+ createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID));
+ createRemoteController2(mSession.getToken()).prepareFromMediaId(mediaId, null);
assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertFalse(mCallback.mOnPrepareFromMediaIdCalled);
}
@@ -446,10 +481,10 @@
@Test
public void testPrepareFromUri() throws InterruptedException {
prepareLooper();
- final Uri uri = Uri.parse("prefetch://from.uri");
+ final Uri uri = Uri.parse("prepare://from.uri");
createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_SESSION_PREFETCH_FROM_URI));
- createRemoteController2(mSession.getToken()).prefetchFromUri(uri, null);
+ createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_URI));
+ createRemoteController2(mSession.getToken()).prepareFromUri(uri, null);
assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mCallback.mOnPrepareFromUriCalled);
@@ -457,8 +492,8 @@
assertNull(mCallback.mExtras);
createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_SESSION_PREFETCH_FROM_URI));
- createRemoteController2(mSession.getToken()).prefetchFromUri(uri, null);
+ createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_URI));
+ createRemoteController2(mSession.getToken()).prepareFromUri(uri, null);
assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertFalse(mCallback.mOnPrepareFromUriCalled);
}
@@ -468,8 +503,8 @@
prepareLooper();
final String query = "testPrepareFromSearch";
createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH));
- createRemoteController2(mSession.getToken()).prefetchFromSearch(query, null);
+ createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
+ createRemoteController2(mSession.getToken()).prepareFromSearch(query, null);
assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mCallback.mOnPrepareFromSearchCalled);
@@ -477,8 +512,8 @@
assertNull(mCallback.mExtras);
createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH));
- createRemoteController2(mSession.getToken()).prefetchFromSearch(query, null);
+ createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
+ createRemoteController2(mSession.getToken()).prepareFromSearch(query, null);
assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertFalse(mCallback.mOnPrepareFromSearchCalled);
}
@@ -509,10 +544,10 @@
prepareLooper();
final String query = "testChangingPermissionWithSetAllowedCommands";
createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH));
+ createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
RemoteMediaController2 controller = createRemoteController2(mSession.getToken());
- controller.prefetchFromSearch(query, null);
+ controller.prepareFromSearch(query, null);
assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mCallback.mOnPrepareFromSearchCalled);
assertEquals(query, mCallback.mQuery);
@@ -521,10 +556,10 @@
// Change allowed commands.
mSession.setAllowedCommands(getTestControllerInfo(),
- createCommandGroupWithout(COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH));
+ createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
Thread.sleep(TIMEOUT_MS);
- controller.prefetchFromSearch(query, null);
+ controller.prepareFromSearch(query, null);
assertFalse(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -564,6 +599,8 @@
public boolean mOnPrepareFromUriCalled;
public boolean mOnFastForwardCalled;
public boolean mOnRewindCalled;
+ public boolean mOnSkipForwardCalled;
+ public boolean mOnSkipBackwardCalled;
public boolean mOnSetRatingCalled;
@@ -619,6 +656,22 @@
}
@Override
+ public int onSkipForward(MediaSession2 session, ControllerInfo controller) {
+ assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
+ mOnSkipForwardCalled = true;
+ mCountDownLatch.countDown();
+ return RESULT_CODE_SUCCESS;
+ }
+
+ @Override
+ public int onSkipBackward(MediaSession2 session, ControllerInfo controller) {
+ assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
+ mOnSkipBackwardCalled = true;
+ mCountDownLatch.countDown();
+ return RESULT_CODE_SUCCESS;
+ }
+
+ @Override
public int onPlayFromMediaId(MediaSession2 session, ControllerInfo controller,
String mediaId, Bundle extras) {
assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
@@ -652,7 +705,7 @@
}
@Override
- public int onPrefetchFromMediaId(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
String mediaId, Bundle extras) {
assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
mOnPrepareFromMediaIdCalled = true;
@@ -663,7 +716,7 @@
}
@Override
- public int onPrefetchFromSearch(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
String query, Bundle extras) {
assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
mOnPrepareFromSearchCalled = true;
@@ -674,7 +727,7 @@
}
@Override
- public int onPrefetchFromUri(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromUri(MediaSession2 session, ControllerInfo controller,
Uri uri, Bundle extras) {
assertTrue(TextUtils.equals(CLIENT_PACKAGE_NAME, controller.getPackageName()));
mOnPrepareFromUriCalled = true;
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionCompatCallbackTestWithMediaController2.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionCompatCallbackTestWithMediaController2.java
index 7b34e14..0998f10 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionCompatCallbackTestWithMediaController2.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionCompatCallbackTestWithMediaController2.java
@@ -57,13 +57,13 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Tests {@link MediaController2}.
@@ -81,7 +81,6 @@
MediaSessionCompat mSession;
MediaSessionCallback mSessionCallback;
AudioManager mAudioManager;
- RemoteMediaController2 mController;
@Before
@Override
@@ -108,35 +107,32 @@
mSession.release();
mSession = null;
}
-
- if (mController != null) {
- mController.close();
- mController = null;
- }
}
- private void createControllerAndWaitConnection() throws Exception {
+ private RemoteMediaController2 createControllerAndWaitConnection() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<SessionToken2> sessionToken2 = new AtomicReference<>();
SessionToken2.createSessionToken2(mContext, mSession.getSessionToken(),
sHandlerExecutor, new SessionToken2.OnSessionToken2CreatedListener() {
@Override
public void onSessionToken2Created(
MediaSessionCompat.Token token, SessionToken2 token2) {
assertTrue(token2.isLegacySession());
- mController = new RemoteMediaController2(mContext, token2, true);
+ sessionToken2.set(token2);
latch.countDown();
}
});
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ return createRemoteController2(sessionToken2.get(), true);
}
@Test
public void testPlay() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.play();
+ controller.play();
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(1, mSessionCallback.mOnPlayCalledCount);
}
@@ -144,10 +140,10 @@
@Test
public void testPause() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.pause();
+ controller.pause();
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPauseCalled);
}
@@ -155,10 +151,10 @@
@Test
public void testPrepare() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.prefetch();
+ controller.prepare();
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPrepareCalled);
}
@@ -166,11 +162,11 @@
@Test
public void testSeekTo() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
final long seekPosition = 12125L;
- mController.seekTo(seekPosition);
+ controller.seekTo(seekPosition);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertTrue(mSessionCallback.mOnSeekToCalled);
assertEquals(seekPosition, mSessionCallback.mSeekPosition);
@@ -181,21 +177,20 @@
prepareLooper();
final List<MediaItem2> testList = MediaTestUtils.createPlaylist(2);
final List<QueueItem> testQueue = MediaUtils2.convertToQueueItemList(testList);
- final MediaItem2 testMediaItem2ToAdd = MediaTestUtils.createMediaItemWithMetadata();
+ final String testMediaId = "testAddPlaylistItem";
mSession.setQueue(testQueue);
mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
final int testIndex = 1;
- mController.addPlaylistItem(testIndex, testMediaItem2ToAdd);
+ controller.addPlaylistItem(testIndex, testMediaId);
assertTrue(mSessionCallback.await(TIMEOUT_MS));
assertTrue(mSessionCallback.mOnAddQueueItemAtCalled);
assertEquals(testIndex, mSessionCallback.mQueueIndex);
assertNotNull(mSessionCallback.mQueueDescriptionForAdd);
- assertEquals(testMediaItem2ToAdd.getMediaId(),
- mSessionCallback.mQueueDescriptionForAdd.getMediaId());
+ assertEquals(testMediaId, mSessionCallback.mQueueDescriptionForAdd.getMediaId());
}
@Test
@@ -206,10 +201,10 @@
mSession.setQueue(testQueue);
mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
final MediaItem2 itemToRemove = testList.get(1);
- mController.removePlaylistItem(itemToRemove);
+ controller.removePlaylistItem(1);
assertTrue(mSessionCallback.await(TIMEOUT_MS));
assertTrue(mSessionCallback.mOnRemoveQueueItemCalled);
@@ -225,14 +220,14 @@
// replace = remove + add
final List<MediaItem2> testList = MediaTestUtils.createPlaylist(2);
final List<QueueItem> testQueue = MediaUtils2.convertToQueueItemList(testList);
- final MediaItem2 testMediaItem2ToReplace = MediaTestUtils.createMediaItemWithMetadata();
+ final String testMediaId = "testReplacePlaylistItem";
mSession.setQueue(testQueue);
mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(2);
- mController.replacePlaylistItem(1, testMediaItem2ToReplace);
+ controller.replacePlaylistItem(testReplaceIndex, testMediaId);
assertTrue(mSessionCallback.await(TIMEOUT_MS));
assertTrue(mSessionCallback.mOnRemoveQueueItemCalled);
assertTrue(mSessionCallback.mOnAddQueueItemAtCalled);
@@ -242,17 +237,16 @@
mSessionCallback.mQueueDescriptionForRemove.getMediaId());
assertNotNull(mSessionCallback.mQueueDescriptionForAdd);
- assertEquals(testMediaItem2ToReplace.getMediaId(),
- mSessionCallback.mQueueDescriptionForAdd.getMediaId());
+ assertEquals(testMediaId, mSessionCallback.mQueueDescriptionForAdd.getMediaId());
}
@Test
public void testSkipToPreviousItem() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.skipToPreviousItem();
+ controller.skipToPreviousItem();
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertTrue(mSessionCallback.mOnSkipToPreviousCalled);
}
@@ -260,31 +254,31 @@
@Test
public void testSkipToNextItem() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.skipToNextItem();
+ controller.skipToNextItem();
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertTrue(mSessionCallback.mOnSkipToNextCalled);
}
- //@Test see: b/110738672
+ @Test
public void testSkipToPlaylistItem() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+
+ final int testSkipToIndex = 1;
+ final List<MediaItem2> testList = MediaTestUtils.createPlaylist(2);
+ final List<QueueItem> testQueue = MediaUtils2.convertToQueueItemList(testList);
+
+ mSession.setQueue(testQueue);
+ mSession.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
+
mSessionCallback.reset(1);
-
- final long queueItemId = 1;
- final QueueItem queueItem = new QueueItem(
- MediaUtils2.convertToMediaMetadataCompat(MediaTestUtils.createMetadata())
- .getDescription(),
- queueItemId);
- final MediaItem2 mediaItem2 = MediaUtils2.convertToMediaItem2(queueItem);
-
- mController.skipToPlaylistItem(mediaItem2);
+ controller.skipToPlaylistItem(testSkipToIndex);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertTrue(mSessionCallback.mOnSkipToQueueItemCalled);
- assertEquals(queueItemId, mSessionCallback.mQueueItemId);
+ assertEquals(testQueue.get(testSkipToIndex).getQueueId(), mSessionCallback.mQueueItemId);
}
@Test
@@ -293,10 +287,10 @@
final int testShuffleMode = SessionPlayer2.SHUFFLE_MODE_GROUP;
mSession.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.setShuffleMode(testShuffleMode);
+ controller.setShuffleMode(testShuffleMode);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertTrue(mSessionCallback.mOnSetShuffleModeCalled);
assertEquals(testShuffleMode, mSessionCallback.mShuffleMode);
@@ -308,10 +302,10 @@
final int testRepeatMode = SessionPlayer2.REPEAT_MODE_ALL;
mSession.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.setRepeatMode(testRepeatMode);
+ controller.setRepeatMode(testRepeatMode);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertTrue(mSessionCallback.mOnSetRepeatModeCalled);
assertEquals(testRepeatMode, mSessionCallback.mRepeatMode);
@@ -326,10 +320,10 @@
TestVolumeProvider volumeProvider =
new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
mSession.setPlaybackToRemote(volumeProvider);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
final int targetVolume = 50;
- mController.setVolumeTo(targetVolume, 0 /* flags */);
+ controller.setVolumeTo(targetVolume, 0 /* flags */);
assertTrue(volumeProvider.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(volumeProvider.mSetVolumeToCalled);
assertEquals(targetVolume, volumeProvider.mVolume);
@@ -344,10 +338,10 @@
TestVolumeProvider volumeProvider =
new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
mSession.setPlaybackToRemote(volumeProvider);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
final int direction = AudioManager.ADJUST_RAISE;
- mController.adjustVolume(direction, 0 /* flags */);
+ controller.adjustVolume(direction, 0 /* flags */);
assertTrue(volumeProvider.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(volumeProvider.mAdjustVolumeCalled);
assertEquals(direction, volumeProvider.mDirection);
@@ -371,12 +365,12 @@
}
// Set stream of the session.
mSession.setPlaybackToLocal(stream);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
final int originalVolume = mAudioManager.getStreamVolume(stream);
final int targetVolume = originalVolume == minVolume
? originalVolume + 1 : originalVolume - 1;
- mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
+ controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
new PollingCheck(TIMEOUT_MS) {
@Override
protected boolean check() {
@@ -406,14 +400,14 @@
}
// Set stream of the session.
mSession.setPlaybackToLocal(stream);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
final int originalVolume = mAudioManager.getStreamVolume(stream);
final int direction = originalVolume == minVolume
? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
final int targetVolume = originalVolume + direction;
- mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
+ controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
new PollingCheck(TIMEOUT_MS) {
@Override
protected boolean check() {
@@ -432,10 +426,10 @@
final Bundle testArgs = new Bundle();
testArgs.putString("args", "test_args");
final SessionCommand2 testCommand = new SessionCommand2(command, null);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.sendCustomCommand(testCommand, testArgs);
+ controller.sendCustomCommand(testCommand, testArgs);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnCommandCalled);
assertEquals(command, mSessionCallback.mCommand);
@@ -445,10 +439,10 @@
@Test
public void testFastForward() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.fastForward();
+ controller.fastForward();
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnFastForwardCalled);
}
@@ -456,10 +450,10 @@
@Test
public void testRewind() throws Exception {
prepareLooper();
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.rewind();
+ controller.rewind();
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnRewindCalled);
}
@@ -470,10 +464,10 @@
final String request = "random query";
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.playFromSearch(request, bundle);
+ controller.playFromSearch(request, bundle);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPlayFromSearchCalled);
assertEquals(request, mSessionCallback.mQuery);
@@ -486,10 +480,10 @@
final Uri request = Uri.parse("foo://boo");
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.playFromUri(request, bundle);
+ controller.playFromUri(request, bundle);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPlayFromUriCalled);
assertEquals(request, mSessionCallback.mUri);
@@ -502,10 +496,10 @@
final String request = "media_id";
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.playFromMediaId(request, bundle);
+ controller.playFromMediaId(request, bundle);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPlayFromMediaIdCalled);
assertEquals(request, mSessionCallback.mMediaId);
@@ -513,16 +507,15 @@
}
@Test
- @Ignore("b/110738672")
public void testPrepareFromSearch() throws Exception {
prepareLooper();
final String request = "random query";
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.prefetchFromSearch(request, bundle);
+ controller.prepareFromSearch(request, bundle);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPrepareFromSearchCalled);
assertEquals(request, mSessionCallback.mQuery);
@@ -535,10 +528,10 @@
final Uri request = Uri.parse("foo://boo");
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.prefetchFromUri(request, bundle);
+ controller.prepareFromUri(request, bundle);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPrepareFromUriCalled);
assertEquals(request, mSessionCallback.mUri);
@@ -551,10 +544,10 @@
final String request = "media_id";
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.prefetchFromMediaId(request, bundle);
+ controller.prepareFromMediaId(request, bundle);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertEquals(true, mSessionCallback.mOnPrepareFromMediaIdCalled);
assertEquals(request, mSessionCallback.mMediaId);
@@ -570,10 +563,10 @@
final MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId).build();
mSession.setMetadata(metadata);
- createControllerAndWaitConnection();
+ RemoteMediaController2 controller = createControllerAndWaitConnection();
mSessionCallback.reset(1);
- mController.setRating(mediaId, rating2);
+ controller.setRating(mediaId, rating2);
assertTrue(mSessionCallback.await(TIME_OUT_MS));
assertTrue(mSessionCallback.mOnSetRatingCalled);
assertEquals(rating2, MediaUtils2.convertToRating2(mSessionCallback.mRating));
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2NotificationTest.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2NotificationTest.java
index 9a576a6..f3347c3 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2NotificationTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2NotificationTest.java
@@ -94,22 +94,23 @@
TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
// Create a controller to start the service.
- RemoteMediaController2 controller = new RemoteMediaController2(
- mContext, new SessionToken2(mContext, MOCK_MEDIA_SESSION_SERVICE), true);
+ RemoteMediaController2 controller = createRemoteController2(
+ new SessionToken2(mContext, MOCK_MEDIA_SESSION_SERVICE), true);
// Set current media item.
final String mediaId = "testMediaId";
Bitmap albumArt = BitmapFactory.decodeResource(mContext.getResources(),
android.support.mediacompat.service.R.drawable.big_buck_bunny);
MediaMetadata2 metadata = new MediaMetadata2.Builder()
- .putText(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
- .putText(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
- .putText(MediaMetadata2.METADATA_KEY_ARTIST, "Test Artist Name")
- .putBitmap(MediaMetadata2.METADATA_KEY_ALBUM_ART, albumArt)
- .build();
- mPlayer.mCurrentMediaItem = new MediaItem2.Builder(MediaItem2.FLAG_PLAYABLE)
+ .putText(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+ .putText(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
+ .putText(MediaMetadata2.METADATA_KEY_ARTIST, "Test Artist Name")
+ .putBitmap(MediaMetadata2.METADATA_KEY_ALBUM_ART, albumArt)
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE, MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
+ .build();
+ mPlayer.mCurrentMediaItem = new MediaItem2.Builder()
.setMetadata(metadata)
- .setMediaId(mediaId)
.build();
// Notification should be shown. Clicking play/pause button will change the player state.
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2Test.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2Test.java
index cd4b5d4..fd1c313 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2Test.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/MediaSessionService2Test.java
@@ -82,8 +82,8 @@
}
});
- RemoteMediaController2 controller1 = new RemoteMediaController2(mContext, mToken, true);
- RemoteMediaController2 controller2 = new RemoteMediaController2(mContext, mToken, true);
+ RemoteMediaController2 controller1 = createRemoteController2(mToken, true);
+ RemoteMediaController2 controller2 = createRemoteController2(mToken, true);
assertNotEquals(controller1.getConnectedSessionToken(),
controller2.getConnectedSessionToken());
@@ -108,8 +108,8 @@
}
});
- RemoteMediaController2 controller1 = new RemoteMediaController2(mContext, mToken, true);
- RemoteMediaController2 controller2 = new RemoteMediaController2(mContext, mToken, true);
+ RemoteMediaController2 controller1 = createRemoteController2(mToken, true);
+ RemoteMediaController2 controller2 = createRemoteController2(mToken, true);
controller1.close();
controller2.close();
@@ -146,8 +146,8 @@
}
});
- RemoteMediaController2 controller1 = new RemoteMediaController2(mContext, mToken, true);
- RemoteMediaController2 controller2 = new RemoteMediaController2(mContext, mToken, true);
+ RemoteMediaController2 controller1 = createRemoteController2(mToken, true);
+ RemoteMediaController2 controller2 = createRemoteController2(mToken, true);
controller1.close();
controller2.close();
@@ -157,7 +157,7 @@
@Test
public void testGetSessions() throws InterruptedException {
prepareLooper();
- RemoteMediaController2 controller = new RemoteMediaController2(mContext, mToken, true);
+ RemoteMediaController2 controller = createRemoteController2(mToken, true);
MediaSessionService2 service = TestServiceRegistry.getInstance().getServiceInstance();
try (MediaSession2 session = new MediaSession2.Builder(mContext, new MockPlayer(0))
.setId("testGetSessions")
@@ -177,7 +177,7 @@
@Test
public void testAddSessions_removedWhenClose() throws InterruptedException {
prepareLooper();
- RemoteMediaController2 controller = new RemoteMediaController2(mContext, mToken, true);
+ RemoteMediaController2 controller = createRemoteController2(mToken, true);
MediaSessionService2 service = TestServiceRegistry.getInstance().getServiceInstance();
try (MediaSession2 session = new MediaSession2.Builder(mContext, new MockPlayer(0))
.setId("testAddSessions_removedWhenClose")
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/SessionPlayerTest.java b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/SessionPlayerTest.java
index fcdd8e6b..fab65a6 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/SessionPlayerTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/androidx/media/test/service/tests/SessionPlayerTest.java
@@ -34,6 +34,7 @@
import androidx.media2.MediaItem2;
import androidx.media2.MediaMetadata2;
import androidx.media2.MediaSession2;
+import androidx.media2.MediaSession2.ControllerInfo;
import androidx.media2.SessionCommandGroup2;
import androidx.media2.SessionPlayer2;
import androidx.test.filters.LargeTest;
@@ -77,6 +78,12 @@
}
return null;
}
+
+ @Override
+ public MediaItem2 onCreateMediaItem(MediaSession2 session,
+ ControllerInfo controller, String mediaId) {
+ return MediaTestUtils.createMediaItem(mediaId);
+ }
}).build();
// Create a default MediaController2 in client app.
@@ -137,7 +144,7 @@
@Test
public void testPrepareByController() {
- mController2.prefetch();
+ mController2.prepare();
try {
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
@@ -197,7 +204,7 @@
@Test
public void testSetPlaylistByController() throws InterruptedException {
- final List<MediaItem2> list = MediaTestUtils.createPlaylist(2);
+ final List<String> list = MediaTestUtils.createMediaIds(2);
mController2.setPlaylist(list, null /* metadata */);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -207,8 +214,7 @@
assertNotNull(mPlayer.mPlaylist);
assertEquals(list.size(), mPlayer.mPlaylist.size());
for (int i = 0; i < list.size(); i++) {
- // MediaController2.setPlaylist does not ensure the equality of the items.
- assertEquals(list.get(i).getMediaId(), mPlayer.mPlaylist.get(i).getMediaId());
+ assertEquals(list.get(i), mPlayer.mPlaylist.get(i).getMediaId());
}
}
@@ -265,15 +271,15 @@
@Test
public void testAddPlaylistItemByController() throws InterruptedException {
final int testIndex = 12;
- final MediaItem2 testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
+ final String testMediaId = "testAddPlaylistItemByController";
- mController2.addPlaylistItem(testIndex, testMediaItem);
+ mController2.addPlaylistItem(testIndex, testMediaId);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mAddPlaylistItemCalled);
assertEquals(testIndex, mPlayer.mIndex);
// MediaController2.addPlaylistItem does not ensure the equality of the items.
- assertEquals(testMediaItem.getMediaId(), mPlayer.mItem.getMediaId());
+ assertEquals(testMediaId, mPlayer.mItem.getMediaId());
}
@Test
@@ -290,7 +296,7 @@
mPlayer.mPlaylist = MediaTestUtils.createPlaylist(2);
MediaItem2 targetItem = mPlayer.mPlaylist.get(0);
- mController2.removePlaylistItem(targetItem);
+ mController2.removePlaylistItem(0);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mRemovePlaylistItemCalled);
@@ -311,14 +317,14 @@
@Test
public void testReplacePlaylistItemByController() throws InterruptedException {
final int testIndex = 12;
- final MediaItem2 testMediaItem = MediaTestUtils.createMediaItemWithMetadata();
+ final String testMediaId = "testReplacePlaylistItemByController";
- mController2.replacePlaylistItem(testIndex, testMediaItem);
+ mController2.replacePlaylistItem(testIndex, testMediaId);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mReplacePlaylistItemCalled);
// MediaController2.replacePlaylistItem does not ensure the equality of the items.
- assertEquals(testMediaItem.getMediaId(), mPlayer.mItem.getMediaId());
+ assertEquals(testMediaId, mPlayer.mItem.getMediaId());
}
@Test
@@ -360,12 +366,13 @@
@Test
public void testSkipToPlaylistItemByController() throws InterruptedException {
- MediaItem2 targetItem = MediaTestUtils.createMediaItemWithMetadata();
- mController2.skipToPlaylistItem(targetItem);
+ mPlayer.mPlaylist = MediaTestUtils.createPlaylist(3);
+
+ mController2.skipToPlaylistItem(2);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSkipToPlaylistItemCalled);
- assertEquals(targetItem, mPlayer.mItem);
+ assertEquals(mPlayer.mPlaylist.get(2), mPlayer.mItem);
}
@Test
diff --git a/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaController2.aidl b/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaController2.aidl
index 67ebe77a..ab82b66 100644
--- a/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaController2.aidl
+++ b/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaController2.aidl
@@ -30,19 +30,19 @@
ParcelImpl getConnectedSessionToken(String controllerId);
void play(String controllerId);
void pause(String controllerId);
- void prefetch(String controllerId);
+ void prepare(String controllerId);
void seekTo(String controllerId, long pos);
void setPlaybackSpeed(String controllerId, float speed);
- void setPlaylist(String controllerId, in List<Bundle> list, in Bundle metadata);
- void createAndSetDummyPlaylist(String controllerId, int size, in Bundle metadata);
- void setMediaItem(String controllerId, in Bundle item);
- void updatePlaylistMetadata(String controllerId, in Bundle metadata);
- void addPlaylistItem(String controllerId, int index, in Bundle item);
- void removePlaylistItem(String controllerId, in Bundle item);
- void replacePlaylistItem(String controllerId, int index, in Bundle item);
+ void setPlaylist(String controllerId, in List<String> list, in ParcelImpl metadata);
+ void createAndSetDummyPlaylist(String controllerId, int size, in ParcelImpl metadata);
+ void setMediaItem(String controllerId, in String mediaId);
+ void updatePlaylistMetadata(String controllerId, in ParcelImpl metadata);
+ void addPlaylistItem(String controllerId, int index, String mediaId);
+ void removePlaylistItem(String controllerId, int index);
+ void replacePlaylistItem(String controllerId, int index, String mediaId);
void skipToPreviousItem(String controllerId);
void skipToNextItem(String controllerId);
- void skipToPlaylistItem(String controllerId, in Bundle item);
+ void skipToPlaylistItem(String controllerId, int index);
void setShuffleMode(String controllerId, int shuffleMode);
void setRepeatMode(String controllerId, int repeatMode);
void setVolumeTo(String controllerId, int value, int flags);
@@ -50,12 +50,14 @@
void sendCustomCommand(String controllerId, in Bundle command, in Bundle args);
void fastForward(String controllerId);
void rewind(String controllerId);
+ void skipForward(String controllerId);
+ void skipBackward(String controllerId);
void playFromMediaId(String controllerId, String mediaId, in Bundle extras);
void playFromSearch(String controllerId, String query, in Bundle extras);
void playFromUri(String controllerId, in Uri uri, in Bundle extras);
- void prefetchFromMediaId(String controllerId, String mediaId, in Bundle extras);
- void prefetchFromSearch(String controllerId, String query, in Bundle extras);
- void prefetchFromUri(String controllerId, in Uri uri, in Bundle extras);
+ void prepareFromMediaId(String controllerId, String mediaId, in Bundle extras);
+ void prepareFromSearch(String controllerId, String query, in Bundle extras);
+ void prepareFromUri(String controllerId, in Uri uri, in Bundle extras);
void setRating(String controllerId, String mediaId, in ParcelImpl rating);
void subscribeRoutesInfo(String controllerId);
void unsubscribeRoutesInfo(String controllerId);
diff --git a/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSessionCompat.aidl b/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSessionCompat.aidl
index 829abaa..05e9c64 100644
--- a/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSessionCompat.aidl
+++ b/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSessionCompat.aidl
@@ -32,6 +32,8 @@
Bundle getSessionToken(String sessionTag);
void release(String sessionTag);
void setPlaybackToLocal(String sessionTag, int stream);
+ void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume,
+ int currentVolume);
void setPlaybackState(String sessionTag, in Bundle stateBundle);
void setMetadata(String sessionTag, in Bundle metadataBundle);
void setQueue(String sessionTag, in Bundle queueBundle);
@@ -41,4 +43,5 @@
void setSessionActivity(String sessionTag, in PendingIntent pi);
void setFlags(String sessionTag, int flags);
void setRatingType(String sessionTag, int type);
+ void sendSessionEvent(String sessionTag, String event, in Bundle extras);
}
diff --git a/media2/api/1.0.0-alpha03.txt b/media2/api/1.0.0-alpha03.txt
index 8282a80..82d51ca 100644
--- a/media2/api/1.0.0-alpha03.txt
+++ b/media2/api/1.0.0-alpha03.txt
@@ -7,9 +7,8 @@
public static final class CallbackMediaItem2.Builder {
ctor public CallbackMediaItem2.Builder(androidx.media2.DataSourceCallback2);
- method public androidx.media2.CallbackMediaItem2! build();
+ method public androidx.media2.CallbackMediaItem2 build();
method public androidx.media2.CallbackMediaItem2.Builder! setEndPosition(long);
- method public androidx.media2.CallbackMediaItem2.Builder! setMediaId(String!);
method public androidx.media2.CallbackMediaItem2.Builder! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.CallbackMediaItem2.Builder! setStartPosition(long);
}
@@ -17,7 +16,7 @@
public abstract class DataSourceCallback2 implements java.io.Closeable {
ctor public DataSourceCallback2();
method public abstract long getSize() throws java.io.IOException;
- method public abstract int readAt(long, byte[]!, int, int) throws java.io.IOException;
+ method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
}
public class FileMediaItem2 extends androidx.media2.MediaItem2 {
@@ -30,9 +29,8 @@
public static final class FileMediaItem2.Builder {
ctor public FileMediaItem2.Builder(java.io.FileDescriptor);
ctor public FileMediaItem2.Builder(java.io.FileDescriptor, long, long);
- method public androidx.media2.FileMediaItem2! build();
+ method public androidx.media2.FileMediaItem2 build();
method public androidx.media2.FileMediaItem2.Builder! setEndPosition(long);
- method public androidx.media2.FileMediaItem2.Builder! setMediaId(String!);
method public androidx.media2.FileMediaItem2.Builder! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.FileMediaItem2.Builder! setStartPosition(long);
}
@@ -46,24 +44,24 @@
public class MediaBrowser2 extends androidx.media2.MediaController2 {
ctor public MediaBrowser2(android.content.Context, androidx.media2.SessionToken2, java.util.concurrent.Executor, androidx.media2.MediaBrowser2.BrowserCallback);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getItem(String);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getLibraryRoot(androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! search(String, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! subscribe(String, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! unsubscribe(String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getItem(String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getLibraryRoot(androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> search(String, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> subscribe(String, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> unsubscribe(String);
}
public static class MediaBrowser2.BrowserCallback extends androidx.media2.MediaController2.ControllerCallback {
ctor public MediaBrowser2.BrowserCallback();
- method public void onChildrenChanged(androidx.media2.MediaBrowser2, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public void onSearchResultChanged(androidx.media2.MediaBrowser2, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void onChildrenChanged(androidx.media2.MediaBrowser2, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void onSearchResultChanged(androidx.media2.MediaBrowser2, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
}
public static class MediaBrowser2.BrowserResult extends androidx.versionedparcelable.CustomVersionedParcelable {
method public long getCompletionTime();
- method public androidx.media2.MediaLibraryService2.LibraryParams! getLibraryParams();
+ method public androidx.media2.MediaLibraryService2.LibraryParams? getLibraryParams();
method public androidx.media2.MediaItem2? getMediaItem();
method public java.util.List<androidx.media2.MediaItem2>? getMediaItems();
method public int getResultCode();
@@ -88,14 +86,14 @@
public class MediaController2 implements java.lang.AutoCloseable {
ctor public MediaController2(android.content.Context, androidx.media2.SessionToken2, java.util.concurrent.Executor, androidx.media2.MediaController2.ControllerCallback);
ctor public MediaController2(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token, java.util.concurrent.Executor, androidx.media2.MediaController2.ControllerCallback);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! addPlaylistItem(int, androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! adjustVolume(int, int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> addPlaylistItem(@IntRange(from=0) int, String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> adjustVolume(int, int);
method public void close();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! fastForward();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> fastForward();
method public long getBufferedPosition();
method public int getBufferingState();
method public androidx.media2.SessionToken2? getConnectedSessionToken();
- method public androidx.media2.MediaItem2! getCurrentMediaItem();
+ method public androidx.media2.MediaItem2? getCurrentMediaItem();
method public long getCurrentPosition();
method public long getDuration();
method public androidx.media2.MediaController2.PlaybackInfo? getPlaybackInfo();
@@ -107,25 +105,27 @@
method public android.app.PendingIntent? getSessionActivity();
method public int getShuffleMode();
method public boolean isConnected();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! pause();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! play();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! prefetch();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! removePlaylistItem(androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! replacePlaylistItem(int, androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! rewind();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! seekTo(long);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! sendCustomCommand(androidx.media2.SessionCommand2, android.os.Bundle?);
- method public void setMediaItem(androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setPlaybackSpeed(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setPlaylist(java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setRating(String, androidx.media2.Rating2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setRepeatMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setShuffleMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setVolumeTo(int, int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! skipToNextPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! skipToPlaylistItem(androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! skipToPreviousPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! updatePlaylistMetadata(androidx.media2.MediaMetadata2?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> pause();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> play();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> prepare();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> removePlaylistItem(@IntRange(from=0) int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> replacePlaylistItem(@IntRange(from=0) int, String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> rewind();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> seekTo(long);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> sendCustomCommand(androidx.media2.SessionCommand2, android.os.Bundle?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setMediaItem(String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setPlaybackSpeed(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setPlaylist(java.util.List<java.lang.String>, androidx.media2.MediaMetadata2?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setRating(String, androidx.media2.Rating2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setRepeatMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setShuffleMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setVolumeTo(int, int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipBackward();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipForward();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipToNextPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipToPlaylistItem(@IntRange(from=0) int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipToPreviousPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata2?);
}
public abstract static class MediaController2.ControllerCallback {
@@ -140,7 +140,7 @@
method public void onPlaybackInfoChanged(androidx.media2.MediaController2, androidx.media2.MediaController2.PlaybackInfo);
method public void onPlaybackSpeedChanged(androidx.media2.MediaController2, float);
method public void onPlayerStateChanged(androidx.media2.MediaController2, int);
- method public void onPlaylistChanged(androidx.media2.MediaController2, java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
+ method public void onPlaylistChanged(androidx.media2.MediaController2, java.util.List<androidx.media2.MediaItem2>?, androidx.media2.MediaMetadata2?);
method public void onPlaylistMetadataChanged(androidx.media2.MediaController2, androidx.media2.MediaMetadata2?);
method public void onRepeatModeChanged(androidx.media2.MediaController2, int);
method public void onSeekCompleted(androidx.media2.MediaController2, long);
@@ -173,7 +173,7 @@
}
public static final class MediaController2.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
- method public androidx.media.AudioAttributesCompat! getAudioAttributes();
+ method public androidx.media.AudioAttributesCompat? getAudioAttributes();
method public int getControlType();
method public int getCurrentVolume();
method public int getMaxVolume();
@@ -184,23 +184,16 @@
public class MediaItem2 implements androidx.versionedparcelable.VersionedParcelable {
method public long getEndPosition();
- method public int getFlags();
- method public String? getMediaId();
method public androidx.media2.MediaMetadata2? getMetadata();
method public long getStartPosition();
- method public boolean isBrowsable();
- method public boolean isPlayable();
method public void setMetadata(androidx.media2.MediaMetadata2?);
- field public static final int FLAG_BROWSABLE = 1; // 0x1
- field public static final int FLAG_PLAYABLE = 2; // 0x2
field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
}
public static class MediaItem2.Builder {
- ctor public MediaItem2.Builder(int);
+ ctor public MediaItem2.Builder();
method public androidx.media2.MediaItem2! build();
method public androidx.media2.MediaItem2.BuilderBase! setEndPosition(long);
- method public androidx.media2.MediaItem2.BuilderBase! setMediaId(String!);
method public androidx.media2.MediaItem2.BuilderBase! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.MediaItem2.BuilderBase! setStartPosition(long);
}
@@ -220,11 +213,11 @@
public static final class MediaLibraryService2.LibraryParams.Builder {
ctor public MediaLibraryService2.LibraryParams.Builder();
- method public androidx.media2.MediaLibraryService2.LibraryParams! build();
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setExtras(android.os.Bundle?);
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setOffline(boolean);
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setRecent(boolean);
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setSuggested(boolean);
+ method public androidx.media2.MediaLibraryService2.LibraryParams build();
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setExtras(android.os.Bundle?);
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setOffline(boolean);
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setRecent(boolean);
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setSuggested(boolean);
}
public static class MediaLibraryService2.LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable {
@@ -250,9 +243,9 @@
}
public static final class MediaLibraryService2.MediaLibrarySession extends androidx.media2.MediaSession2 {
- method public void notifyChildrenChanged(androidx.media2.MediaSession2.ControllerInfo, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void notifyChildrenChanged(androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
method public void notifyChildrenChanged(String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public void notifySearchResultChanged(androidx.media2.MediaSession2.ControllerInfo, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void notifySearchResultChanged(androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
}
public static final class MediaLibraryService2.MediaLibrarySession.Builder {
@@ -264,10 +257,10 @@
public static class MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.MediaSession2.SessionCallback {
ctor public MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback();
- method public androidx.media2.MediaLibraryService2.LibraryResult onGetChildren(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, int, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public androidx.media2.MediaLibraryService2.LibraryResult onGetChildren(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
method public androidx.media2.MediaLibraryService2.LibraryResult onGetItem(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String);
method public androidx.media2.MediaLibraryService2.LibraryResult onGetLibraryRoot(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public androidx.media2.MediaLibraryService2.LibraryResult onGetSearchResult(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, int, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public androidx.media2.MediaLibraryService2.LibraryResult onGetSearchResult(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
method public int onSearch(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, androidx.media2.MediaLibraryService2.LibraryParams?);
method public int onSubscribe(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, androidx.media2.MediaLibraryService2.LibraryParams?);
method public int onUnsubscribe(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String);
@@ -287,13 +280,14 @@
method public java.util.Set<java.lang.String> keySet();
method public int size();
method public android.os.Bundle toBundle();
- field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
- field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
- field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
- field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
- field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
- field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
- field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
+ field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
@@ -303,7 +297,7 @@
field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
- field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_BROWSABLE = "android.media.metadata.BT_FOLDER_TYPE";
field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
@@ -320,6 +314,7 @@
field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_PLAYABLE = "android.media.metadata.playable";
field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
@@ -341,7 +336,98 @@
method public androidx.media2.MediaMetadata2.Builder putRating(String, androidx.media2.Rating2?);
method public androidx.media2.MediaMetadata2.Builder putString(String, String?);
method public androidx.media2.MediaMetadata2.Builder putText(String, CharSequence?);
- method public androidx.media2.MediaMetadata2.Builder! setExtras(android.os.Bundle?);
+ method public androidx.media2.MediaMetadata2.Builder setExtras(android.os.Bundle?);
+ }
+
+ public class MediaPlayer extends androidx.media2.SessionPlayer2 {
+ ctor public MediaPlayer(android.content.Context);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> addPlaylistItem(int, androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> attachAuxEffect(int);
+ method public void close() throws java.lang.Exception;
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> deselectTrack(int);
+ method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+ method public int getAudioSessionId();
+ method public long getBufferedPosition();
+ method public int getBufferingState();
+ method public androidx.media2.MediaItem2? getCurrentMediaItem();
+ method public long getCurrentPosition();
+ method public long getDuration();
+ method public float getMaxPlayerVolume();
+ method public androidx.media2.PlaybackParams2 getPlaybackParams();
+ method public float getPlaybackSpeed();
+ method public int getPlayerState();
+ method public float getPlayerVolume();
+ method public java.util.List<androidx.media2.MediaItem2>? getPlaylist();
+ method public androidx.media2.MediaMetadata2? getPlaylistMetadata();
+ method public int getRepeatMode();
+ method public int getSelectedTrack(int);
+ method public int getShuffleMode();
+ method public androidx.media2.MediaTimestamp2? getTimestamp();
+ method public java.util.List<androidx.media2.MediaPlayer.TrackInfo> getTrackInfo();
+ method public int getVideoHeight();
+ method public int getVideoWidth();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> pause();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> play();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> prepare();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> removePlaylistItem(androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> replacePlaylistItem(int, androidx.media2.MediaItem2);
+ method public void reset();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> seekTo(long);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> seekTo(long, int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> selectTrack(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setAudioSessionId(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setAuxEffectSendLevel(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setMediaItem(androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlaybackParams(androidx.media2.PlaybackParams2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlaybackSpeed(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlayerVolume(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlaylist(java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setRepeatMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setShuffleMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setSurface(android.view.Surface?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> skipToNextPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> skipToPlaylistItem(androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> skipToPreviousPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata2?);
+ field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
+ field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
+ field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
+ field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
+ field public static final int MEDIA_INFO_PREPARED = 100; // 0x64
+ field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
+ field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
+ field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+ field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
+ field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
+ field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
+ field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
+ field public static final int SEEK_CLOSEST = 3; // 0x3
+ field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
+ field public static final int SEEK_NEXT_SYNC = 1; // 0x1
+ field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
+ }
+
+ public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.SessionPlayer2.PlayerCallback {
+ ctor public MediaPlayer.PlayerCallback();
+ method public void onError(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, int, int);
+ method public void onInfo(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, int, int);
+ method public void onMediaTimeDiscontinuity(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, androidx.media2.MediaTimestamp2);
+ method public void onSubtitleData(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, androidx.media2.SubtitleData2);
+ method public void onTimedMetaDataAvailable(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, androidx.media2.TimedMetaData2);
+ method public void onVideoSizeChanged(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, int, int);
+ }
+
+ public static final class MediaPlayer.TrackInfo {
+ method public android.media.MediaFormat? getFormat();
+ method public String getLanguage();
+ method public int getTrackType();
+ field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+ field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
+ field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
+ field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
}
public class MediaSession2 implements java.lang.AutoCloseable {
@@ -392,12 +478,14 @@
ctor public MediaSession2.SessionCallback();
method public int onCommandRequest(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.SessionCommand2);
method public androidx.media2.SessionCommandGroup2? onConnect(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
- method public androidx.media2.MediaItem2? onCreateMediaItem(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.MediaItem2);
+ method public androidx.media2.MediaItem2? onCreateMediaItem(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, String);
method public androidx.media2.MediaSession2.SessionResult onCustomCommand(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.SessionCommand2, android.os.Bundle?);
method public void onDisconnected(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
method public int onFastForward(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
method public int onRewind(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
method public int onSetRating(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, String, androidx.media2.Rating2);
+ method public int onSkipBackward(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
+ method public int onSkipForward(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
}
public static class MediaSession2.SessionResult implements androidx.versionedparcelable.VersionedParcelable {
@@ -432,7 +520,7 @@
ctor public MediaSessionService2();
method public final void addSession(androidx.media2.MediaSession2);
method public final java.util.List<androidx.media2.MediaSession2> getSessions();
- method @CallSuper public android.os.IBinder? onBind(android.content.Intent!);
+ method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
method public abstract androidx.media2.MediaSession2 onGetSession();
method public androidx.media2.MediaSessionService2.MediaNotification? onUpdateNotification(androidx.media2.MediaSession2);
method public final void removeSession(androidx.media2.MediaSession2);
@@ -449,7 +537,7 @@
method public long getAnchorMediaTimeUs();
method public long getAnchorSystemNanoTime();
method public float getMediaClockRate();
- field public static final androidx.media2.MediaTimestamp2! TIMESTAMP_UNKNOWN;
+ field public static final androidx.media2.MediaTimestamp2 TIMESTAMP_UNKNOWN;
}
public final class PercentageRating2 implements androidx.media2.Rating2 {
@@ -460,9 +548,9 @@
}
public final class PlaybackParams2 {
- method public Integer! getAudioFallbackMode();
- method public Float! getPitch();
- method public Float! getSpeed();
+ method public Integer? getAudioFallbackMode();
+ method public Float? getPitch();
+ method public Float? getSpeed();
field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
@@ -470,10 +558,10 @@
public static final class PlaybackParams2.Builder {
ctor public PlaybackParams2.Builder();
- method public androidx.media2.PlaybackParams2! build();
- method public androidx.media2.PlaybackParams2.Builder! setAudioFallbackMode(int);
- method public androidx.media2.PlaybackParams2.Builder! setPitch(float);
- method public androidx.media2.PlaybackParams2.Builder! setSpeed(float);
+ method public androidx.media2.PlaybackParams2 build();
+ method public androidx.media2.PlaybackParams2.Builder setAudioFallbackMode(int);
+ method public androidx.media2.PlaybackParams2.Builder setPitch(float);
+ method public androidx.media2.PlaybackParams2.Builder setSpeed(float);
}
public interface Rating2 extends androidx.versionedparcelable.VersionedParcelable {
@@ -500,7 +588,7 @@
field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
- field public static final int COMMAND_CODE_PLAYER_PREFETCH = 10002; // 0x2712
+ field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
@@ -515,7 +603,9 @@
field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
- field public static final int COMMAND_CODE_SESSION_SET_RATING = 40008; // 0x9c48
+ field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
+ field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
+ field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
field public static final int COMMAND_VERSION_1 = 1; // 0x1
@@ -531,9 +621,9 @@
public static final class SessionCommandGroup2.Builder {
ctor public SessionCommandGroup2.Builder();
- ctor public SessionCommandGroup2.Builder(androidx.media2.SessionCommandGroup2!);
+ ctor public SessionCommandGroup2.Builder(androidx.media2.SessionCommandGroup2);
method public androidx.media2.SessionCommandGroup2.Builder addAllPredefinedCommands(int);
- method public androidx.media2.SessionCommandGroup2.Builder addCommand(androidx.media2.SessionCommand2!);
+ method public androidx.media2.SessionCommandGroup2.Builder addCommand(androidx.media2.SessionCommand2);
method public androidx.media2.SessionCommandGroup2.Builder addCommand(int);
method public androidx.media2.SessionCommandGroup2 build();
method public androidx.media2.SessionCommandGroup2.Builder removeCommand(androidx.media2.SessionCommand2);
@@ -600,7 +690,7 @@
method public void onPlaybackCompleted(androidx.media2.SessionPlayer2);
method public void onPlaybackSpeedChanged(androidx.media2.SessionPlayer2, float);
method public void onPlayerStateChanged(androidx.media2.SessionPlayer2, int);
- method public void onPlaylistChanged(androidx.media2.SessionPlayer2, java.util.List<androidx.media2.MediaItem2>!, androidx.media2.MediaMetadata2?);
+ method public void onPlaylistChanged(androidx.media2.SessionPlayer2, java.util.List<androidx.media2.MediaItem2>?, androidx.media2.MediaMetadata2?);
method public void onPlaylistMetadataChanged(androidx.media2.SessionPlayer2, androidx.media2.MediaMetadata2?);
method public void onRepeatModeChanged(androidx.media2.SessionPlayer2, int);
method public void onSeekCompleted(androidx.media2.SessionPlayer2, long);
@@ -673,143 +763,11 @@
public static final class UriMediaItem2.Builder {
ctor public UriMediaItem2.Builder(android.content.Context, android.net.Uri);
ctor public UriMediaItem2.Builder(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
- method public androidx.media2.UriMediaItem2! build();
+ method public androidx.media2.UriMediaItem2 build();
method public androidx.media2.UriMediaItem2.Builder! setEndPosition(long);
- method public androidx.media2.UriMediaItem2.Builder! setMediaId(String!);
method public androidx.media2.UriMediaItem2.Builder! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.UriMediaItem2.Builder! setStartPosition(long);
}
- public class XMediaPlayer extends androidx.media2.SessionPlayer2 {
- ctor public XMediaPlayer(android.content.Context!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! addPlaylistItem(int, androidx.media2.MediaItem2!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! attachAuxEffect(int);
- method public void close() throws java.lang.Exception;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! deselectTrack(int);
- method public androidx.media.AudioAttributesCompat! getAudioAttributes();
- method public int getAudioSessionId();
- method public long getBufferedPosition();
- method public int getBufferingState();
- method public androidx.media2.MediaItem2! getCurrentMediaItem();
- method public long getCurrentPosition();
- method public androidx.media2.XMediaPlayer.DrmInfo! getDrmInfo();
- method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String,java.lang.String>?) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public String getDrmPropertyString(String) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public long getDuration();
- method public float getMaxPlayerVolume();
- method public androidx.media2.PlaybackParams2! getPlaybackParams();
- method public float getPlaybackSpeed();
- method public int getPlayerState();
- method public float getPlayerVolume();
- method public java.util.List<androidx.media2.MediaItem2>! getPlaylist();
- method public androidx.media2.MediaMetadata2! getPlaylistMetadata();
- method public int getRepeatMode();
- method public int getSelectedTrack(int);
- method public int getShuffleMode();
- method public androidx.media2.MediaTimestamp2? getTimestamp();
- method public java.util.List<androidx.media2.XMediaPlayer.TrackInfo>! getTrackInfo();
- method public int getVideoHeight();
- method public int getVideoWidth();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! pause();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! play();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! prepare();
- method public androidx.concurrent.futures.ResolvableFuture<androidx.media2.XMediaPlayer.DrmResult>! prepareDrm(java.util.UUID);
- method public byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public void releaseDrm() throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! removePlaylistItem(androidx.media2.MediaItem2!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! replacePlaylistItem(int, androidx.media2.MediaItem2!);
- method public void reset();
- method public void restoreDrmKeys(byte[]) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! seekTo(long);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! seekTo(long, int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! selectTrack(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setAudioAttributes(androidx.media.AudioAttributesCompat!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setAudioSessionId(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setAuxEffectSendLevel(float);
- method public void setDrmPropertyString(String, String) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setMediaItem(androidx.media2.MediaItem2);
- method public void setOnDrmConfigHelper(androidx.media2.XMediaPlayer.OnDrmConfigHelper!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlaybackParams(androidx.media2.PlaybackParams2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlaybackSpeed(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlayerVolume(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlaylist(java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setRepeatMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setShuffleMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setSurface(android.view.Surface!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! skipToNextPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! skipToPlaylistItem(androidx.media2.MediaItem2!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! skipToPreviousPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! updatePlaylistMetadata(androidx.media2.MediaMetadata2!);
- field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
- field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
- field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
- field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
- field public static final int MEDIA_INFO_PREFETCHED = 100; // 0x64
- field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
- field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
- field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
- field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
- field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
- field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
- field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
- field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
- field public static final int SEEK_CLOSEST = 3; // 0x3
- field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
- field public static final int SEEK_NEXT_SYNC = 1; // 0x1
- field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
- }
-
- public static final class XMediaPlayer.DrmInfo {
- method public java.util.Map<java.util.UUID,byte[]>! getPssh();
- method public java.util.List<java.util.UUID>! getSupportedSchemes();
- }
-
- public static class XMediaPlayer.DrmResult extends androidx.media2.SessionPlayer2.PlayerResult {
- ctor public XMediaPlayer.DrmResult(int, androidx.media2.MediaItem2?);
- field public static final int RESULT_CODE_BAD_VALUE = -3; // 0xfffffffd
- field public static final int RESULT_CODE_INVALID_STATE = -2; // 0xfffffffe
- field public static final int RESULT_CODE_IO_ERROR = -5; // 0xfffffffb
- field public static final int RESULT_CODE_NOT_SUPPORTED = -6; // 0xfffffffa
- field public static final int RESULT_CODE_PERMISSION_DENIED = -4; // 0xfffffffc
- field public static final int RESULT_CODE_PREPARATION_ERROR = -1003; // 0xfffffc15
- field public static final int RESULT_CODE_PROVISIONING_NETWORK_ERROR = -1001; // 0xfffffc17
- field public static final int RESULT_CODE_PROVISIONING_SERVER_ERROR = -1002; // 0xfffffc16
- field public static final int RESULT_CODE_RESOURCE_BUSY = -1005; // 0xfffffc13
- field public static final int RESULT_CODE_SKIPPED = 1; // 0x1
- field public static final int RESULT_CODE_SUCCESS = 0; // 0x0
- field public static final int RESULT_CODE_UNKNOWN_ERROR = -1; // 0xffffffff
- field public static final int RESULT_CODE_UNSUPPORTED_SCHEME = -1004; // 0xfffffc14
- }
-
- public static class XMediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
- ctor public XMediaPlayer.NoDrmSchemeException(String!);
- }
-
- public static interface XMediaPlayer.OnDrmConfigHelper {
- method public void onDrmConfig(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!);
- }
-
- public abstract static class XMediaPlayer.PlayerCallback extends androidx.media2.SessionPlayer2.PlayerCallback {
- ctor public XMediaPlayer.PlayerCallback();
- method public void onDrmInfo(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.XMediaPlayer.DrmInfo!);
- method public void onError(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, int, int);
- method public void onInfo(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, int, int);
- method public void onMediaTimeDiscontinuity(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.MediaTimestamp2!);
- method public void onSubtitleData(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.SubtitleData2);
- method public void onTimedMetaDataAvailable(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.TimedMetaData2!);
- method public void onVideoSizeChanged(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, int, int);
- }
-
- public static final class XMediaPlayer.TrackInfo {
- method public android.media.MediaFormat! getFormat();
- method public String! getLanguage();
- method public int getTrackType();
- field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
- field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
- field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
- field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
- field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
- }
-
}
diff --git a/media2/api/current.txt b/media2/api/current.txt
index 8282a80..82d51ca 100644
--- a/media2/api/current.txt
+++ b/media2/api/current.txt
@@ -7,9 +7,8 @@
public static final class CallbackMediaItem2.Builder {
ctor public CallbackMediaItem2.Builder(androidx.media2.DataSourceCallback2);
- method public androidx.media2.CallbackMediaItem2! build();
+ method public androidx.media2.CallbackMediaItem2 build();
method public androidx.media2.CallbackMediaItem2.Builder! setEndPosition(long);
- method public androidx.media2.CallbackMediaItem2.Builder! setMediaId(String!);
method public androidx.media2.CallbackMediaItem2.Builder! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.CallbackMediaItem2.Builder! setStartPosition(long);
}
@@ -17,7 +16,7 @@
public abstract class DataSourceCallback2 implements java.io.Closeable {
ctor public DataSourceCallback2();
method public abstract long getSize() throws java.io.IOException;
- method public abstract int readAt(long, byte[]!, int, int) throws java.io.IOException;
+ method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
}
public class FileMediaItem2 extends androidx.media2.MediaItem2 {
@@ -30,9 +29,8 @@
public static final class FileMediaItem2.Builder {
ctor public FileMediaItem2.Builder(java.io.FileDescriptor);
ctor public FileMediaItem2.Builder(java.io.FileDescriptor, long, long);
- method public androidx.media2.FileMediaItem2! build();
+ method public androidx.media2.FileMediaItem2 build();
method public androidx.media2.FileMediaItem2.Builder! setEndPosition(long);
- method public androidx.media2.FileMediaItem2.Builder! setMediaId(String!);
method public androidx.media2.FileMediaItem2.Builder! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.FileMediaItem2.Builder! setStartPosition(long);
}
@@ -46,24 +44,24 @@
public class MediaBrowser2 extends androidx.media2.MediaController2 {
ctor public MediaBrowser2(android.content.Context, androidx.media2.SessionToken2, java.util.concurrent.Executor, androidx.media2.MediaBrowser2.BrowserCallback);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getItem(String);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getLibraryRoot(androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! search(String, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! subscribe(String, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult>! unsubscribe(String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getItem(String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getLibraryRoot(androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> search(String, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> subscribe(String, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaBrowser2.BrowserResult> unsubscribe(String);
}
public static class MediaBrowser2.BrowserCallback extends androidx.media2.MediaController2.ControllerCallback {
ctor public MediaBrowser2.BrowserCallback();
- method public void onChildrenChanged(androidx.media2.MediaBrowser2, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public void onSearchResultChanged(androidx.media2.MediaBrowser2, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void onChildrenChanged(androidx.media2.MediaBrowser2, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void onSearchResultChanged(androidx.media2.MediaBrowser2, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
}
public static class MediaBrowser2.BrowserResult extends androidx.versionedparcelable.CustomVersionedParcelable {
method public long getCompletionTime();
- method public androidx.media2.MediaLibraryService2.LibraryParams! getLibraryParams();
+ method public androidx.media2.MediaLibraryService2.LibraryParams? getLibraryParams();
method public androidx.media2.MediaItem2? getMediaItem();
method public java.util.List<androidx.media2.MediaItem2>? getMediaItems();
method public int getResultCode();
@@ -88,14 +86,14 @@
public class MediaController2 implements java.lang.AutoCloseable {
ctor public MediaController2(android.content.Context, androidx.media2.SessionToken2, java.util.concurrent.Executor, androidx.media2.MediaController2.ControllerCallback);
ctor public MediaController2(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token, java.util.concurrent.Executor, androidx.media2.MediaController2.ControllerCallback);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! addPlaylistItem(int, androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! adjustVolume(int, int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> addPlaylistItem(@IntRange(from=0) int, String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> adjustVolume(int, int);
method public void close();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! fastForward();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> fastForward();
method public long getBufferedPosition();
method public int getBufferingState();
method public androidx.media2.SessionToken2? getConnectedSessionToken();
- method public androidx.media2.MediaItem2! getCurrentMediaItem();
+ method public androidx.media2.MediaItem2? getCurrentMediaItem();
method public long getCurrentPosition();
method public long getDuration();
method public androidx.media2.MediaController2.PlaybackInfo? getPlaybackInfo();
@@ -107,25 +105,27 @@
method public android.app.PendingIntent? getSessionActivity();
method public int getShuffleMode();
method public boolean isConnected();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! pause();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! play();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! prefetch();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! removePlaylistItem(androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! replacePlaylistItem(int, androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! rewind();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! seekTo(long);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! sendCustomCommand(androidx.media2.SessionCommand2, android.os.Bundle?);
- method public void setMediaItem(androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setPlaybackSpeed(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setPlaylist(java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setRating(String, androidx.media2.Rating2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setRepeatMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setShuffleMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! setVolumeTo(int, int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! skipToNextPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! skipToPlaylistItem(androidx.media2.MediaItem2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! skipToPreviousPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult>! updatePlaylistMetadata(androidx.media2.MediaMetadata2?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> pause();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> play();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> prepare();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> removePlaylistItem(@IntRange(from=0) int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> replacePlaylistItem(@IntRange(from=0) int, String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> rewind();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> seekTo(long);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> sendCustomCommand(androidx.media2.SessionCommand2, android.os.Bundle?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setMediaItem(String);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setPlaybackSpeed(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setPlaylist(java.util.List<java.lang.String>, androidx.media2.MediaMetadata2?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setRating(String, androidx.media2.Rating2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setRepeatMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setShuffleMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> setVolumeTo(int, int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipBackward();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipForward();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipToNextPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipToPlaylistItem(@IntRange(from=0) int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> skipToPreviousPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaController2.ControllerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata2?);
}
public abstract static class MediaController2.ControllerCallback {
@@ -140,7 +140,7 @@
method public void onPlaybackInfoChanged(androidx.media2.MediaController2, androidx.media2.MediaController2.PlaybackInfo);
method public void onPlaybackSpeedChanged(androidx.media2.MediaController2, float);
method public void onPlayerStateChanged(androidx.media2.MediaController2, int);
- method public void onPlaylistChanged(androidx.media2.MediaController2, java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
+ method public void onPlaylistChanged(androidx.media2.MediaController2, java.util.List<androidx.media2.MediaItem2>?, androidx.media2.MediaMetadata2?);
method public void onPlaylistMetadataChanged(androidx.media2.MediaController2, androidx.media2.MediaMetadata2?);
method public void onRepeatModeChanged(androidx.media2.MediaController2, int);
method public void onSeekCompleted(androidx.media2.MediaController2, long);
@@ -173,7 +173,7 @@
}
public static final class MediaController2.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
- method public androidx.media.AudioAttributesCompat! getAudioAttributes();
+ method public androidx.media.AudioAttributesCompat? getAudioAttributes();
method public int getControlType();
method public int getCurrentVolume();
method public int getMaxVolume();
@@ -184,23 +184,16 @@
public class MediaItem2 implements androidx.versionedparcelable.VersionedParcelable {
method public long getEndPosition();
- method public int getFlags();
- method public String? getMediaId();
method public androidx.media2.MediaMetadata2? getMetadata();
method public long getStartPosition();
- method public boolean isBrowsable();
- method public boolean isPlayable();
method public void setMetadata(androidx.media2.MediaMetadata2?);
- field public static final int FLAG_BROWSABLE = 1; // 0x1
- field public static final int FLAG_PLAYABLE = 2; // 0x2
field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
}
public static class MediaItem2.Builder {
- ctor public MediaItem2.Builder(int);
+ ctor public MediaItem2.Builder();
method public androidx.media2.MediaItem2! build();
method public androidx.media2.MediaItem2.BuilderBase! setEndPosition(long);
- method public androidx.media2.MediaItem2.BuilderBase! setMediaId(String!);
method public androidx.media2.MediaItem2.BuilderBase! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.MediaItem2.BuilderBase! setStartPosition(long);
}
@@ -220,11 +213,11 @@
public static final class MediaLibraryService2.LibraryParams.Builder {
ctor public MediaLibraryService2.LibraryParams.Builder();
- method public androidx.media2.MediaLibraryService2.LibraryParams! build();
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setExtras(android.os.Bundle?);
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setOffline(boolean);
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setRecent(boolean);
- method public androidx.media2.MediaLibraryService2.LibraryParams.Builder! setSuggested(boolean);
+ method public androidx.media2.MediaLibraryService2.LibraryParams build();
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setExtras(android.os.Bundle?);
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setOffline(boolean);
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setRecent(boolean);
+ method public androidx.media2.MediaLibraryService2.LibraryParams.Builder setSuggested(boolean);
}
public static class MediaLibraryService2.LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable {
@@ -250,9 +243,9 @@
}
public static final class MediaLibraryService2.MediaLibrarySession extends androidx.media2.MediaSession2 {
- method public void notifyChildrenChanged(androidx.media2.MediaSession2.ControllerInfo, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void notifyChildrenChanged(androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
method public void notifyChildrenChanged(String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public void notifySearchResultChanged(androidx.media2.MediaSession2.ControllerInfo, String, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public void notifySearchResultChanged(androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.MediaLibraryService2.LibraryParams?);
}
public static final class MediaLibraryService2.MediaLibrarySession.Builder {
@@ -264,10 +257,10 @@
public static class MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.MediaSession2.SessionCallback {
ctor public MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback();
- method public androidx.media2.MediaLibraryService2.LibraryResult onGetChildren(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, int, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public androidx.media2.MediaLibraryService2.LibraryResult onGetChildren(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
method public androidx.media2.MediaLibraryService2.LibraryResult onGetItem(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String);
method public androidx.media2.MediaLibraryService2.LibraryResult onGetLibraryRoot(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.MediaLibraryService2.LibraryParams?);
- method public androidx.media2.MediaLibraryService2.LibraryResult onGetSearchResult(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, int, int, androidx.media2.MediaLibraryService2.LibraryParams?);
+ method public androidx.media2.MediaLibraryService2.LibraryResult onGetSearchResult(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.MediaLibraryService2.LibraryParams?);
method public int onSearch(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, androidx.media2.MediaLibraryService2.LibraryParams?);
method public int onSubscribe(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String, androidx.media2.MediaLibraryService2.LibraryParams?);
method public int onUnsubscribe(androidx.media2.MediaLibraryService2.MediaLibrarySession, androidx.media2.MediaSession2.ControllerInfo, String);
@@ -287,13 +280,14 @@
method public java.util.Set<java.lang.String> keySet();
method public int size();
method public android.os.Bundle toBundle();
- field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
- field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
- field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
- field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
- field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
- field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
- field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
+ field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
@@ -303,7 +297,7 @@
field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
- field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_BROWSABLE = "android.media.metadata.BT_FOLDER_TYPE";
field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
@@ -320,6 +314,7 @@
field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_PLAYABLE = "android.media.metadata.playable";
field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
@@ -341,7 +336,98 @@
method public androidx.media2.MediaMetadata2.Builder putRating(String, androidx.media2.Rating2?);
method public androidx.media2.MediaMetadata2.Builder putString(String, String?);
method public androidx.media2.MediaMetadata2.Builder putText(String, CharSequence?);
- method public androidx.media2.MediaMetadata2.Builder! setExtras(android.os.Bundle?);
+ method public androidx.media2.MediaMetadata2.Builder setExtras(android.os.Bundle?);
+ }
+
+ public class MediaPlayer extends androidx.media2.SessionPlayer2 {
+ ctor public MediaPlayer(android.content.Context);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> addPlaylistItem(int, androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> attachAuxEffect(int);
+ method public void close() throws java.lang.Exception;
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> deselectTrack(int);
+ method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+ method public int getAudioSessionId();
+ method public long getBufferedPosition();
+ method public int getBufferingState();
+ method public androidx.media2.MediaItem2? getCurrentMediaItem();
+ method public long getCurrentPosition();
+ method public long getDuration();
+ method public float getMaxPlayerVolume();
+ method public androidx.media2.PlaybackParams2 getPlaybackParams();
+ method public float getPlaybackSpeed();
+ method public int getPlayerState();
+ method public float getPlayerVolume();
+ method public java.util.List<androidx.media2.MediaItem2>? getPlaylist();
+ method public androidx.media2.MediaMetadata2? getPlaylistMetadata();
+ method public int getRepeatMode();
+ method public int getSelectedTrack(int);
+ method public int getShuffleMode();
+ method public androidx.media2.MediaTimestamp2? getTimestamp();
+ method public java.util.List<androidx.media2.MediaPlayer.TrackInfo> getTrackInfo();
+ method public int getVideoHeight();
+ method public int getVideoWidth();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> pause();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> play();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> prepare();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> removePlaylistItem(androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> replacePlaylistItem(int, androidx.media2.MediaItem2);
+ method public void reset();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> seekTo(long);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> seekTo(long, int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> selectTrack(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setAudioSessionId(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setAuxEffectSendLevel(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setMediaItem(androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlaybackParams(androidx.media2.PlaybackParams2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlaybackSpeed(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlayerVolume(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setPlaylist(java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setRepeatMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setShuffleMode(int);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> setSurface(android.view.Surface?);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> skipToNextPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> skipToPlaylistItem(androidx.media2.MediaItem2);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> skipToPreviousPlaylistItem();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata2?);
+ field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
+ field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
+ field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
+ field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
+ field public static final int MEDIA_INFO_PREPARED = 100; // 0x64
+ field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
+ field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
+ field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+ field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
+ field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
+ field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
+ field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
+ field public static final int SEEK_CLOSEST = 3; // 0x3
+ field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
+ field public static final int SEEK_NEXT_SYNC = 1; // 0x1
+ field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
+ }
+
+ public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.SessionPlayer2.PlayerCallback {
+ ctor public MediaPlayer.PlayerCallback();
+ method public void onError(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, int, int);
+ method public void onInfo(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, int, int);
+ method public void onMediaTimeDiscontinuity(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, androidx.media2.MediaTimestamp2);
+ method public void onSubtitleData(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, androidx.media2.SubtitleData2);
+ method public void onTimedMetaDataAvailable(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, androidx.media2.TimedMetaData2);
+ method public void onVideoSizeChanged(androidx.media2.MediaPlayer, androidx.media2.MediaItem2, int, int);
+ }
+
+ public static final class MediaPlayer.TrackInfo {
+ method public android.media.MediaFormat? getFormat();
+ method public String getLanguage();
+ method public int getTrackType();
+ field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+ field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
+ field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
+ field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
}
public class MediaSession2 implements java.lang.AutoCloseable {
@@ -392,12 +478,14 @@
ctor public MediaSession2.SessionCallback();
method public int onCommandRequest(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.SessionCommand2);
method public androidx.media2.SessionCommandGroup2? onConnect(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
- method public androidx.media2.MediaItem2? onCreateMediaItem(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.MediaItem2);
+ method public androidx.media2.MediaItem2? onCreateMediaItem(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, String);
method public androidx.media2.MediaSession2.SessionResult onCustomCommand(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, androidx.media2.SessionCommand2, android.os.Bundle?);
method public void onDisconnected(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
method public int onFastForward(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
method public int onRewind(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
method public int onSetRating(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo, String, androidx.media2.Rating2);
+ method public int onSkipBackward(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
+ method public int onSkipForward(androidx.media2.MediaSession2, androidx.media2.MediaSession2.ControllerInfo);
}
public static class MediaSession2.SessionResult implements androidx.versionedparcelable.VersionedParcelable {
@@ -432,7 +520,7 @@
ctor public MediaSessionService2();
method public final void addSession(androidx.media2.MediaSession2);
method public final java.util.List<androidx.media2.MediaSession2> getSessions();
- method @CallSuper public android.os.IBinder? onBind(android.content.Intent!);
+ method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
method public abstract androidx.media2.MediaSession2 onGetSession();
method public androidx.media2.MediaSessionService2.MediaNotification? onUpdateNotification(androidx.media2.MediaSession2);
method public final void removeSession(androidx.media2.MediaSession2);
@@ -449,7 +537,7 @@
method public long getAnchorMediaTimeUs();
method public long getAnchorSystemNanoTime();
method public float getMediaClockRate();
- field public static final androidx.media2.MediaTimestamp2! TIMESTAMP_UNKNOWN;
+ field public static final androidx.media2.MediaTimestamp2 TIMESTAMP_UNKNOWN;
}
public final class PercentageRating2 implements androidx.media2.Rating2 {
@@ -460,9 +548,9 @@
}
public final class PlaybackParams2 {
- method public Integer! getAudioFallbackMode();
- method public Float! getPitch();
- method public Float! getSpeed();
+ method public Integer? getAudioFallbackMode();
+ method public Float? getPitch();
+ method public Float? getSpeed();
field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
@@ -470,10 +558,10 @@
public static final class PlaybackParams2.Builder {
ctor public PlaybackParams2.Builder();
- method public androidx.media2.PlaybackParams2! build();
- method public androidx.media2.PlaybackParams2.Builder! setAudioFallbackMode(int);
- method public androidx.media2.PlaybackParams2.Builder! setPitch(float);
- method public androidx.media2.PlaybackParams2.Builder! setSpeed(float);
+ method public androidx.media2.PlaybackParams2 build();
+ method public androidx.media2.PlaybackParams2.Builder setAudioFallbackMode(int);
+ method public androidx.media2.PlaybackParams2.Builder setPitch(float);
+ method public androidx.media2.PlaybackParams2.Builder setSpeed(float);
}
public interface Rating2 extends androidx.versionedparcelable.VersionedParcelable {
@@ -500,7 +588,7 @@
field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
- field public static final int COMMAND_CODE_PLAYER_PREFETCH = 10002; // 0x2712
+ field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
@@ -515,7 +603,9 @@
field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
- field public static final int COMMAND_CODE_SESSION_SET_RATING = 40008; // 0x9c48
+ field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
+ field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
+ field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
field public static final int COMMAND_VERSION_1 = 1; // 0x1
@@ -531,9 +621,9 @@
public static final class SessionCommandGroup2.Builder {
ctor public SessionCommandGroup2.Builder();
- ctor public SessionCommandGroup2.Builder(androidx.media2.SessionCommandGroup2!);
+ ctor public SessionCommandGroup2.Builder(androidx.media2.SessionCommandGroup2);
method public androidx.media2.SessionCommandGroup2.Builder addAllPredefinedCommands(int);
- method public androidx.media2.SessionCommandGroup2.Builder addCommand(androidx.media2.SessionCommand2!);
+ method public androidx.media2.SessionCommandGroup2.Builder addCommand(androidx.media2.SessionCommand2);
method public androidx.media2.SessionCommandGroup2.Builder addCommand(int);
method public androidx.media2.SessionCommandGroup2 build();
method public androidx.media2.SessionCommandGroup2.Builder removeCommand(androidx.media2.SessionCommand2);
@@ -600,7 +690,7 @@
method public void onPlaybackCompleted(androidx.media2.SessionPlayer2);
method public void onPlaybackSpeedChanged(androidx.media2.SessionPlayer2, float);
method public void onPlayerStateChanged(androidx.media2.SessionPlayer2, int);
- method public void onPlaylistChanged(androidx.media2.SessionPlayer2, java.util.List<androidx.media2.MediaItem2>!, androidx.media2.MediaMetadata2?);
+ method public void onPlaylistChanged(androidx.media2.SessionPlayer2, java.util.List<androidx.media2.MediaItem2>?, androidx.media2.MediaMetadata2?);
method public void onPlaylistMetadataChanged(androidx.media2.SessionPlayer2, androidx.media2.MediaMetadata2?);
method public void onRepeatModeChanged(androidx.media2.SessionPlayer2, int);
method public void onSeekCompleted(androidx.media2.SessionPlayer2, long);
@@ -673,143 +763,11 @@
public static final class UriMediaItem2.Builder {
ctor public UriMediaItem2.Builder(android.content.Context, android.net.Uri);
ctor public UriMediaItem2.Builder(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
- method public androidx.media2.UriMediaItem2! build();
+ method public androidx.media2.UriMediaItem2 build();
method public androidx.media2.UriMediaItem2.Builder! setEndPosition(long);
- method public androidx.media2.UriMediaItem2.Builder! setMediaId(String!);
method public androidx.media2.UriMediaItem2.Builder! setMetadata(androidx.media2.MediaMetadata2!);
method public androidx.media2.UriMediaItem2.Builder! setStartPosition(long);
}
- public class XMediaPlayer extends androidx.media2.SessionPlayer2 {
- ctor public XMediaPlayer(android.content.Context!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! addPlaylistItem(int, androidx.media2.MediaItem2!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! attachAuxEffect(int);
- method public void close() throws java.lang.Exception;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! deselectTrack(int);
- method public androidx.media.AudioAttributesCompat! getAudioAttributes();
- method public int getAudioSessionId();
- method public long getBufferedPosition();
- method public int getBufferingState();
- method public androidx.media2.MediaItem2! getCurrentMediaItem();
- method public long getCurrentPosition();
- method public androidx.media2.XMediaPlayer.DrmInfo! getDrmInfo();
- method public android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[]?, byte[]?, String?, int, java.util.Map<java.lang.String,java.lang.String>?) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public String getDrmPropertyString(String) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public long getDuration();
- method public float getMaxPlayerVolume();
- method public androidx.media2.PlaybackParams2! getPlaybackParams();
- method public float getPlaybackSpeed();
- method public int getPlayerState();
- method public float getPlayerVolume();
- method public java.util.List<androidx.media2.MediaItem2>! getPlaylist();
- method public androidx.media2.MediaMetadata2! getPlaylistMetadata();
- method public int getRepeatMode();
- method public int getSelectedTrack(int);
- method public int getShuffleMode();
- method public androidx.media2.MediaTimestamp2? getTimestamp();
- method public java.util.List<androidx.media2.XMediaPlayer.TrackInfo>! getTrackInfo();
- method public int getVideoHeight();
- method public int getVideoWidth();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! pause();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! play();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! prepare();
- method public androidx.concurrent.futures.ResolvableFuture<androidx.media2.XMediaPlayer.DrmResult>! prepareDrm(java.util.UUID);
- method public byte[]! provideDrmKeyResponse(byte[]?, byte[]) throws android.media.DeniedByServerException, androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public void releaseDrm() throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! removePlaylistItem(androidx.media2.MediaItem2!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! replacePlaylistItem(int, androidx.media2.MediaItem2!);
- method public void reset();
- method public void restoreDrmKeys(byte[]) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! seekTo(long);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! seekTo(long, int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! selectTrack(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setAudioAttributes(androidx.media.AudioAttributesCompat!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setAudioSessionId(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setAuxEffectSendLevel(float);
- method public void setDrmPropertyString(String, String) throws androidx.media2.XMediaPlayer.NoDrmSchemeException;
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setMediaItem(androidx.media2.MediaItem2);
- method public void setOnDrmConfigHelper(androidx.media2.XMediaPlayer.OnDrmConfigHelper!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlaybackParams(androidx.media2.PlaybackParams2);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlaybackSpeed(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlayerVolume(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setPlaylist(java.util.List<androidx.media2.MediaItem2>, androidx.media2.MediaMetadata2?);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setRepeatMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setShuffleMode(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! setSurface(android.view.Surface!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! skipToNextPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! skipToPlaylistItem(androidx.media2.MediaItem2!);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! skipToPreviousPlaylistItem();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer2.PlayerResult>! updatePlaylistMetadata(androidx.media2.MediaMetadata2!);
- field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
- field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
- field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
- field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
- field public static final int MEDIA_INFO_PREFETCHED = 100; // 0x64
- field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
- field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
- field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
- field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
- field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
- field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
- field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
- field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
- field public static final int SEEK_CLOSEST = 3; // 0x3
- field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
- field public static final int SEEK_NEXT_SYNC = 1; // 0x1
- field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
- }
-
- public static final class XMediaPlayer.DrmInfo {
- method public java.util.Map<java.util.UUID,byte[]>! getPssh();
- method public java.util.List<java.util.UUID>! getSupportedSchemes();
- }
-
- public static class XMediaPlayer.DrmResult extends androidx.media2.SessionPlayer2.PlayerResult {
- ctor public XMediaPlayer.DrmResult(int, androidx.media2.MediaItem2?);
- field public static final int RESULT_CODE_BAD_VALUE = -3; // 0xfffffffd
- field public static final int RESULT_CODE_INVALID_STATE = -2; // 0xfffffffe
- field public static final int RESULT_CODE_IO_ERROR = -5; // 0xfffffffb
- field public static final int RESULT_CODE_NOT_SUPPORTED = -6; // 0xfffffffa
- field public static final int RESULT_CODE_PERMISSION_DENIED = -4; // 0xfffffffc
- field public static final int RESULT_CODE_PREPARATION_ERROR = -1003; // 0xfffffc15
- field public static final int RESULT_CODE_PROVISIONING_NETWORK_ERROR = -1001; // 0xfffffc17
- field public static final int RESULT_CODE_PROVISIONING_SERVER_ERROR = -1002; // 0xfffffc16
- field public static final int RESULT_CODE_RESOURCE_BUSY = -1005; // 0xfffffc13
- field public static final int RESULT_CODE_SKIPPED = 1; // 0x1
- field public static final int RESULT_CODE_SUCCESS = 0; // 0x0
- field public static final int RESULT_CODE_UNKNOWN_ERROR = -1; // 0xffffffff
- field public static final int RESULT_CODE_UNSUPPORTED_SCHEME = -1004; // 0xfffffc14
- }
-
- public static class XMediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
- ctor public XMediaPlayer.NoDrmSchemeException(String!);
- }
-
- public static interface XMediaPlayer.OnDrmConfigHelper {
- method public void onDrmConfig(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!);
- }
-
- public abstract static class XMediaPlayer.PlayerCallback extends androidx.media2.SessionPlayer2.PlayerCallback {
- ctor public XMediaPlayer.PlayerCallback();
- method public void onDrmInfo(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.XMediaPlayer.DrmInfo!);
- method public void onError(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, int, int);
- method public void onInfo(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, int, int);
- method public void onMediaTimeDiscontinuity(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.MediaTimestamp2!);
- method public void onSubtitleData(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.SubtitleData2);
- method public void onTimedMetaDataAvailable(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, androidx.media2.TimedMetaData2!);
- method public void onVideoSizeChanged(androidx.media2.XMediaPlayer!, androidx.media2.MediaItem2!, int, int);
- }
-
- public static final class XMediaPlayer.TrackInfo {
- method public android.media.MediaFormat! getFormat();
- method public String! getLanguage();
- method public int getTrackType();
- field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
- field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
- field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
- field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
- field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
- }
-
}
diff --git a/media2/src/androidTest/java/androidx/media2/MediaBrowser2Test.java b/media2/src/androidTest/java/androidx/media2/MediaBrowser2Test.java
index c25dd91..4348a2c 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaBrowser2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaBrowser2Test.java
@@ -357,7 +357,7 @@
LibraryParams params) {
// This wouldn't be called at all.
return new LibraryResult(RESULT_CODE_SUCCESS,
- TestUtils.createPlaylist(testChildrenCount), null);
+ TestUtils.createMediaItems(testChildrenCount), null);
}
};
final CountDownLatch latch = new CountDownLatch(1);
@@ -410,7 +410,7 @@
ControllerInfo controller, String parentId, int page, int pageSize,
LibraryParams params) {
return new LibraryResult(RESULT_CODE_SUCCESS,
- TestUtils.createPlaylist(testChildrenCount), null);
+ TestUtils.createMediaItems(testChildrenCount), null);
}
};
final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
@@ -460,7 +460,7 @@
ControllerInfo controller, String parentId, int page, int pageSize,
LibraryParams params) {
return new LibraryResult(RESULT_CODE_SUCCESS,
- TestUtils.createPlaylist(testChildrenCount), null);
+ TestUtils.createMediaItems(testChildrenCount), null);
}
};
final CountDownLatch latch = new CountDownLatch(1);
@@ -515,7 +515,7 @@
ControllerInfo controller, String parentId, int page, int pageSize,
LibraryParams params) {
return new LibraryResult(RESULT_CODE_SUCCESS,
- TestUtils.createPlaylist(testChildrenCount), null);
+ TestUtils.createMediaItems(testChildrenCount), null);
}
};
final BrowserCallback controllerCallbackProxy = new BrowserCallback() {
diff --git a/media2/src/androidTest/java/androidx/media2/MediaController2Test.java b/media2/src/androidTest/java/androidx/media2/MediaController2Test.java
index 52920dc..c5abb08 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaController2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaController2Test.java
@@ -38,6 +38,7 @@
import android.os.Process;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat;
import androidx.media.VolumeProviderCompat;
import androidx.media2.MediaController2.ControllerCallback;
@@ -103,6 +104,12 @@
}
return null;
}
+
+ @Override
+ public MediaItem2 onCreateMediaItem(MediaSession2 session,
+ ControllerInfo controller, String mediaId) {
+ return TestUtils.createMediaItem(mediaId);
+ }
})
.setSessionActivity(mIntent)
.setId(TAG).build();
@@ -185,7 +192,7 @@
@Test
public void testPrepare() {
prepareLooper();
- mController.prefetch();
+ mController.prepare();
try {
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
@@ -241,7 +248,7 @@
public void testUpdatePlayer() throws InterruptedException {
prepareLooper();
final int testState = SessionPlayer2.PLAYER_STATE_PLAYING;
- final List<MediaItem2> testPlaylist = TestUtils.createPlaylist(3);
+ final List<MediaItem2> testPlaylist = TestUtils.createMediaItems(3);
final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder()
.setLegacyStreamType(AudioManager.STREAM_RING).build();
final CountDownLatch latch = new CountDownLatch(3);
@@ -294,7 +301,7 @@
@Test
public void testSetPlaylist() throws InterruptedException {
prepareLooper();
- final List<MediaItem2> list = TestUtils.createPlaylist(2);
+ final List<String> list = TestUtils.createMediaIds(2);
mController.setPlaylist(list, null /* Metadata */);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -305,7 +312,7 @@
assertEquals(list.size(), mPlayer.mPlaylist.size());
for (int i = 0; i < list.size(); i++) {
// MediaController2.setPlaylist does not ensure the equality of the items.
- assertEquals(list.get(i).getMediaId(), mPlayer.mPlaylist.get(i).getMediaId());
+ assertEquals(list.get(i), mPlayer.mPlaylist.get(i).getMediaId());
}
}
@@ -313,7 +320,8 @@
public void testSetMediaItem() throws InterruptedException {
prepareLooper();
final MediaItem2 item = TestUtils.createMediaItemWithMetadata();
- mController.setMediaItem(item);
+ mController.setMediaItem(item.getMetadata()
+ .getString(MediaMetadata2.METADATA_KEY_MEDIA_ID));
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertNull(mPlayer.mMetadata);
@@ -327,7 +335,7 @@
@Test
public void testGetPlaylist() throws InterruptedException {
prepareLooper();
- final List<MediaItem2> testList = TestUtils.createPlaylist(2);
+ final List<MediaItem2> testList = TestUtils.createMediaItems(2);
final MediaMetadata2 testMetadata = TestUtils.createMetadata();
final AtomicReference<List<MediaItem2>> listFromCallback = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
@@ -360,7 +368,7 @@
@LargeTest
public void testGetPlaylist_withLongPlaylist() throws InterruptedException {
prepareLooper();
- final List<MediaItem2> testList = TestUtils.createPlaylist(5000);
+ final List<MediaItem2> testList = TestUtils.createMediaItems(5000);
final MediaMetadata2 testMetadata = TestUtils.createMetadata();
final AtomicReference<List<MediaItem2>> listFromCallback = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
@@ -474,7 +482,7 @@
public void testControllerCallback_onPlaylistMetadataChanged() throws InterruptedException {
prepareLooper();
final MediaItem2 item = TestUtils.createMediaItemWithMetadata();
- final List<MediaItem2> list = TestUtils.createPlaylist(2);
+ final List<MediaItem2> list = TestUtils.createMediaItems(2);
final CountDownLatch latch = new CountDownLatch(1);
final ControllerCallback callback = new ControllerCallback() {
@Override
@@ -542,7 +550,7 @@
@Test
public void testControllerCallback_onBufferingStateChanged() throws InterruptedException {
prepareLooper();
- final List<MediaItem2> testPlaylist = TestUtils.createPlaylist(3);
+ final List<MediaItem2> testPlaylist = TestUtils.createMediaItems(3);
final MediaItem2 testItem = testPlaylist.get(0);
final int testBufferingState = SessionPlayer2.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
final long testBufferingPosition = 500;
@@ -602,7 +610,7 @@
public void testControllerCallback_onCurrentMediaItemChanged() throws InterruptedException {
prepareLooper();
final int listSize = 5;
- final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
+ final List<MediaItem2> list = TestUtils.createMediaItems(listSize);
mPlayer.setPlaylist(list, null);
final MediaItem2 currentItem = list.get(3);
@@ -640,27 +648,26 @@
public void testAddPlaylistItem() throws InterruptedException {
prepareLooper();
final int testIndex = 12;
- final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
- mController.addPlaylistItem(testIndex, testMediaItem);
+ final String testId = "testAddPlaylistItem";
+ mController.addPlaylistItem(testIndex, testId);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mAddPlaylistItemCalled);
assertEquals(testIndex, mPlayer.mIndex);
- // MediaController2.addPlaylistItem does not ensure the equality of the items.
- assertEquals(testMediaItem.getMediaId(), mPlayer.mItem.getMediaId());
+ assertEquals(testId, mPlayer.mItem.getMediaId());
}
@Test
public void testRemovePlaylistItem() throws InterruptedException {
prepareLooper();
- mPlayer.mPlaylist = TestUtils.createPlaylist(2);
+ mPlayer.mPlaylist = TestUtils.createMediaItems(2);
// Recreate controller for sending removePlaylistItem.
// It's easier to ensure that MediaController2.getPlaylist() returns the playlist from the
- // agent.
+ // player.
MediaController2 controller = createController(mSession.getToken());
MediaItem2 targetItem = controller.getPlaylist().get(0);
- controller.removePlaylistItem(targetItem);
+ controller.removePlaylistItem(0);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mRemovePlaylistItemCalled);
@@ -671,13 +678,13 @@
public void testReplacePlaylistItem() throws InterruptedException {
prepareLooper();
final int testIndex = 12;
- final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
- mController.replacePlaylistItem(testIndex, testMediaItem);
+ final String testId = "testAddPlaylistItem";
+ mController.replacePlaylistItem(testIndex, testId);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mReplacePlaylistItemCalled);
// MediaController2.replacePlaylistItem does not ensure the equality of the items.
- assertEquals(testMediaItem.getMediaId(), mPlayer.mItem.getMediaId());
+ assertEquals(testId, mPlayer.mItem.getMediaId());
}
@Test
@@ -699,13 +706,15 @@
@Test
public void testSkipToPlaylistItem() throws InterruptedException {
prepareLooper();
+ List<MediaItem2> playlist = TestUtils.createMediaItems(2);
+ mPlayer.mPlaylist = playlist;
+
MediaController2 controller = createController(mSession.getToken());
- MediaItem2 targetItem = TestUtils.createMediaItemWithMetadata();
- controller.skipToPlaylistItem(targetItem);
+ controller.skipToPlaylistItem(1);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSkipToPlaylistItemCalled);
- assertEquals(targetItem, mPlayer.mItem);
+ assertEquals(playlist.get(1), mPlayer.mItem);
}
/**
@@ -1120,7 +1129,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@Override
- public int onPrefetchFromSearch(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
String query, Bundle extras) {
assertEquals(mContext.getPackageName(), controller.getPackageName());
assertEquals(request, query);
@@ -1133,7 +1142,7 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromSearch").build()) {
MediaController2 controller = createController(session.getToken());
- controller.prefetchFromSearch(request, bundle);
+ controller.prepareFromSearch(request, bundle);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -1147,7 +1156,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@Override
- public int onPrefetchFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
+ public int onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
Bundle extras) {
assertEquals(mContext.getPackageName(), controller.getPackageName());
assertEquals(request, uri);
@@ -1160,7 +1169,7 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromUri").build()) {
MediaController2 controller = createController(session.getToken());
- controller.prefetchFromUri(request, bundle);
+ controller.prepareFromUri(request, bundle);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -1174,7 +1183,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@Override
- public int onPrefetchFromMediaId(MediaSession2 session, ControllerInfo controller,
+ public int onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
String mediaId, Bundle extras) {
assertEquals(mContext.getPackageName(), controller.getPackageName());
assertEquals(request, mediaId);
@@ -1187,7 +1196,7 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromMediaId").build()) {
MediaController2 controller = createController(session.getToken());
- controller.prefetchFromMediaId(request, bundle);
+ controller.prepareFromMediaId(request, bundle);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -1531,6 +1540,73 @@
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+ @Test
+ public void testSetMetadataForCurrentMediaItem() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(2);
+ final long duration = 1000L;
+ final MediaItem2 item = TestUtils.createMediaItemWithMetadata();
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onCurrentMediaItemChanged(@NonNull MediaController2 controller,
+ @Nullable MediaItem2 item) {
+ MediaMetadata2 metadata = item.getMetadata();
+ if (metadata != null) {
+ switch ((int) latch.getCount()) {
+ case 2:
+ assertFalse(metadata.containsKey(
+ MediaMetadata2.METADATA_KEY_DURATION));
+ break;
+ case 1:
+ assertTrue(metadata.containsKey(
+ MediaMetadata2.METADATA_KEY_DURATION));
+ assertEquals(duration,
+ metadata.getLong(MediaMetadata2.METADATA_KEY_DURATION));
+ }
+ }
+ latch.countDown();
+ }
+ };
+ MediaController2 controller = createController(mSession.getToken(), true, callback);
+ mPlayer.setMediaItem(item);
+ mPlayer.notifyCurrentMediaItemChanged(item);
+ assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ item.setMetadata(TestUtils.createMetadata(item.getMediaId(), duration));
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testSetMetadataForMediaItemInPlaylist() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(2);
+ final long duration = 1000L;
+ final List<MediaItem2> list = TestUtils.createMediaItems(2);
+ final MediaMetadata2 oldMetadata = list.get(1).getMetadata();
+ final MediaMetadata2 newMetadata = TestUtils.createMetadata(oldMetadata.getMediaId(),
+ duration);
+ final ControllerCallback callback = new ControllerCallback() {
+ @Override
+ public void onPlaylistChanged(@NonNull MediaController2 controller,
+ @NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
+ switch ((int) latch.getCount()) {
+ case 2:
+ assertFalse(oldMetadata.containsKey(MediaMetadata2.METADATA_KEY_DURATION));
+ break;
+ case 1:
+ assertTrue(list.get(1).getMetadata().containsKey(
+ MediaMetadata2.METADATA_KEY_DURATION));
+ assertEquals(duration, list.get(1).getMetadata().getLong(
+ MediaMetadata2.METADATA_KEY_DURATION));
+ }
+ latch.countDown();
+ }
+ };
+ MediaController2 controller = createController(mSession.getToken(), true, callback);
+ mPlayer.setPlaylist(list, null);
+ mPlayer.notifyPlaylistChanged();
+ assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ list.get(1).setMetadata(newMetadata);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
private void testCloseFromService(String id) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
TestServiceRegistry.getInstance().setSessionServiceCallback(new SessionServiceCallback() {
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
index 5d9e156..51ac7844 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
@@ -737,7 +737,7 @@
public void testVideoSurfaceResetting() throws Exception {
final int tolerance = 150;
final int audioLatencyTolerance = 1000; /* covers audio path latency variability */
- final int seekPos = 4760; // This is the I-frame position
+ final int seekPos = 1840; // This is the I-frame position
final CountDownLatch seekDone = new CountDownLatch(1);
@@ -754,10 +754,11 @@
mEventCallbacks.add(ecb);
}
- if (!checkLoadResource(R.raw.testvideo)) {
+ if (!checkLoadResource(
+ R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz)) {
return; // skip;
}
- playLoadedVideo(352, 288, -1);
+ playLoadedVideo(480, 360, -1);
Thread.sleep(SLEEP_TIME);
diff --git a/media2/src/androidTest/java/androidx/media2/XMediaPlayerDrmTest.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
similarity index 96%
rename from media2/src/androidTest/java/androidx/media2/XMediaPlayerDrmTest.java
rename to media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
index 57dcff4..13ef147 100644
--- a/media2/src/androidTest/java/androidx/media2/XMediaPlayerDrmTest.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
@@ -43,10 +43,10 @@
import android.view.WindowManager;
import androidx.annotation.CallSuper;
+import androidx.media2.MediaPlayer.DrmInfo;
+import androidx.media2.MediaPlayer.DrmResult;
import androidx.media2.SessionPlayer2.PlayerResult;
import androidx.media2.TestUtils.Monitor;
-import androidx.media2.XMediaPlayer.DrmInfo;
-import androidx.media2.XMediaPlayer.DrmResult;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.Suppress;
@@ -80,12 +80,12 @@
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * DRM tests which use XMediaPlayer to play audio or video.
+ * DRM tests which use MediaPlayer to play audio or video.
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@Suppress // Disabled as it 100% fails b/79682973
-public class XMediaPlayerDrmTest {
+public class MediaPlayerDrmTest {
private static final int STREAM_RETRIES = 3;
private Monitor mOnVideoSizeChangedCalled = new Monitor();
@@ -96,12 +96,12 @@
private Context mContext;
private Resources mResources;
- private XMediaPlayer mPlayer = null;
+ private MediaPlayer mPlayer = null;
private MediaStubActivity mActivity;
private Instrumentation mInstrumentation;
private ExecutorService mExecutor;
- private XMediaPlayer.PlayerCallback mECb = null;
+ private MediaPlayer.PlayerCallback mECb = null;
@Rule
public GrantPermissionRule mRuntimePermissionRule =
@@ -134,7 +134,7 @@
try {
mActivityRule.runOnUiThread(new Runnable() {
public void run() {
- mPlayer = new XMediaPlayer(mActivity);
+ mPlayer = new MediaPlayer(mActivity);
}
});
} catch (Throwable e) {
@@ -256,7 +256,7 @@
//////////////////////////////////////////////////////////////////////////////////////////
// Modular DRM
- private static final String TAG = "XMediaPlayerDrmTest";
+ private static final String TAG = "MediaPlayerDrmTest";
private static final int PLAY_TIME_MS = 60 * 1000;
private byte[] mKeySetId;
@@ -377,21 +377,21 @@
mAudioOnly = (width == 0);
- mECb = new XMediaPlayer.PlayerCallback() {
+ mECb = new MediaPlayer.PlayerCallback() {
@Override
- public void onVideoSizeChanged(XMediaPlayer mp, MediaItem2 item, int w, int h) {
+ public void onVideoSizeChanged(MediaPlayer mp, MediaItem2 item, int w, int h) {
Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
mOnVideoSizeChangedCalled.signal();
}
@Override
- public void onError(XMediaPlayer mp, MediaItem2 item, int what, int extra) {
+ public void onError(MediaPlayer mp, MediaItem2 item, int what, int extra) {
fail("Media player had error " + what + " playing video");
}
@Override
- public void onInfo(XMediaPlayer mp, MediaItem2 item, int what, int extra) {
- if (what == XMediaPlayer.MEDIA_INFO_MEDIA_ITEM_END) {
+ public void onInfo(MediaPlayer mp, MediaItem2 item, int what, int extra) {
+ if (what == MediaPlayer.MEDIA_INFO_MEDIA_ITEM_END) {
Log.v(TAG, "playLoadedVideo: onInfo_PlaybackComplete");
mOnPlaybackCompleted.signal();
}
@@ -474,9 +474,9 @@
throws InterruptedException, ExecutionException {
final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
- mPlayer.registerPlayerCallback(mExecutor, new XMediaPlayer.PlayerCallback() {
+ mPlayer.registerPlayerCallback(mExecutor, new MediaPlayer.PlayerCallback() {
@Override
- public void onDrmInfo(XMediaPlayer mp, MediaItem2 item, DrmInfo drmInfo) {
+ public void onDrmInfo(MediaPlayer mp, MediaItem2 item, DrmInfo drmInfo) {
Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
// in the callback (async mode) so handling exceptions here
@@ -506,9 +506,9 @@
}
private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
- mPlayer.setOnDrmConfigHelper(new XMediaPlayer.OnDrmConfigHelper() {
+ mPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() {
@Override
- public void onDrmConfig(XMediaPlayer mp, MediaItem2 item) {
+ public void onDrmConfig(MediaPlayer mp, MediaItem2 item) {
String widevineSecurityLevel3 = "L3";
String securityLevelProperty = "securityLevel";
@@ -520,7 +520,7 @@
level = mp.getDrmPropertyString(securityLevelProperty);
Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
+ securityLevelProperty + " -> " + level);
- } catch (XMediaPlayer.NoDrmSchemeException e) {
+ } catch (MediaPlayer.NoDrmSchemeException e) {
Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
} catch (Exception e) {
Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
@@ -544,9 +544,9 @@
throws InterruptedException, ExecutionException {
final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
- mPlayer.registerPlayerCallback(mExecutor, new XMediaPlayer.PlayerCallback() {
+ mPlayer.registerPlayerCallback(mExecutor, new MediaPlayer.PlayerCallback() {
@Override
- public void onDrmInfo(XMediaPlayer mp, MediaItem2 item, DrmInfo drmInfo) {
+ public void onDrmInfo(MediaPlayer mp, MediaItem2 item, DrmInfo drmInfo) {
Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
// DRM preperation
@@ -831,7 +831,7 @@
// storing offline key for a later restore
mKeySetId = (keyType == MediaDrm.KEY_TYPE_OFFLINE) ? keySetId : null;
- } catch (XMediaPlayer.NoDrmSchemeException e) {
+ } catch (MediaPlayer.NoDrmSchemeException e) {
Log.d(TAG, "setupDrm: NoDrmSchemeException");
e.printStackTrace();
throw e;
@@ -866,7 +866,7 @@
mPlayer.restoreDrmKeys(mKeySetId);
- } catch (XMediaPlayer.NoDrmSchemeException e) {
+ } catch (MediaPlayer.NoDrmSchemeException e) {
Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
e.printStackTrace();
throw e;
diff --git a/media2/src/androidTest/java/androidx/media2/XMediaPlayerTest.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerTest.java
similarity index 90%
rename from media2/src/androidTest/java/androidx/media2/XMediaPlayerTest.java
rename to media2/src/androidTest/java/androidx/media2/MediaPlayerTest.java
index 568644a..e8a3826 100644
--- a/media2/src/androidTest/java/androidx/media2/XMediaPlayerTest.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerTest.java
@@ -63,8 +63,8 @@
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
-public class XMediaPlayerTest extends XMediaPlayerTestBase {
- private static final String LOG_TAG = "XMediaPlayerTest";
+public class MediaPlayerTest extends MediaPlayerTestBase {
+ private static final String LOG_TAG = "MediaPlayerTest";
private static final int SLEEP_TIME = 1000;
private static final int WAIT_TIME_MS = 300;
@@ -137,10 +137,10 @@
ListenableFuture<PlayerResult> future = mPlayer.prepare();
assertEquals(RESULT_CODE_SUCCESS, future.get().getResultCode());
- assertFalse(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertFalse(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
future = mPlayer.play();
assertEquals(RESULT_CODE_SUCCESS, future.get().getResultCode());
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
assertEquals(mp3Duration, mPlayer.getDuration(), tolerance);
long pos = mPlayer.getCurrentPosition();
@@ -153,13 +153,13 @@
future = mPlayer.pause();
assertEquals(RESULT_CODE_SUCCESS, future.get().getResultCode());
- assertFalse(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertFalse(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
future = mPlayer.play();
assertEquals(RESULT_CODE_SUCCESS, future.get().getResultCode());
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
// waiting to complete
- while (mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING) {
+ while (mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING) {
Thread.sleep(SLEEP_TIME);
}
}
@@ -178,9 +178,9 @@
final TestUtils.Monitor onVideoSizeChangedCalled = new TestUtils.Monitor();
final TestUtils.Monitor onVideoRenderingStartCalled = new TestUtils.Monitor();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
- public void onVideoSizeChanged(XMediaPlayer mp, MediaItem2 dsd, int w, int h) {
+ public void onVideoSizeChanged(MediaPlayer mp, MediaItem2 dsd, int w, int h) {
if (w == 0 && h == 0) {
// A size of 0x0 can be sent initially one time when using NuPlayer.
assertFalse(onVideoSizeChangedCalled.isSignalled());
@@ -192,13 +192,13 @@
}
@Override
- public void onError(XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ public void onError(MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
fail("Media player had error " + what + " playing video");
}
@Override
- public void onInfo(XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
- if (what == XMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
+ public void onInfo(MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
onVideoRenderingStartCalled.signal();
}
}
@@ -214,7 +214,7 @@
mPlayer.setPlayerVolume(volume);
// waiting to complete
- while (mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING) {
+ while (mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING) {
Thread.sleep(SLEEP_TIME);
}
@@ -266,34 +266,34 @@
final int tolerance = 70;
mPlayer.setSurface(mActivity.getSurfaceHolder2().getSurface());
- assertEquals(XMediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
- assertEquals(XMediaPlayer.UNKNOWN_TIME, mPlayer.getDuration());
+ assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.UNKNOWN_TIME, mPlayer.getDuration());
ListenableFuture<PlayerResult> future = mPlayer.prepare();
assertEquals(RESULT_CODE_SUCCESS, future.get().getResultCode());
- assertEquals(XMediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
assertEquals(expectedDuration, mPlayer.getDuration(), tolerance);
}
@Test
@SmallTest
public void testGetCurrentPosition() throws Exception {
- assertEquals(XMediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
- assertEquals(XMediaPlayer.UNKNOWN_TIME, mPlayer.getCurrentPosition());
+ assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.UNKNOWN_TIME, mPlayer.getCurrentPosition());
}
@Test
@SmallTest
public void testGetBufferedPosition() throws Exception {
- assertEquals(XMediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
- assertEquals(XMediaPlayer.UNKNOWN_TIME, mPlayer.getBufferedPosition());
+ assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.UNKNOWN_TIME, mPlayer.getBufferedPosition());
}
@Test
@SmallTest
public void testGetPlaybackSpeed() throws Exception {
- assertEquals(XMediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
try {
mPlayer.getPlaybackSpeed();
} catch (Exception e) {
@@ -334,7 +334,7 @@
PlaybackParams2 pbp = mPlayer.getPlaybackParams();
assertEquals(playbackRate, pbp.getSpeed(), FLOAT_TOLERANCE);
assertTrue("The player should still be playing",
- mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
long playedMediaDurationMs = mPlayer.getCurrentPosition();
int diff = Math.abs((int) (playedMediaDurationMs / playbackRate) - playTime);
@@ -478,7 +478,7 @@
private void readSubtitleTracks() throws Exception {
mSubtitleTrackIndex.clear();
- List<XMediaPlayer.TrackInfo> trackInfos = mPlayer.getTrackInfo();
+ List<MediaPlayer.TrackInfo> trackInfos = mPlayer.getTrackInfo();
if (trackInfos == null || trackInfos.size() == 0) {
return;
}
@@ -520,16 +520,16 @@
mInstrumentation.waitForIdleSync();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
- public void onInfo(XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ public void onInfo(MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
mOnInfoCalled.signal();
}
}
@Override
- public void onSubtitleData(XMediaPlayer mp, MediaItem2 dsd, SubtitleData2 data) {
+ public void onSubtitleData(MediaPlayer mp, MediaItem2 dsd, SubtitleData2 data) {
if (data != null && data.getData() != null) {
mOnSubtitleDataCalled.signal();
}
@@ -539,7 +539,7 @@
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
// Closed caption tracks are in-band.
// So, those tracks will be found after processing a number of frames.
@@ -575,9 +575,9 @@
fail();
}
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
- public void onInfo(XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ public void onInfo(MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
mOnInfoCalled.signal();
}
@@ -585,7 +585,7 @@
@Override
public void onSubtitleData(
- XMediaPlayer mp, MediaItem2 dsd, SubtitleData2 data) {
+ MediaPlayer mp, MediaItem2 dsd, SubtitleData2 data) {
if (data != null && data.getData() != null) {
mOnSubtitleDataCalled.signal();
}
@@ -595,7 +595,7 @@
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
// Closed caption tracks are in-band.
// So, those tracks will be found after processing a number of frames.
@@ -624,9 +624,9 @@
fail();
}
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
- public void onInfo(XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ public void onInfo(MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
mOnInfoCalled.signal();
}
@@ -636,7 +636,7 @@
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
// The media metadata will be changed while playing since closed caption tracks are in-band
// and those tracks will be found after processing a number of frames. These tracks will be
@@ -661,10 +661,10 @@
}
final BlockingDeque<MediaTimestamp2> timestamps = new LinkedBlockingDeque<>();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onMediaTimeDiscontinuity(
- XMediaPlayer mp, MediaItem2 dsd, MediaTimestamp2 timestamp) {
+ MediaPlayer mp, MediaItem2 dsd, MediaTimestamp2 timestamp) {
timestamps.add(timestamp);
mOnMediaTimeDiscontinuityCalled.signal();
}
@@ -719,15 +719,15 @@
mPlayer.setMediaItem(new CallbackMediaItem2.Builder(dataSource).build());
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
// Test pause and restart.
mPlayer.pause();
Thread.sleep(SLEEP_TIME);
- assertFalse(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertFalse(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
// Test reset.
mPlayer.reset();
@@ -735,12 +735,12 @@
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING);
+ assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
// Test seek. Note: the seek position is cached and returned as the
// current position so there's no point in comparing them.
mPlayer.seekTo(duration - SLEEP_TIME, MediaPlayer2.SEEK_PREVIOUS_SYNC);
- while (mPlayer.getPlayerState() == XMediaPlayer.PLAYER_STATE_PLAYING) {
+ while (mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING) {
Thread.sleep(SLEEP_TIME);
}
}
@@ -772,10 +772,10 @@
public void testPlaybackFailsIfMedia2DataSourceThrows() throws Exception {
final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onError(
- XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
mOnErrorCalled.signal();
}
};
@@ -802,10 +802,10 @@
TestDataSourceCallback2.fromAssetFd(mResources.openRawResourceFd(resid));
mPlayer.setMediaItem(new CallbackMediaItem2.Builder(dataSource).build());
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onError(
- XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
mOnErrorCalled.signal();
}
};
@@ -821,7 +821,7 @@
@Test
@LargeTest
public void testPreservePlaybackProperties() throws Exception {
- /* TODO: enable this test once XMediaPlayer has playlist implementation.
+ /* TODO: enable this test once MediaPlayer has playlist implementation.
final int resid1 = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
final long start1 = 6000;
final long end1 = 7000;
@@ -846,7 +846,7 @@
mPlayer.setNextMediaItem(dsd2);
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onInfo(MediaPlayer2 mp, MediaItem2 dsd, int what, int extra) {
if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -936,9 +936,9 @@
}
};
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
- public void onError(XMediaPlayer mp, MediaItem2 dsd, int what, int extra) {
+ public void onError(MediaPlayer mp, MediaItem2 dsd, int what, int extra) {
mOnErrorCalled.signal();
}
};
@@ -971,33 +971,33 @@
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
ListenableFuture<PlayerResult> future;
- assertEquals(XMediaPlayer.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
- assertEquals(XMediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
+ assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
future = mPlayer.prepare();
assertEquals(MediaPlayer2.CALL_STATUS_NO_ERROR, future.get().getResultCode());
- assertEquals(XMediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
+ assertEquals(MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
mPlayer.getBufferingState());
- assertEquals(XMediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
future = mPlayer.play();
assertEquals(MediaPlayer2.CALL_STATUS_NO_ERROR, future.get().getResultCode());
- assertEquals(XMediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
+ assertEquals(MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
mPlayer.getBufferingState());
- assertEquals(XMediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
future = mPlayer.pause();
assertEquals(MediaPlayer2.CALL_STATUS_NO_ERROR, future.get().getResultCode());
- assertEquals(XMediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
+ assertEquals(MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
mPlayer.getBufferingState());
- assertEquals(XMediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
mPlayer.reset();
- assertEquals(XMediaPlayer.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
- assertEquals(XMediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
+ assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
}
@Test
@@ -1017,7 +1017,7 @@
final TestUtils.Monitor onPlaybackSpeedChanged = new TestUtils.Monitor();
final AtomicReference<Float> playbackSpeed = new AtomicReference<>();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onPlayerStateChanged(SessionPlayer2 player, int state) {
playerState.set(state);
@@ -1050,16 +1050,16 @@
future = mPlayer.prepare();
do {
assertTrue(onBufferingStateChangedCalled.waitForSignal(1000));
- } while (bufferingState.get() != XMediaPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED);
+ } while (bufferingState.get() != MediaPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED);
assertEquals(MediaPlayer2.CALL_STATUS_NO_ERROR, future.get().getResultCode());
do {
assertTrue(onPlayerStateChangedCalled.waitForSignal(1000));
- } while (playerState.get() != XMediaPlayer.PLAYER_STATE_PAUSED);
+ } while (playerState.get() != MediaPlayer.PLAYER_STATE_PAUSED);
do {
assertTrue(onBufferingStateChangedCalled.waitForSignal(1000));
- } while (bufferingState.get() != XMediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
+ } while (bufferingState.get() != MediaPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
onSeekCompleteCalled.reset();
mPlayer.seekTo(mp4Duration >> 1);
@@ -1072,7 +1072,7 @@
} while (Math.abs(playbackSpeed.get() - 0.5f) > FLOAT_TOLERANCE);
mPlayer.reset();
- assertEquals(XMediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+ assertEquals(MediaPlayer.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
mPlayer.unregisterPlayerCallback(callback);
}
@@ -1138,9 +1138,9 @@
mTestSource.close();
}
};
- XMediaPlayer.PlayerCallback ecb = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback ecb = new MediaPlayer.PlayerCallback() {
@Override
- public void onError(XMediaPlayer mp, MediaItem2 item, int what, int extra) {
+ public void onError(MediaPlayer mp, MediaItem2 item, int what, int extra) {
mOnErrorCalled.signal();
}
};
@@ -1175,7 +1175,7 @@
@SmallTest
public void testSetAndGetShuflleMode() throws Exception {
final TestUtils.Monitor onShuffleModeChangedMonitor = new TestUtils.Monitor();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onShuffleModeChanged(SessionPlayer2 player, int shuffleMode) {
mPlayerCbArg1 = player;
@@ -1231,7 +1231,7 @@
@SmallTest
public void testSetAndGetRepeatMode() throws Exception {
final TestUtils.Monitor onRepeatModeChangedMonitor = new TestUtils.Monitor();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onRepeatModeChanged(SessionPlayer2 player, int repeatMode) {
mPlayerCbArg1 = player;
@@ -1287,6 +1287,20 @@
@SmallTest
public void testSetPlaylist() throws Exception {
List<MediaItem2> playlist = createPlaylist(10);
+ try {
+ mPlayer.setPlaylist(null, null);
+ fail();
+ } catch (Exception e) {
+ // pass-through
+ }
+ try {
+ List<MediaItem2> list = new ArrayList<>();
+ list.add(null);
+ mPlayer.setPlaylist(list, null);
+ fail();
+ } catch (Exception e) {
+ // pass-through
+ }
ListenableFuture<PlayerResult> future = mPlayer.setPlaylist(playlist, null);
PlayerResult result = future.get();
assertEquals(RESULT_CODE_SUCCESS, result.getResultCode());
@@ -1429,7 +1443,7 @@
MediaItem2 item = createMediaItem(100);
final TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onCurrentMediaItemChanged(SessionPlayer2 player, MediaItem2 item) {
onCurrentMediaItemChangedMonitor.signal();
@@ -1449,7 +1463,7 @@
List<MediaItem2> playlist = createPlaylist(listSize);
final TestUtils.Monitor onCurrentMediaItemChangedMonitor = new TestUtils.Monitor();
- XMediaPlayer.PlayerCallback callback = new XMediaPlayer.PlayerCallback() {
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onCurrentMediaItemChanged(SessionPlayer2 player, MediaItem2 item) {
onCurrentMediaItemChangedMonitor.signal();
@@ -1465,9 +1479,7 @@
private MediaItem2 createMediaItem(int key) throws Exception {
AssetFileDescriptor afd = mResources.openRawResourceFd(R.raw.testvideo);
return new FileMediaItem2.Builder(
- afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
- .setMediaId("TEST_MEDIA_" + key)
- .build();
+ afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()).build();
}
private List<MediaItem2> createPlaylist(int size) throws Exception {
diff --git a/media2/src/androidTest/java/androidx/media2/XMediaPlayerTestBase.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
similarity index 93%
rename from media2/src/androidTest/java/androidx/media2/XMediaPlayerTestBase.java
rename to media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
index a13b3a3..cef0dedf 100644
--- a/media2/src/androidTest/java/androidx/media2/XMediaPlayerTestBase.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
@@ -43,10 +43,10 @@
import java.util.concurrent.Executors;
/**
- * Base class for {@link XMediaPlayer} tests.
+ * Base class for {@link MediaPlayer} tests.
*/
-abstract class XMediaPlayerTestBase extends MediaTestBase {
- private static final String TAG = "XMediaPlayerTest";
+abstract class MediaPlayerTestBase extends MediaTestBase {
+ private static final String TAG = "MediaPlayerTest";
@Rule
public ActivityTestRule<MediaStubActivity> mActivityRule =
@@ -56,7 +56,7 @@
Resources mResources;
ExecutorService mExecutor;
- XMediaPlayer mPlayer;
+ MediaPlayer mPlayer;
MediaStubActivity mActivity;
Instrumentation mInstrumentation;
@@ -75,11 +75,11 @@
try {
mActivityRule.runOnUiThread(new Runnable() {
public void run() {
- mPlayer = new XMediaPlayer(mActivity);
+ mPlayer = new MediaPlayer(mActivity);
}
});
} catch (Throwable e) {
- Log.e(TAG, "Failed to instantiate XMediaPlayer", e);
+ Log.e(TAG, "Failed to instantiate MediaPlayer", e);
fail(e.getMessage());
}
mContext = mActivityRule.getActivity();
diff --git a/media2/src/androidTest/java/androidx/media2/XMediaPlayer_AudioFocusTest.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer_AudioFocusTest.java
similarity index 94%
rename from media2/src/androidTest/java/androidx/media2/XMediaPlayer_AudioFocusTest.java
rename to media2/src/androidTest/java/androidx/media2/MediaPlayer_AudioFocusTest.java
index 54bc1f0..5501575 100644
--- a/media2/src/androidTest/java/androidx/media2/XMediaPlayer_AudioFocusTest.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer_AudioFocusTest.java
@@ -53,7 +53,6 @@
import androidx.media.AudioAttributesCompat;
import androidx.media2.test.R;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
@@ -72,7 +71,7 @@
import java.util.concurrent.TimeUnit;
/**
- * Tests {@link XMediaPlayer} for audio focus and noisy intent handling.
+ * Tests {@link MediaPlayer} for audio focus and noisy intent handling.
* <p>
* This may be flaky test because another app including system component may take audio focus.
*/
@@ -80,7 +79,7 @@
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
@MediumTest
-public class XMediaPlayer_AudioFocusTest extends XMediaPlayerTestBase {
+public class MediaPlayer_AudioFocusTest extends MediaPlayerTestBase {
private static final int WAIT_TIME_MS = 2000;
static TestUtils.SyncHandler sHandler;
@@ -91,12 +90,12 @@
@BeforeClass
public static void setUpThread() {
- synchronized (XMediaPlayer_AudioFocusTest.class) {
+ synchronized (MediaPlayer_AudioFocusTest.class) {
if (sHandler != null) {
return;
}
prepareLooper();
- HandlerThread handlerThread = new HandlerThread("XMediaPlayer_AudioFocusTest");
+ HandlerThread handlerThread = new HandlerThread("MediaPlayer_AudioFocusTest");
handlerThread.start();
sHandler = new TestUtils.SyncHandler(handlerThread.getLooper());
sHandlerExecutor = new Executor() {
@@ -124,7 +123,7 @@
@AfterClass
public static void cleanUpThread() {
- synchronized (XMediaPlayer_AudioFocusTest.class) {
+ synchronized (MediaPlayer_AudioFocusTest.class) {
if (sHandler == null) {
return;
}
@@ -159,7 +158,7 @@
.setContentType(contentType).setUsage(usage).build();
}
- private void sendNoisyIntent(XMediaPlayer player) {
+ private void sendNoisyIntent(MediaPlayer player) {
// We cannot use Context.sendBroadcast() because it throws SecurityException for such
// framework related intent.
Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
@@ -216,7 +215,7 @@
try {
mInstrumentation.runOnMainSync(new Runnable() {
public void run() {
- mPlayer = new XMediaPlayer(mActivity) {
+ mPlayer = new MediaPlayer(mActivity) {
@Override
public ListenableFuture<PlayerResult> setPlayerVolume(float volume) {
if (volume < getMaxPlayerVolume()) {
@@ -254,14 +253,13 @@
}
@Test
- @FlakyTest(bugId = 117352673)
public void testNoisyIntent_pausePlaybackForMedia() throws Exception {
prepareLooper();
testPausedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_MEDIA),
new PlayerRunnable() {
@Override
- public void run(XMediaPlayer player) {
+ public void run(MediaPlayer player) {
// Noisy intent would pause for USAGE_MEDIA.
sendNoisyIntent(player);
}
@@ -269,14 +267,13 @@
}
@Test
- @FlakyTest(bugId = 117107087)
public void testNoisyIntent_lowerVolumeForGame() throws Exception {
prepareLooper();
testDuckedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_GAME),
new PlayerRunnable() {
@Override
- public void run(XMediaPlayer player) {
+ public void run(MediaPlayer player) {
// Noisy intent would duck for USAGE_GAME.
sendNoisyIntent(player);
}
@@ -400,7 +397,7 @@
testPausedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_MEDIA),
new PlayerRunnable() {
@Override
- public void run(XMediaPlayer player) throws InterruptedException {
+ public void run(MediaPlayer player) throws InterruptedException {
// Somebody else has request audio focus.
// Session should lose audio focus and pause playback.
requestAudioFocus(AUDIOFOCUS_GAIN_TRANSIENT);
@@ -415,7 +412,7 @@
testPausedAfterAction(createAudioAttributes(CONTENT_TYPE_SPEECH, USAGE_MEDIA),
new PlayerRunnable() {
@Override
- public void run(XMediaPlayer player) throws InterruptedException {
+ public void run(MediaPlayer player) throws InterruptedException {
// Although ducking is possible, CONTENT_TYPE_SPEECH should prefer pause.
requestAudioFocus(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
}
@@ -434,7 +431,7 @@
testDuckedAfterAction(createAudioAttributes(CONTENT_TYPE_MUSIC, USAGE_MEDIA),
new PlayerRunnable() {
@Override
- public void run(XMediaPlayer player) throws InterruptedException {
+ public void run(MediaPlayer player) throws InterruptedException {
// This will trigger duck (lower volume).
requestAudioFocus(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
}
@@ -443,7 +440,7 @@
@FunctionalInterface
private interface PlayerRunnable {
- void run(XMediaPlayer player) throws InterruptedException;
+ void run(MediaPlayer player) throws InterruptedException;
}
private class AudioFocusListener implements OnAudioFocusChangeListener {
diff --git a/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java b/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java
index 36b924f..e1350d2 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java
@@ -333,7 +333,7 @@
@Test
public void testSetPlaylist() {
prepareLooper();
- final List<MediaItem2> list = TestUtils.createPlaylist(2);
+ final List<MediaItem2> list = TestUtils.createMediaItems(2);
mSession.getPlayer().setPlaylist(list, null);
assertTrue(mPlayer.mSetPlaylistCalled);
assertSame(list, mPlayer.mPlaylist);
@@ -343,7 +343,7 @@
@Test
public void testGetPlaylist() {
prepareLooper();
- final List<MediaItem2> list = TestUtils.createPlaylist(2);
+ final List<MediaItem2> list = TestUtils.createMediaItems(2);
mPlayer.mPlaylist = list;
assertEquals(list, mSession.getPlayer().getPlaylist());
}
@@ -565,7 +565,7 @@
public void testSendCustomCommand() throws InterruptedException {
prepareLooper();
final SessionCommand2 testCommand = new SessionCommand2(
- SessionCommand2.COMMAND_CODE_PLAYER_PREFETCH);
+ SessionCommand2.COMMAND_CODE_PLAYER_PREPARE);
final Bundle testArgs = new Bundle();
testArgs.putString("args", "testSendCustomAction");
diff --git a/media2/src/androidTest/java/androidx/media2/MockMediaLibraryService2.java b/media2/src/androidTest/java/androidx/media2/MockMediaLibraryService2.java
index 3e7a19b..cc02c0e 100644
--- a/media2/src/androidTest/java/androidx/media2/MockMediaLibraryService2.java
+++ b/media2/src/androidTest/java/androidx/media2/MockMediaLibraryService2.java
@@ -19,8 +19,10 @@
import static androidx.media2.MediaLibraryService2.LibraryResult.RESULT_CODE_BAD_VALUE;
import static androidx.media2.MediaLibraryService2.LibraryResult.RESULT_CODE_INVALID_STATE;
import static androidx.media2.MediaLibraryService2.LibraryResult.RESULT_CODE_SUCCESS;
-import static androidx.media2.MediaMetadata2.FLAG_BROWSABLE;
+import static androidx.media2.MediaMetadata2.BROWSABLE_TYPE_MIXED;
+import static androidx.media2.MediaMetadata2.METADATA_KEY_BROWSABLE;
import static androidx.media2.MediaMetadata2.METADATA_KEY_MEDIA_ID;
+import static androidx.media2.MediaMetadata2.METADATA_KEY_PLAYABLE;
import static androidx.media2.TestUtils.assertLibraryParamsEquals;
import static org.junit.Assert.assertEquals;
@@ -75,9 +77,11 @@
public static final int SEARCH_RESULT_COUNT = 50;
static {
- ROOT_ITEM = new MediaItem2.Builder(FLAG_BROWSABLE)
+ ROOT_ITEM = new MediaItem2.Builder()
.setMetadata(new MediaMetadata2.Builder()
- .putString(METADATA_KEY_MEDIA_ID, "rootId").build()).build();
+ .putString(METADATA_KEY_MEDIA_ID, "rootId")
+ .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
+ .putLong(METADATA_KEY_PLAYABLE, 1).build()).build();
ROOT_PARAMS_EXTRA = new Bundle();
ROOT_PARAMS_EXTRA.putString(ID, ID);
ROOT_PARAMS = new LibraryParams.Builder().setExtras(ROOT_PARAMS_EXTRA).build();
@@ -274,10 +278,13 @@
}
private MediaItem2 createMediaItem(String mediaId) {
- return new MediaItem2.Builder(MediaItem2.FLAG_PLAYABLE)
- .setMediaId(mediaId)
- .setMetadata(new MediaMetadata2.Builder()
- .putString(METADATA_KEY_MEDIA_ID, mediaId)
+ return new MediaItem2.Builder()
+ .setMetadata(
+ new MediaMetadata2.Builder()
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE,
+ MediaMetadata2.BROWSABLE_TYPE_MIXED)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
.build())
.build();
}
diff --git a/media2/src/androidTest/java/androidx/media2/MockPlayer.java b/media2/src/androidTest/java/androidx/media2/MockPlayer.java
index 242188d..12649ff 100644
--- a/media2/src/androidTest/java/androidx/media2/MockPlayer.java
+++ b/media2/src/androidTest/java/androidx/media2/MockPlayer.java
@@ -268,6 +268,7 @@
@Override
public ListenableFuture<PlayerResult> setMediaItem(MediaItem2 item) {
mItem = item;
+ mCurrentMediaItem = item;
ArrayList list = new ArrayList<>();
list.add(item);
return setPlaylist(list, null);
diff --git a/media2/src/androidTest/java/androidx/media2/Rating2Test.java b/media2/src/androidTest/java/androidx/media2/Rating2Test.java
index cf50472..f92f8d4 100644
--- a/media2/src/androidTest/java/androidx/media2/Rating2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/Rating2Test.java
@@ -25,7 +25,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -108,12 +107,12 @@
}
private Rating2 writeToParcelAndCreateRating2(Rating2 rating2) {
- ParcelImpl parcelImpl = (ParcelImpl) ParcelUtils.toParcelable(rating2);
+ ParcelImpl parcelImpl = MediaUtils2.toParcelable(rating2);
Parcel parcel = Parcel.obtain();
parcelImpl.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
ParcelImpl newParcelImpl = ParcelImpl.CREATOR.createFromParcel(parcel);
parcel.recycle();
- return ParcelUtils.fromParcelable(newParcelImpl);
+ return MediaUtils2.fromParcelable(newParcelImpl);
}
}
diff --git a/media2/src/androidTest/java/androidx/media2/TestUtils.java b/media2/src/androidTest/java/androidx/media2/TestUtils.java
index 5427d1d..34b4ef2 100644
--- a/media2/src/androidTest/java/androidx/media2/TestUtils.java
+++ b/media2/src/androidTest/java/androidx/media2/TestUtils.java
@@ -101,19 +101,25 @@
}
/**
- * Create a playlist for testing purpose
+ * Create a list of media items for testing purpose
* <p>
* Caller's method name will be used for prefix of each media item's media id.
*
* @param size list size
- * @return the newly created playlist
+ * @return the newly created media item list
*/
- public static List<MediaItem2> createPlaylist(int size) {
+ public static List<MediaItem2> createMediaItems(int size) {
final List<MediaItem2> list = new ArrayList<>();
String caller = Thread.currentThread().getStackTrace()[3].getMethodName();
for (int i = 0; i < size; i++) {
MediaItem2 item = new FileMediaItem2.Builder(new FileDescriptor())
- .setMediaId(caller + "_item_" + (size + 1))
+ .setMetadata(new MediaMetadata2.Builder()
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID,
+ caller + "_item_" + (size + 1))
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE,
+ MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
+ .build())
.build();
list.add(item);
}
@@ -121,6 +127,23 @@
}
/**
+ * Create a list of media ids for testing purpose
+ * <p>
+ * Caller's method name will be used for prefix of media id.
+ *
+ * @param size list size
+ * @return the newly created ids
+ */
+ public static List<String> createMediaIds(int size) {
+ final List<String> list = new ArrayList<>();
+ String caller = Thread.currentThread().getStackTrace()[3].getMethodName();
+ for (int i = 0; i < size; i++) {
+ list.add(caller + "_item_" + (size + 1));
+ }
+ return list;
+ }
+
+ /**
* Create a media item with the metadata for testing purpose.
*
* @return the newly created media item
@@ -141,18 +164,33 @@
public static MediaMetadata2 createMetadata() {
String mediaId = Thread.currentThread().getStackTrace()[3].getMethodName();
return new MediaMetadata2.Builder()
- .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId).build();
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE, MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
+ .build();
}
/**
- * Create a bundle for testing purpose.
+ * Create a media metadata for testing purpose.
*
- * @return the newly created bundle.
+ * @return the newly created media item
*/
- public static Bundle createTestBundle() {
- Bundle bundle = new Bundle();
- bundle.putString("test_key", "test_value");
- return bundle;
+ public static MediaMetadata2 createMetadata(String mediaId, long duration) {
+ return new MediaMetadata2.Builder()
+ .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+ .putLong(MediaMetadata2.METADATA_KEY_DURATION, duration)
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE, MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
+ .build();
+ }
+
+ /**
+ * Create a {@link MediaItem2} with the id.
+ *
+ * @return the newly created media item.
+ */
+ public static MediaItem2 createMediaItem(String mediaId) {
+ return new MediaItem2.Builder().setMetadata(createMetadata(mediaId, 0)).build();
}
public static LibraryParams createLibraryParams() {
@@ -185,7 +223,6 @@
}
assertEquals(aItem.getMediaId(), bItem.getMediaId());
- assertEquals(aItem.getFlags(), bItem.getFlags());
TestUtils.assertMetadataEquals(aItem.getMetadata(), bItem.getMetadata());
// Note: Here it does not check whether MediaItem2 are equal,
diff --git a/media2/src/main/aidl/androidx/media2/IMediaController2.aidl b/media2/src/main/aidl/androidx/media2/IMediaController2.aidl
index 1a489a8..df889c8 100644
--- a/media2/src/main/aidl/androidx/media2/IMediaController2.aidl
+++ b/media2/src/main/aidl/androidx/media2/IMediaController2.aidl
@@ -35,8 +35,8 @@
void onPlayerStateChanged(long eventTimeMs, long positionMs, int state) = 1;
void onPlaybackSpeedChanged(long eventTimeMs, long positionMs, float speed) = 2;
void onBufferingStateChanged(in ParcelImpl item, int state, long bufferedPositionMs) = 3;
- void onPlaylistChanged(in ParcelImplListSlice listSlice, in Bundle metadata) = 4;
- void onPlaylistMetadataChanged(in Bundle metadata) = 5;
+ void onPlaylistChanged(in ParcelImplListSlice listSlice, in ParcelImpl metadata) = 4;
+ void onPlaylistMetadataChanged(in ParcelImpl metadata) = 5;
void onPlaybackInfoChanged(in ParcelImpl playbackInfo) = 6;
void onRepeatModeChanged(int repeatMode) = 7;
void onShuffleModeChanged(int shuffleMode) = 8;
diff --git a/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl b/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl
index 952aa8e..ca995f6 100644
--- a/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl
+++ b/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl
@@ -39,56 +39,56 @@
void play(IMediaController2 caller, int seq) = 4;
void pause(IMediaController2 caller, int seq) = 5;
- void prefetch(IMediaController2 caller, int seq) = 7;
+ void prepare(IMediaController2 caller, int seq) = 7;
void fastForward(IMediaController2 caller, int seq) = 8;
void rewind(IMediaController2 caller, int seq) = 9;
- void seekTo(IMediaController2 caller, int seq, long pos) = 10;
+ void skipForward(IMediaController2 caller, int seq) = 10;
+ void skipBackward(IMediaController2 caller, int seq) = 11;
+ void seekTo(IMediaController2 caller, int seq, long pos) = 12;
void onCustomCommand(IMediaController2 caller, int seq, in ParcelImpl sessionCommand2,
- in Bundle args) = 11;
- void prefetchFromUri(IMediaController2 caller, int seq, in Uri uri, in Bundle extras) = 12;
- void prefetchFromSearch(IMediaController2 caller, int seq, String query, in Bundle extras) = 13;
- void prefetchFromMediaId(IMediaController2 caller, int seq, String mediaId,
- in Bundle extras) = 14;
- void playFromUri(IMediaController2 caller, int seq, in Uri uri, in Bundle extras) = 15;
- void playFromSearch(IMediaController2 caller, int seq, String query, in Bundle extras) = 16;
- void playFromMediaId(IMediaController2 caller, int seq, String mediaId, in Bundle extras) = 17;
- void setRating(IMediaController2 caller, int seq, String mediaId, in ParcelImpl rating2) = 18;
- void setPlaybackSpeed(IMediaController2 caller, int seq, float speed) = 19;
+ in Bundle args) = 13;
+ void prepareFromUri(IMediaController2 caller, int seq, in Uri uri, in Bundle extras) = 14;
+ void prepareFromSearch(IMediaController2 caller, int seq, String query, in Bundle extras) = 15;
+ void prepareFromMediaId(IMediaController2 caller, int seq, String mediaId,
+ in Bundle extras) = 16;
+ void playFromUri(IMediaController2 caller, int seq, in Uri uri, in Bundle extras) = 17;
+ void playFromSearch(IMediaController2 caller, int seq, String query, in Bundle extras) = 18;
+ void playFromMediaId(IMediaController2 caller, int seq, String mediaId, in Bundle extras) = 19;
+ void setRating(IMediaController2 caller, int seq, String mediaId, in ParcelImpl rating2) = 20;
+ void setPlaybackSpeed(IMediaController2 caller, int seq, float speed) = 21;
- void setPlaylist(IMediaController2 caller, int seq, in ParcelImplListSlice listSlice,
- in Bundle metadata) = 20;
- void setMediaItem(IMediaController2 caller, int seq, in ParcelImpl mediaItem) = 40;
- void updatePlaylistMetadata(IMediaController2 caller, int seq, in Bundle metadata) = 21;
- void addPlaylistItem(IMediaController2 caller, int seq, int index,
- in ParcelImpl mediaItem) = 22;
- void removePlaylistItem(IMediaController2 caller, int seq, in ParcelImpl mediaItem) = 23;
- void replacePlaylistItem(IMediaController2 caller, int seq, int index,
- in ParcelImpl mediaItem) = 24;
- void skipToPlaylistItem(IMediaController2 caller, int seq, in ParcelImpl mediaItem) = 25;
- void skipToPreviousItem(IMediaController2 caller, int seq) = 26;
- void skipToNextItem(IMediaController2 caller, int seq) = 27;
- void setRepeatMode(IMediaController2 caller, int seq, int repeatMode) = 28;
- void setShuffleMode(IMediaController2 caller, int seq, int shuffleMode) = 29;
+ void setPlaylist(IMediaController2 caller, int seq, in List<String> list,
+ in ParcelImpl metadata) = 22;
+ void setMediaItem(IMediaController2 caller, int seq, String mediaId) = 23;
+ void updatePlaylistMetadata(IMediaController2 caller, int seq, in ParcelImpl metadata) = 24;
+ void addPlaylistItem(IMediaController2 caller, int seq, int index, String mediaId) = 25;
+ void removePlaylistItem(IMediaController2 caller, int seq, int index) = 26;
+ void replacePlaylistItem(IMediaController2 caller, int seq, int index, String mediaId) = 27;
+ void skipToPlaylistItem(IMediaController2 caller, int seq, int index) = 28;
+ void skipToPreviousItem(IMediaController2 caller, int seq) = 29;
+ void skipToNextItem(IMediaController2 caller, int seq) = 30;
+ void setRepeatMode(IMediaController2 caller, int seq, int repeatMode) = 31;
+ void setShuffleMode(IMediaController2 caller, int seq, int shuffleMode) = 32;
- void subscribeRoutesInfo(IMediaController2 caller, int seq) = 30;
- void unsubscribeRoutesInfo(IMediaController2 caller, int seq) = 31;
- void selectRoute(IMediaController2 caller, int seq, in Bundle route) = 32;
+ void subscribeRoutesInfo(IMediaController2 caller, int seq) = 33;
+ void unsubscribeRoutesInfo(IMediaController2 caller, int seq) = 34;
+ void selectRoute(IMediaController2 caller, int seq, in Bundle route) = 35;
void onControllerResult(IMediaController2 caller, int seq,
- in ParcelImpl controllerResult) = 41;
+ in ParcelImpl controllerResult) = 46;
//////////////////////////////////////////////////////////////////////////////////////////////
// library service specific
//////////////////////////////////////////////////////////////////////////////////////////////
- void getLibraryRoot(IMediaController2 caller, int seq, in ParcelImpl libraryParams) = 33;
- void getItem(IMediaController2 caller, int seq, String mediaId) = 34;
+ void getLibraryRoot(IMediaController2 caller, int seq, in ParcelImpl libraryParams) = 37;
+ void getItem(IMediaController2 caller, int seq, String mediaId) = 38;
void getChildren(IMediaController2 caller, int seq, String parentId, int page, int pageSize,
- in ParcelImpl libraryParams) = 35;
- void search(IMediaController2 caller, int seq, String query, in ParcelImpl libraryParams) = 36;
+ in ParcelImpl libraryParams) = 39;
+ void search(IMediaController2 caller, int seq, String query, in ParcelImpl libraryParams) = 40;
void getSearchResult(IMediaController2 caller, int seq, String query, int page, int pageSize,
- in ParcelImpl libraryParams) = 37;
+ in ParcelImpl libraryParams) = 41;
void subscribe(IMediaController2 caller, int seq, String parentId,
- in ParcelImpl libraryParams) = 38;
- void unsubscribe(IMediaController2 caller, int seq, String parentId) = 39;
- // Next Id : 42
+ in ParcelImpl libraryParams) = 42;
+ void unsubscribe(IMediaController2 caller, int seq, String parentId) = 43;
+ // Next Id : 44
}
diff --git a/media2/src/main/java/androidx/media2/AudioFocusHandler.java b/media2/src/main/java/androidx/media2/AudioFocusHandler.java
index 0410120..7d41b71 100644
--- a/media2/src/main/java/androidx/media2/AudioFocusHandler.java
+++ b/media2/src/main/java/androidx/media2/AudioFocusHandler.java
@@ -17,10 +17,7 @@
package androidx.media2;
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
-import static androidx.media2.SessionPlayer2.PLAYER_STATE_ERROR;
-import static androidx.media2.SessionPlayer2.PLAYER_STATE_IDLE;
import static androidx.media2.SessionPlayer2.PLAYER_STATE_PAUSED;
-import static androidx.media2.SessionPlayer2.PLAYER_STATE_PLAYING;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -34,8 +31,6 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.VisibleForTesting;
-import androidx.core.content.ContextCompat;
-import androidx.core.util.ObjectsCompat;
import androidx.media.AudioAttributesCompat;
/**
@@ -56,38 +51,43 @@
private static final boolean DEBUG = true;
interface AudioFocusHandlerImpl {
- boolean onPlayRequested();
- void onPauseRequested();
+ boolean onPlay();
+ void onPause();
+ void onReset();
void close();
void sendIntent(Intent intent);
}
private final AudioFocusHandlerImpl mImpl;
- AudioFocusHandler(Context context, XMediaPlayer player) {
+ AudioFocusHandler(Context context, MediaPlayer player) {
mImpl = new AudioFocusHandlerImplBase(context, player);
}
/**
- * Should be called when the {@link MediaSession2#play()} is called. Returns whether the play()
+ * Should be called when the {@link MediaPlayer#play()} is called. Returns whether the play()
* can be proceed.
- * <p>
- * This matches with the Session.Callback#onPlay() written in the guideline.
*
* @return {@code true} if it's OK to start playback because audio focus was successfully
- * granted or audio focus isn't needed for starting playback. {@code false} otherwise.
- * (i.e. Audio focus is needed for starting playback but failed)
+ * granted or audio focus isn't needed for starting playback. {@code false} otherwise.
+ * (i.e. Audio focus is needed for starting playback but failed)
*/
- public boolean onPlayRequested() {
- return mImpl.onPlayRequested();
+ public boolean onPlay() {
+ return mImpl.onPlay();
}
/**
- * Should be called when the {@link MediaSession2#pause()} is called. Returns whether the
- * pause() can be proceed.
+ * Called when the {@link MediaPlayer#pause()} is called.
*/
- public void onPauseRequested() {
- mImpl.onPauseRequested();
+ public void onPause() {
+ mImpl.onPause();
+ }
+
+ /**
+ * Called when the {@link MediaPlayer#reset()} is called.
+ */
+ public void onReset() {
+ mImpl.onReset();
}
/**
@@ -106,20 +106,19 @@
mImpl.sendIntent(intent);
}
- private static class AudioFocusHandlerImplBase extends SessionPlayer2.PlayerCallback
- implements AudioFocusHandlerImpl {
+ private static class AudioFocusHandlerImplBase implements AudioFocusHandlerImpl {
// Value is from the {@link AudioFocusRequest} as follows
// 'A typical attenuation by the “ducked” application is a factor of 0.2f (or -14dB), that
// can for instance be applied with MediaPlayer.setVolume(0.2f) when using this class for
// playback.'
private static final float VOLUME_DUCK_FACTOR = 0.2f;
- private final BroadcastReceiver mBecomingNoisyIntentReceiver = new NoisyIntentReceiver();
+ private final BroadcastReceiver mBecomingNoisyReceiver = new BecomingNoisyReceiver();
private final IntentFilter mIntentFilter =
new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private final OnAudioFocusChangeListener mAudioFocusListener = new AudioFocusListener();
final Object mLock = new Object();
private final Context mContext;
- final XMediaPlayer mPlayer;
+ final MediaPlayer mPlayer;
private final AudioManager mAudioManager;
@GuardedBy("mLock")
@@ -129,12 +128,11 @@
@GuardedBy("mLock")
boolean mResumeWhenAudioFocusGain;
@GuardedBy("mLock")
- boolean mHasRegisteredReceiver;
+ boolean mBecomingNoisyReceiverRegistered;
- AudioFocusHandlerImplBase(Context context, XMediaPlayer player) {
+ AudioFocusHandlerImplBase(Context context, MediaPlayer player) {
mContext = context;
mPlayer = player;
- mPlayer.registerPlayerCallback(ContextCompat.getMainExecutor(context), this);
// Cannot use session.getContext() because session's impl isn't initialized at this
// moment.
@@ -142,129 +140,54 @@
}
@Override
- public boolean onPlayRequested() {
+ public boolean onPlay() {
final AudioAttributesCompat attrs = mPlayer.getAudioAttributes();
boolean result = true;
synchronized (mLock) {
+ mAudioAttributes = attrs;
+ // Checks whether the audio attributes is {@code null}, to check indirectly whether
+ // the media item has audio track.
if (attrs == null) {
- mAudioAttributes = null;
+ // No sound.
abandonAudioFocusLocked();
+ unregisterBecomingNoisyReceiverLocked();
} else {
- // Keep cache of the audio attributes. Otherwise audio attributes may be changed
- // between the audio focus request and audio focus change, resulting the
- // unexpected situation.
- mAudioAttributes = attrs;
result = requestAudioFocusLocked();
+ if (result) {
+ registerBecomingNoisyReceiverLocked();
+ }
}
}
- if (attrs == null) {
- mPlayer.setPlayerVolume(0);
- }
return result;
}
@Override
- public void onPauseRequested() {
+ public void onPause() {
synchronized (mLock) {
mResumeWhenAudioFocusGain = false;
- }
- }
-
- /**
- * Check we need to abandon/request audio focus when playback state becomes playing. It's
- * needed to handle following cases.
- * 1. Audio attribute has changed between {@link SessionPlayer2#play} and actual
- * start of playback. Note that {@link MediaPlayer2} only allows changing audio
- * attributes in IDLE state, so such issue wouldn't happen.
- * 2. Or, playback is started without MediaSession2#play().
- * <p>
- * If there's no huge issue, then register noisy intent receiver here.
- */
- private void onPlayingNotLocked() {
- final AudioAttributesCompat attrs = mPlayer.getAudioAttributes();
- final int expectedFocusGain;
- boolean pauseNeeded = false;
- synchronized (mLock) {
- expectedFocusGain = convertAudioAttributesToFocusGain(attrs);
- if (ObjectsCompat.equals(mAudioAttributes, attrs)
- && expectedFocusGain == mCurrentFocusGainType) {
- // No change in audio attributes, and audio focus is granted as expected.
- // Register noisy intent if it has an audio attribute (i.e. has sound).
- if (attrs != null) {
- registerReceiverLocked();
- }
- return;
- }
- Log.w(TAG, "Expected " + mAudioAttributes + " and audioFocusGainType="
- + mCurrentFocusGainType + " when playback is started, but actually "
- + attrs
- + " and audioFocusGainType=" + mCurrentFocusGainType + ". Use"
- + " MediaSession2#play() for starting playback.");
- mAudioAttributes = attrs;
- if (mCurrentFocusGainType != expectedFocusGain) {
- // Note: Calling AudioManager#requestAudioFocus() again with the same
- // listener but different focus gain type only updates the focus gain
- // type.
- if (expectedFocusGain == AudioManager.AUDIOFOCUS_NONE) {
- abandonAudioFocusLocked();
- } else {
- if (requestAudioFocusLocked()) {
- registerReceiverLocked();
- } else {
- Log.e(TAG, "Playback is started without audio focus, and requesting"
- + " audio focus is failed. Forcefully pausing playback");
- pauseNeeded = true;
- }
- }
- }
- }
- if (attrs == null) {
- // If attributes becomes null (i.e. no sound)
- mPlayer.setPlayerVolume(0);
- }
- if (pauseNeeded) {
- mPlayer.pause();
+ unregisterBecomingNoisyReceiverLocked();
}
}
@Override
- public void onPlayerStateChanged(SessionPlayer2 player, int playerState) {
- switch (playerState) {
- case PLAYER_STATE_IDLE: {
- synchronized (mLock) {
- abandonAudioFocusLocked();
- }
- break;
- }
- case PLAYER_STATE_PAUSED: {
- synchronized (mLock) {
- unregisterReceiverLocked();
- }
- break;
- }
- case PLAYER_STATE_PLAYING: {
- onPlayingNotLocked();
- break;
- }
- case PLAYER_STATE_ERROR: {
- close();
- break;
- }
+ public void onReset() {
+ synchronized (mLock) {
+ abandonAudioFocusLocked();
+ unregisterBecomingNoisyReceiverLocked();
}
}
@Override
public void close() {
- mPlayer.unregisterPlayerCallback(this);
synchronized (mLock) {
- unregisterReceiverLocked();
+ unregisterBecomingNoisyReceiverLocked();
abandonAudioFocusLocked();
}
}
@Override
public void sendIntent(Intent intent) {
- mBecomingNoisyIntentReceiver.onReceive(mContext, intent);
+ mBecomingNoisyReceiver.onReceive(mContext, intent);
}
/**
@@ -321,29 +244,29 @@
}
@GuardedBy("mLock")
- private void registerReceiverLocked() {
- if (mHasRegisteredReceiver) {
+ private void registerBecomingNoisyReceiverLocked() {
+ if (mBecomingNoisyReceiverRegistered) {
return;
}
if (DEBUG) {
- Log.d(TAG, "registering noisy intent");
+ Log.d(TAG, "registering becoming noisy receiver");
}
// Registering the receiver multiple-times may not be allowed for newer platform.
// Register only when it's not registered.
- mContext.registerReceiver(mBecomingNoisyIntentReceiver, mIntentFilter);
- mHasRegisteredReceiver = true;
+ mContext.registerReceiver(mBecomingNoisyReceiver, mIntentFilter);
+ mBecomingNoisyReceiverRegistered = true;
}
@GuardedBy("mLock")
- private void unregisterReceiverLocked() {
- if (!mHasRegisteredReceiver) {
+ private void unregisterBecomingNoisyReceiverLocked() {
+ if (!mBecomingNoisyReceiverRegistered) {
return;
}
if (DEBUG) {
- Log.d(TAG, "unregistering noisy intent");
+ Log.d(TAG, "unregistering becoming noisy receiver");
}
- mContext.unregisterReceiver(mBecomingNoisyIntentReceiver);
- mHasRegisteredReceiver = false;
+ mContext.unregisterReceiver(mBecomingNoisyReceiver);
+ mBecomingNoisyReceiverRegistered = false;
}
// Converts {@link AudioAttributesCompat} to one of the audio focus request. This follows
@@ -416,44 +339,41 @@
return AudioManager.AUDIOFOCUS_NONE;
}
- private class NoisyIntentReceiver extends BroadcastReceiver {
- NoisyIntentReceiver() {
+ private class BecomingNoisyReceiver extends BroadcastReceiver {
+ BecomingNoisyReceiver() {
}
+ // Note: This is always the main thread, except for the test.
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.d(TAG, "Received noisy intent " + intent);
+ if (!AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
+ return;
}
- // This is always the main thread, except for the test.
+ final int usage;
synchronized (mLock) {
- if (!mHasRegisteredReceiver) {
+ if (DEBUG) {
+ Log.d(TAG, "Received noisy intent, intent=" + intent + ", registered="
+ + mBecomingNoisyReceiverRegistered + ", attr=" + mAudioAttributes);
+ }
+ if (!mBecomingNoisyReceiverRegistered || mAudioAttributes == null) {
return;
}
+ usage = mAudioAttributes.getUsage();
}
- if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
- final int usage;
- synchronized (mLock) {
- if (mAudioAttributes == null) {
- return;
- }
- usage = mAudioAttributes.getUsage();
- }
- switch (usage) {
- case AudioAttributesCompat.USAGE_MEDIA:
- // Noisy intent guide says 'In the case of music players, users
- // typically expect the playback to be paused.'
- mPlayer.pause();
- break;
- case AudioAttributesCompat.USAGE_GAME:
- // Noisy intent guide says 'For gaming apps, you may choose to
- // significantly lower the volume instead'.
- mPlayer.setPlayerVolume(mPlayer.getPlayerVolume() * VOLUME_DUCK_FACTOR);
- break;
- default:
- // Noisy intent guide didn't say anything more for this. No-op for now.
- break;
- }
+ switch (usage) {
+ case AudioAttributesCompat.USAGE_MEDIA:
+ // Noisy intent guide says 'In the case of music players, users
+ // typically expect the playback to be paused.'
+ mPlayer.pause();
+ break;
+ case AudioAttributesCompat.USAGE_GAME:
+ // Noisy intent guide says 'For gaming apps, you may choose to
+ // significantly lower the volume instead'.
+ mPlayer.setPlayerVolume(mPlayer.getPlayerVolume() * VOLUME_DUCK_FACTOR);
+ break;
+ default:
+ // Noisy intent guide didn't say anything more for this. No-op for now.
+ break;
}
}
}
@@ -473,7 +393,7 @@
case AudioManager.AUDIOFOCUS_GAIN:
// Regains focus after the LOSS_TRANSIENT or LOSS_TRANSIENT_CAN_DUCK.
if (mPlayer.getPlayerState() == PLAYER_STATE_PAUSED) {
- // Note: onPlayRequested() will be called again with this.
+ // Note: onPlay() will be called again with this.
synchronized (mLock) {
if (!mResumeWhenAudioFocusGain) {
break;
diff --git a/media2/src/main/java/androidx/media2/BaseResult2.java b/media2/src/main/java/androidx/media2/BaseResult2.java
index 41fc4df..c316ef3 100644
--- a/media2/src/main/java/androidx/media2/BaseResult2.java
+++ b/media2/src/main/java/androidx/media2/BaseResult2.java
@@ -78,6 +78,11 @@
int getResultCode();
// Subclasses should write its own documentation.
+ // Should use SystemClock#elapsedRealtime() instead of System#currentTimeMillis() because
+ // 1. System#currentTimeMillis() can be unexpectedly set by System#setCurrentTimeMillis() or
+ // changes in the timezone. So receiver cannot know when the command is finished.
+ // 2. For matching the timestamp with the PlaybackState(Compat) which uses
+ // SystemClock#elapsedRealtime().
long getCompletionTime();
// Subclasses should write its own documentation.
diff --git a/media2/src/main/java/androidx/media2/CallbackMediaItem2.java b/media2/src/main/java/androidx/media2/CallbackMediaItem2.java
index 7f10efa..db2aba2 100644
--- a/media2/src/main/java/androidx/media2/CallbackMediaItem2.java
+++ b/media2/src/main/java/androidx/media2/CallbackMediaItem2.java
@@ -19,16 +19,20 @@
import androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import androidx.versionedparcelable.NonParcelField;
+import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelize;
/**
* Structure for media item descriptor for {@link DataSourceCallback2}.
* <p>
* Users should use {@link Builder} to create {@link CallbackMediaItem2}.
+ * <p>
+ * You cannot directly send this object across the process through {@link ParcelUtils}. See
+ * {@link MediaItem2} for detail.
*
* @see MediaItem2
*/
-@VersionedParcelize
+@VersionedParcelize(isCustom = true)
public class CallbackMediaItem2 extends MediaItem2 {
@NonParcelField
@SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -70,7 +74,6 @@
* @param dsc2 the DataSourceCallback2 for the media you want to play
*/
public Builder(@NonNull DataSourceCallback2 dsc2) {
- super(FLAG_PLAYABLE);
Preconditions.checkNotNull(dsc2);
mDataSourceCallback2 = dsc2;
}
@@ -79,7 +82,7 @@
* @return A new CallbackMediaItem2 with values supplied by the Builder.
*/
@Override
- public CallbackMediaItem2 build() {
+ public @NonNull CallbackMediaItem2 build() {
return new CallbackMediaItem2(this);
}
}
diff --git a/media2/src/main/java/androidx/media2/DataSourceCallback2.java b/media2/src/main/java/androidx/media2/DataSourceCallback2.java
index d057659..fe53777 100644
--- a/media2/src/main/java/androidx/media2/DataSourceCallback2.java
+++ b/media2/src/main/java/androidx/media2/DataSourceCallback2.java
@@ -17,6 +17,8 @@
package androidx.media2;
+import androidx.annotation.NonNull;
+
import java.io.Closeable;
import java.io.IOException;
@@ -49,7 +51,7 @@
* @throws IOException on fatal errors.
* @return the number of bytes read, or -1 if there was an error.
*/
- public abstract int readAt(long position, byte[] buffer, int offset, int size)
+ public abstract int readAt(long position, @NonNull byte[] buffer, int offset, int size)
throws IOException;
/**
diff --git a/media2/src/main/java/androidx/media2/FileMediaItem2.java b/media2/src/main/java/androidx/media2/FileMediaItem2.java
index 5a32bbc..a2f941f 100644
--- a/media2/src/main/java/androidx/media2/FileMediaItem2.java
+++ b/media2/src/main/java/androidx/media2/FileMediaItem2.java
@@ -19,6 +19,7 @@
import androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import androidx.versionedparcelable.NonParcelField;
+import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelize;
import java.io.FileDescriptor;
@@ -27,10 +28,13 @@
* Structure for media item for a file.
* <p>
* Users should use {@link Builder} to create {@link FileMediaItem2}.
+ * <p>
+ * You cannot directly send this object across the process through {@link ParcelUtils}. See
+ * {@link MediaItem2} for detail.
*
* @see MediaItem2
*/
-@VersionedParcelize
+@VersionedParcelize(isCustom = true)
public class FileMediaItem2 extends MediaItem2 {
/**
* Used when the length of file descriptor is unknown.
@@ -109,7 +113,6 @@
* @param fd the FileDescriptor for the file you want to play
*/
public Builder(@NonNull FileDescriptor fd) {
- super(FLAG_PLAYABLE);
Preconditions.checkNotNull(fd);
mFD = fd;
mFDOffset = 0;
@@ -129,7 +132,6 @@
* @param length the length in bytes of the data to be played
*/
public Builder(@NonNull FileDescriptor fd, long offset, long length) {
- super(FLAG_PLAYABLE);
Preconditions.checkNotNull(fd);
if (offset < 0) {
offset = 0;
@@ -146,7 +148,7 @@
* @return A new FileMediaItem2 with values supplied by the Builder.
*/
@Override
- public FileMediaItem2 build() {
+ public @NonNull FileMediaItem2 build() {
return new FileMediaItem2(this);
}
}
diff --git a/media2/src/main/java/androidx/media2/MediaBrowser2.java b/media2/src/main/java/androidx/media2/MediaBrowser2.java
index e87591d..cfd9305 100644
--- a/media2/src/main/java/androidx/media2/MediaBrowser2.java
+++ b/media2/src/main/java/androidx/media2/MediaBrowser2.java
@@ -62,28 +62,28 @@
* {@link MediaLibrarySession#notifyChildrenChanged} for the parent.
*
* @param browser the browser for this event
- * @param parentId parent id that you've specified with
+ * @param parentId non-empty parent id that you've specified with
* {@link #subscribe(String, LibraryParams)}
* @param itemCount number of children
* @param params library params from the library service. Can be differ from params
* that you've specified with {@link #subscribe(String, LibraryParams)}.
*/
public void onChildrenChanged(@NonNull MediaBrowser2 browser, @NonNull String parentId,
- int itemCount, @Nullable LibraryParams params) { }
+ @IntRange(from = 0) int itemCount, @Nullable LibraryParams params) { }
/**
* Called when there's change in the search result requested by the previous
* {@link MediaBrowser2#search(String, LibraryParams)}.
*
* @param browser the browser for this event
- * @param query search query that you've specified with
+ * @param query non-empty search query that you've specified with
* {@link #search(String, LibraryParams)}
* @param itemCount The item count for the search result
* @param params library params from the library service. Can be differ from params
* that you've specified with {@link #search(String, LibraryParams)}.
*/
public void onSearchResultChanged(@NonNull MediaBrowser2 browser, @NonNull String query,
- int itemCount, @Nullable LibraryParams params) { }
+ @IntRange(from = 0) int itemCount, @Nullable LibraryParams params) { }
}
public MediaBrowser2(@NonNull Context context, @NonNull SessionToken2 token,
@@ -122,6 +122,7 @@
* @param params library params getting root
* @see BrowserResult#getMediaItem()
*/
+ @NonNull
public ListenableFuture<BrowserResult> getLibraryRoot(@Nullable final LibraryParams params) {
if (isConnected()) {
return getImpl().getLibraryRoot(params);
@@ -135,10 +136,10 @@
* called with the library params. You should call
* {@link #getChildren(String, int, int, LibraryParams)} to get the items under the parent.
*
- * @param parentId parent id
+ * @param parentId non-empty parent id
* @param params library params
*/
- public ListenableFuture<BrowserResult> subscribe(@NonNull String parentId,
+ public @NonNull ListenableFuture<BrowserResult> subscribe(@NonNull String parentId,
@Nullable LibraryParams params) {
if (TextUtils.isEmpty(parentId)) {
throw new IllegalArgumentException("parentId shouldn't be empty");
@@ -156,9 +157,9 @@
* This unsubscribes all previous subscription with the parent id, regardless of the library
* param that was previously sent to the library service.
*
- * @param parentId parent id
+ * @param parentId non-empty parent id
*/
- public ListenableFuture<BrowserResult> unsubscribe(@NonNull String parentId) {
+ public @NonNull ListenableFuture<BrowserResult> unsubscribe(@NonNull String parentId) {
if (TextUtils.isEmpty(parentId)) {
throw new IllegalArgumentException("parentId shouldn't be empty");
}
@@ -174,13 +175,13 @@
* If it's successfully completed, {@link BrowserResult#getMediaItems()} will return the list
* of children.
*
- * @param parentId parent id for getting the children.
+ * @param parentId non-empty parent id for getting the children
* @param page page number to get the result. Starts from {@code 0}
* @param pageSize page size. Should be greater or equal to {@code 1}
* @param params library params
* @see BrowserResult#getMediaItems()
*/
- public ListenableFuture<BrowserResult> getChildren(@NonNull String parentId,
+ public @NonNull ListenableFuture<BrowserResult> getChildren(@NonNull String parentId,
@IntRange(from = 0) int page, @IntRange(from = 1) int pageSize,
@Nullable LibraryParams params) {
if (TextUtils.isEmpty(parentId)) {
@@ -204,10 +205,10 @@
* If it's successfully completed, {@link BrowserResult#getMediaItem()} will return the media
* item.
*
- * @param mediaId media id for specifying the item
+ * @param mediaId non-empty media id for specifying the item
* @see BrowserResult#getMediaItems()
*/
- public ListenableFuture<BrowserResult> getItem(@NonNull final String mediaId) {
+ public @NonNull ListenableFuture<BrowserResult> getItem(@NonNull final String mediaId) {
if (TextUtils.isEmpty(mediaId)) {
throw new IllegalArgumentException("mediaId shouldn't be empty");
}
@@ -225,12 +226,12 @@
* {@link BrowserCallback#getSearchResult(String, int, int, LibraryParams)} the search result
* and calls {@link #getSearchResult(String, int, int, LibraryParams)}} for getting the result.
*
- * @param query search query. Should not be an empty string.
+ * @param query non-empty search query
* @param params library params
* @see BrowserCallback#getSearchResult(String, int, int, LibraryParams)
* @see #getSearchResult(String, int, int, LibraryParams)
*/
- public ListenableFuture<BrowserResult> search(@NonNull String query,
+ public @NonNull ListenableFuture<BrowserResult> search(@NonNull String query,
@Nullable LibraryParams params) {
if (TextUtils.isEmpty(query)) {
throw new IllegalArgumentException("query shouldn't be empty");
@@ -247,13 +248,14 @@
* If it's successfully completed, {@link BrowserResult#getMediaItems()} will return the search
* result.
*
- * @param query search query that you've specified with {@link #search(String, LibraryParams)}
+ * @param query non-empty search query that you've specified with
+ * {@link #search(String, LibraryParams)}.
* @param page page number to get search result. Starts from {@code 0}
* @param pageSize page size. Should be greater or equal to {@code 1}
* @param params library params
* @see BrowserResult#getMediaItems()
*/
- public ListenableFuture<BrowserResult> getSearchResult(final @NonNull String query,
+ public @NonNull ListenableFuture<BrowserResult> getSearchResult(final @NonNull String query,
@IntRange(from = 0) int page, @IntRange(from = 1) int pageSize,
final @Nullable LibraryParams params) {
if (TextUtils.isEmpty(query)) {
@@ -438,7 +440,7 @@
*
* @return library params.
*/
- public LibraryParams getLibraryParams() {
+ public @Nullable LibraryParams getLibraryParams() {
return mParams;
}
diff --git a/media2/src/main/java/androidx/media2/MediaBrowser2ImplBase.java b/media2/src/main/java/androidx/media2/MediaBrowser2ImplBase.java
index e9c13ea..9e7482e 100644
--- a/media2/src/main/java/androidx/media2/MediaBrowser2ImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaBrowser2ImplBase.java
@@ -35,8 +35,6 @@
import androidx.media2.MediaBrowser2.BrowserResult;
import androidx.media2.MediaLibraryService2.LibraryParams;
import androidx.media2.SequencedFutureManager.SequencedFuture;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
import com.google.common.util.concurrent.ListenableFuture;
@@ -67,7 +65,7 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.getLibraryRoot(mControllerStub, seq,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
}
});
}
@@ -80,7 +78,7 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.subscribe(mControllerStub, seq, parentId,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
}
});
}
@@ -104,7 +102,7 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.getChildren(mControllerStub, seq, parentId, page, pageSize,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
}
});
}
@@ -127,7 +125,7 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.search(mControllerStub, seq, query,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
}
});
}
@@ -140,7 +138,7 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.getSearchResult(mControllerStub, seq, query, page, pageSize,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
}
});
}
diff --git a/media2/src/main/java/androidx/media2/MediaBrowser2ImplLegacy.java b/media2/src/main/java/androidx/media2/MediaBrowser2ImplLegacy.java
index 88e84d4..bb07a19 100644
--- a/media2/src/main/java/androidx/media2/MediaBrowser2ImplLegacy.java
+++ b/media2/src/main/java/androidx/media2/MediaBrowser2ImplLegacy.java
@@ -20,7 +20,6 @@
import static androidx.media2.MediaBrowser2.BrowserResult.RESULT_CODE_DISCONNECTED;
import static androidx.media2.MediaBrowser2.BrowserResult.RESULT_CODE_SUCCESS;
import static androidx.media2.MediaBrowser2.BrowserResult.RESULT_CODE_UNKNOWN_ERROR;
-import static androidx.media2.MediaItem2.FLAG_BROWSABLE;
import android.content.Context;
import android.os.Bundle;
@@ -313,9 +312,11 @@
// TODO: Query again with getMediaItem() to get real media item.
MediaMetadata2 metadata = new MediaMetadata2.Builder()
.putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, browser.getRoot())
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE, MediaMetadata2.BROWSABLE_TYPE_MIXED)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 0)
.setExtras(browser.getExtras())
.build();
- return new MediaItem2.Builder(FLAG_BROWSABLE).setMetadata(metadata).build();
+ return new MediaItem2.Builder().setMetadata(metadata).build();
}
private class GetLibraryRootCallback extends MediaBrowserCompat.ConnectionCallback {
diff --git a/media2/src/main/java/androidx/media2/MediaController2.java b/media2/src/main/java/androidx/media2/MediaController2.java
index e3f1d69..bbd2b84 100644
--- a/media2/src/main/java/androidx/media2/MediaController2.java
+++ b/media2/src/main/java/androidx/media2/MediaController2.java
@@ -38,6 +38,7 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
@@ -250,7 +251,8 @@
*
* @return SessionToken2 of the connected session, or {@code null} if not connected
*/
- public @Nullable SessionToken2 getConnectedSessionToken() {
+ @Nullable
+ public SessionToken2 getConnectedSessionToken() {
return isConnected() ? getImpl().getConnectedSessionToken() : null;
}
@@ -265,6 +267,7 @@
/**
* Requests that the player start or resume playback.
*/
+ @NonNull
public ListenableFuture<ControllerResult> play() {
if (isConnected()) {
return getImpl().play();
@@ -275,6 +278,7 @@
/**
* Requests that the player pause playback.
*/
+ @NonNull
public ListenableFuture<ControllerResult> pause() {
if (isConnected()) {
return getImpl().pause();
@@ -283,23 +287,26 @@
}
/**
- * Requests that the player prefetch the media items for playback. In other words, other
- * sessions can continue to play during the prefetch of this session. This method can be used
- * to speed up the start of the playback. Once the prefetch is done, the player will change
+ * Requests that the player prepare the media items for playback. In other words, other
+ * sessions can continue to play during the prepare of this session. This method can be used
+ * to speed up the start of the playback. Once the prepare is done, the player will change
* its playback state to {@link SessionPlayer2#PLAYER_STATE_PAUSED}. Afterwards, {@link #play}
* can be called to start playback.
*/
- public ListenableFuture<ControllerResult> prefetch() {
+ @NonNull
+ public ListenableFuture<ControllerResult> prepare() {
if (isConnected()) {
- return getImpl().prefetch();
+ return getImpl().prepare();
}
return createDisconnectedFuture();
}
/**
- * Start fast forwarding. If playback is already fast forwarding this
- * may increase the rate.
+ * Requests session to increase the playback speed.
+ *
+ * @see MediaSession2.SessionCallback#onFastForward(MediaSession2, ControllerInfo)
*/
+ @NonNull
public ListenableFuture<ControllerResult> fastForward() {
if (isConnected()) {
return getImpl().fastForward();
@@ -308,9 +315,11 @@
}
/**
- * Start rewinding. If playback is already rewinding this may increase
- * the rate.
+ * Requests session to decrease the playback speed.
+ *
+ * @see MediaSession2.SessionCallback#onRewind(MediaSession2, ControllerInfo)
*/
+ @NonNull
public ListenableFuture<ControllerResult> rewind() {
if (isConnected()) {
return getImpl().rewind();
@@ -319,21 +328,11 @@
}
/**
- * Move to a new location in the media stream.
+ * Requests session to skip backward within the current media item.
*
- * @param pos Position to move to, in milliseconds.
+ * @see MediaSession2.SessionCallback#onSkipForward(MediaSession2, ControllerInfo)
*/
- public ListenableFuture<ControllerResult> seekTo(long pos) {
- if (isConnected()) {
- return getImpl().seekTo(pos);
- }
- return createDisconnectedFuture();
- }
-
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
+ @NonNull
public ListenableFuture<ControllerResult> skipForward() {
// To match with KEYCODE_MEDIA_SKIP_FORWARD
if (isConnected()) {
@@ -343,9 +342,11 @@
}
/**
- * @hide
+ * Requests session to skip forward within the current media item.
+ *
+ * @see MediaSession2.SessionCallback#onSkipBackward(MediaSession2, ControllerInfo)
*/
- @RestrictTo(LIBRARY_GROUP)
+ @NonNull
public ListenableFuture<ControllerResult> skipBackward() {
// To match with KEYCODE_MEDIA_SKIP_BACKWARD
if (isConnected()) {
@@ -355,9 +356,22 @@
}
/**
+ * Move to a new location in the media stream.
+ *
+ * @param pos Position to move to, in milliseconds.
+ */
+ @NonNull
+ public ListenableFuture<ControllerResult> seekTo(long pos) {
+ if (isConnected()) {
+ return getImpl().seekTo(pos);
+ }
+ return createDisconnectedFuture();
+ }
+
+ /**
* Requests that the player start playback for a specific media id.
*
- * @param mediaId The id of the requested media.
+ * @param mediaId The non-empty media id
* @param extras Optional extras that can include extra information about the media item
* to be played.
* @hide
@@ -365,8 +379,8 @@
@RestrictTo(LIBRARY_GROUP)
public ListenableFuture<ControllerResult> playFromMediaId(@NonNull String mediaId,
@Nullable Bundle extras) {
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
+ if (TextUtils.isEmpty(mediaId)) {
+ throw new IllegalArgumentException("mediaId shouldn't be empty");
}
if (isConnected()) {
return getImpl().playFromMediaId(mediaId, extras);
@@ -377,7 +391,7 @@
/**
* Requests that the player start playback for a specific search query.
*
- * @param query The search query. Should not be an empty string.
+ * @param query The non-empty search query
* @param extras Optional extras that can include extra information about the query.
* @hide
*/
@@ -414,63 +428,63 @@
}
/**
- * Requests that the player prefetch a media item with the media id for playback.
+ * Requests that the player prepare a media item with the media id for playback.
* In other words, other sessions can continue to play during the preparation of this session.
* This method can be used to speed up the start of the playback.
- * Once the prefetch is done, the session will change its playback state to
+ * Once the prepare is done, the session will change its playback state to
* {@link SessionPlayer2#PLAYER_STATE_PAUSED}. Afterwards, {@link #play} can be called to start
- * playback. If the prefetch is not needed, {@link #playFromMediaId} can be directly called
+ * playback. If the prepare is not needed, {@link #playFromMediaId} can be directly called
* without this method.
*
- * @param mediaId The id of the requested media.
+ * @param mediaId The non-empty media id
* @param extras Optional extras that can include extra information about the media item
* to be prepared.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public ListenableFuture<ControllerResult> prefetchFromMediaId(@NonNull String mediaId,
+ public ListenableFuture<ControllerResult> prepareFromMediaId(@NonNull String mediaId,
@Nullable Bundle extras) {
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
+ if (TextUtils.isEmpty(mediaId)) {
+ throw new IllegalArgumentException("mediaId shouldn't be empty");
}
if (isConnected()) {
- return getImpl().prefetchFromMediaId(mediaId, extras);
+ return getImpl().prepareFromMediaId(mediaId, extras);
}
return createDisconnectedFuture();
}
/**
- * Requests that the player prefetch a media item with the specific search query for playback.
+ * Requests that the player prepare a media item with the specific search query for playback.
* In other words, other sessions can continue to play during the preparation of this session.
* This method can be used to speed up the start of the playback.
- * Once the prefetch is done, the session will change its playback state to
+ * Once the prepare is done, the session will change its playback state to
* {@link SessionPlayer2#PLAYER_STATE_PAUSED}. Afterwards, {@link #play} can be called to start
- * playback. If the prefetch is not needed, {@link #playFromSearch} can be directly called
+ * playback. If the prepare is not needed, {@link #playFromSearch} can be directly called
* without this method.
*
- * @param query The search query. Should not be an empty string.
+ * @param query The non-empty search query
* @param extras Optional extras that can include extra information about the query.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public ListenableFuture<ControllerResult> prefetchFromSearch(@NonNull String query,
+ public ListenableFuture<ControllerResult> prepareFromSearch(@NonNull String query,
@Nullable Bundle extras) {
if (TextUtils.isEmpty(query)) {
throw new IllegalArgumentException("query shouldn't be empty");
}
if (isConnected()) {
- return getImpl().prefetchFromSearch(query, extras);
+ return getImpl().prepareFromSearch(query, extras);
}
return createDisconnectedFuture();
}
/**
- * Requests that the player prefetch a media item with the specific {@link Uri} for playback.
+ * Requests that the player prepare a media item with the specific {@link Uri} for playback.
* In other words, other sessions can continue to play during the preparation of this session.
* This method can be used to speed up the start of the playback.
- * Once the prefetch is done, the session will change its playback state to
+ * Once the prepare is done, the session will change its playback state to
* {@link SessionPlayer2#PLAYER_STATE_PAUSED}. Afterwards, {@link #play} can be called to start
- * playback. If the prefetch is not needed, {@link #playFromUri} can be directly called
+ * playback. If the prepare is not needed, {@link #playFromUri} can be directly called
* without this method.
*
* @param uri The URI of the requested media.
@@ -479,13 +493,13 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public ListenableFuture<ControllerResult> prefetchFromUri(@NonNull Uri uri,
+ public ListenableFuture<ControllerResult> prepareFromUri(@NonNull Uri uri,
@Nullable Bundle extras) {
if (uri == null) {
throw new IllegalArgumentException("uri shouldn't be null");
}
if (isConnected()) {
- return getImpl().prefetchFromUri(uri, extras);
+ return getImpl().prepareFromUri(uri, extras);
}
return createDisconnectedFuture();
}
@@ -505,6 +519,7 @@
* @param flags flags from {@link AudioManager} to include with the volume request for local
* playback
*/
+ @NonNull
public ListenableFuture<ControllerResult> setVolumeTo(int value, @VolumeFlags int flags) {
if (isConnected()) {
return getImpl().setVolumeTo(value, flags);
@@ -532,6 +547,7 @@
* @param flags flags from {@link AudioManager} to include with the volume request for local
* playback
*/
+ @NonNull
public ListenableFuture<ControllerResult> adjustVolume(@VolumeDirection int direction,
@VolumeFlags int flags) {
if (isConnected()) {
@@ -546,7 +562,8 @@
*
* @return A {@link PendingIntent} to launch UI or null
*/
- public @Nullable PendingIntent getSessionActivity() {
+ @Nullable
+ public PendingIntent getSessionActivity() {
return isConnected() ? getImpl().getSessionActivity() : null;
}
@@ -597,6 +614,7 @@
/**
* Set the playback speed.
*/
+ @NonNull
public ListenableFuture<ControllerResult> setPlaybackSpeed(float speed) {
if (isConnected()) {
return getImpl().setPlaybackSpeed(speed);
@@ -634,7 +652,8 @@
*
* @return The current playback info or null
*/
- public @Nullable PlaybackInfo getPlaybackInfo() {
+ @Nullable
+ public PlaybackInfo getPlaybackInfo() {
return isConnected() ? getImpl().getPlaybackInfo() : null;
}
@@ -647,13 +666,14 @@
* <p>
* If the user rating was {@code null}, the media item does not accept setting user rating.
*
- * @param mediaId The id of the media
+ * @param mediaId The non-empty media id
* @param rating The rating to set
*/
+ @NonNull
public ListenableFuture<ControllerResult> setRating(@NonNull String mediaId,
@NonNull Rating2 rating) {
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
+ if (TextUtils.isEmpty(mediaId)) {
+ throw new IllegalArgumentException("mediaId shouldn't be empty");
}
if (rating == null) {
throw new IllegalArgumentException("rating shouldn't be null");
@@ -676,6 +696,7 @@
* @param command custom command
* @param args optional argument
*/
+ @NonNull
public ListenableFuture<ControllerResult> sendCustomCommand(@NonNull SessionCommand2 command,
@Nullable Bundle args) {
if (command == null) {
@@ -702,27 +723,31 @@
* or it doesn't have enough permission
* @see SessionCommand2#COMMAND_CODE_PLAYER_GET_PLAYLIST
*/
- public @Nullable List<MediaItem2> getPlaylist() {
+ @Nullable
+ public List<MediaItem2> getPlaylist() {
return isConnected() ? getImpl().getPlaylist() : null;
}
/**
- * Sets the playlist.
- * <p>
- * Even when the playlist is successfully set, use the playlist returned from
- * {@link #getPlaylist()} for playlist APIs such as {@link #skipToPlaylistItem(MediaItem2)}.
- * Otherwise the session in the remote process can't distinguish between media items.
+ * Sets the playlist with the list of media IDs. All media IDs in the list shouldn't be empty.
*
- * @param list playlist
+ * @param list list of media id. Shouldn't contain an empty id.
* @param metadata metadata of the playlist
* @see #getPlaylist()
* @see ControllerCallback#onPlaylistChanged
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
*/
- public ListenableFuture<ControllerResult> setPlaylist(@NonNull List<MediaItem2> list,
+ @NonNull
+ public ListenableFuture<ControllerResult> setPlaylist(@NonNull List<String> list,
@Nullable MediaMetadata2 metadata) {
if (list == null) {
throw new IllegalArgumentException("list shouldn't be null");
}
+ for (int i = 0; i < list.size(); i++) {
+ if (TextUtils.isEmpty(list.get(i))) {
+ throw new IllegalArgumentException("list shouldn't contain empty id, index=" + i);
+ }
+ }
if (isConnected()) {
return getImpl().setPlaylist(list, metadata);
}
@@ -732,15 +757,18 @@
/**
* Sets a {@link MediaItem2} for playback.
*
- * @param item the descriptor of media item you want to play
+ * @param mediaId The non-empty media id of the item to play
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
*/
- public void setMediaItem(@NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ @NonNull
+ public ListenableFuture<ControllerResult> setMediaItem(@NonNull String mediaId) {
+ if (TextUtils.isEmpty(mediaId)) {
+ throw new IllegalArgumentException("mediaId shouldn't be empty");
}
if (isConnected()) {
- getImpl().setMediaItem(item);
+ getImpl().setMediaItem(mediaId);
}
+ return createDisconnectedFuture();
}
/**
@@ -748,6 +776,7 @@
*
* @param metadata metadata of the playlist
*/
+ @NonNull
public ListenableFuture<ControllerResult> updatePlaylistMetadata(
@Nullable MediaMetadata2 metadata) {
if (isConnected()) {
@@ -764,70 +793,76 @@
* @return metadata metadata of the playlist, or null if none is set or the controller is not
* connected
*/
- public @Nullable MediaMetadata2 getPlaylistMetadata() {
+ @Nullable
+ public MediaMetadata2 getPlaylistMetadata() {
return isConnected() ? getImpl().getPlaylistMetadata() : null;
}
/**
- * Adds the media item to the playlist at position index. Index equals or greater than
- * the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
- * the playlist.
+ * Adds the media item to the playlist at the index with the media ID. Index equals or greater
+ * than the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end
+ * of the playlist.
* <p>
* This will not change the currently playing media item.
* If index is less than or equal to the current index of the playlist,
* the current index of the playlist will be incremented correspondingly.
*
* @param index the index you want to add
- * @param item the media item you want to add
+ * @param mediaId The non-empty media id of the new item
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
*/
- public ListenableFuture<ControllerResult> addPlaylistItem(int index, @NonNull MediaItem2 item) {
+ @NonNull
+ public ListenableFuture<ControllerResult> addPlaylistItem(@IntRange(from = 0) int index,
+ @NonNull String mediaId) {
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ if (TextUtils.isEmpty(mediaId)) {
+ throw new IllegalArgumentException("mediaId shouldn't be empty");
}
if (isConnected()) {
- return getImpl().addPlaylistItem(index, item);
+ return getImpl().addPlaylistItem(index, mediaId);
}
return createDisconnectedFuture();
}
/**
* Removes the media item at index in the playlist.
- *<p>
+ * <p>
* If the item is the currently playing item of the playlist, current playback
* will be stopped and playback moves to next source in the list.
*
- * @param item the media item you want to add
+ * @param index the media item you want to add
*/
- public ListenableFuture<ControllerResult> removePlaylistItem(@NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ @NonNull
+ public ListenableFuture<ControllerResult> removePlaylistItem(@IntRange(from = 0) int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("index shouldn't be negative");
}
if (isConnected()) {
- return getImpl().removePlaylistItem(item);
+ return getImpl().removePlaylistItem(index);
}
return createDisconnectedFuture();
}
/**
- * Replace the media item at index in the playlist. This can be also used to update metadata of
- * an item.
+ * Replaces the media item at index in the playlist with the media ID.
*
* @param index the index of the item to replace
- * @param item the new item
+ * @param mediaId The non-empty media id of the new item
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
*/
- public ListenableFuture<ControllerResult> replacePlaylistItem(int index,
- @NonNull MediaItem2 item) {
+ @NonNull
+ public ListenableFuture<ControllerResult> replacePlaylistItem(@IntRange(from = 0) int index,
+ @NonNull String mediaId) {
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ if (TextUtils.isEmpty(mediaId)) {
+ throw new IllegalArgumentException("mediaId shouldn't be empty");
}
if (isConnected()) {
- return getImpl().replacePlaylistItem(index, item);
+ return getImpl().replacePlaylistItem(index, mediaId);
}
return createDisconnectedFuture();
}
@@ -838,6 +873,7 @@
*
* @return the currently playing item, or null if unknown or not connected
*/
+ @Nullable
public MediaItem2 getCurrentMediaItem() {
return isConnected() ? getImpl().getCurrentMediaItem() : null;
}
@@ -847,6 +883,7 @@
* <p>
* This calls {@link SessionPlayer2#skipToPreviousPlaylistItem()}.
*/
+ @NonNull
public ListenableFuture<ControllerResult> skipToPreviousPlaylistItem() {
if (isConnected()) {
return getImpl().skipToPreviousItem();
@@ -859,6 +896,7 @@
* <p>
* This calls {@link SessionPlayer2#skipToNextPlaylistItem()}.
*/
+ @NonNull
public ListenableFuture<ControllerResult> skipToNextPlaylistItem() {
if (isConnected()) {
return getImpl().skipToNextItem();
@@ -867,18 +905,19 @@
}
/**
- * Skips to the item in the playlist.
+ * Skips to the item in the playlist at the index.
* <p>
* This calls {@link SessionPlayer2#skipToPlaylistItem(MediaItem2)}.
*
- * @param item The item in the playlist you want to play
+ * @param index The item in the playlist you want to play
*/
- public ListenableFuture<ControllerResult> skipToPlaylistItem(@NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ @NonNull
+ public ListenableFuture<ControllerResult> skipToPlaylistItem(@IntRange(from = 0) int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("index shouldn't be negative");
}
if (isConnected()) {
- return getImpl().skipToPlaylistItem(item);
+ return getImpl().skipToPlaylistItem(index);
}
return createDisconnectedFuture();
}
@@ -906,6 +945,7 @@
* @see SessionPlayer2#REPEAT_MODE_ALL
* @see SessionPlayer2#REPEAT_MODE_GROUP
*/
+ @NonNull
public ListenableFuture<ControllerResult> setRepeatMode(@RepeatMode int repeatMode) {
if (isConnected()) {
return getImpl().setRepeatMode(repeatMode);
@@ -934,6 +974,7 @@
* @see SessionPlayer2#SHUFFLE_MODE_ALL
* @see SessionPlayer2#SHUFFLE_MODE_GROUP
*/
+ @NonNull
public ListenableFuture<ControllerResult> setShuffleMode(@ShuffleMode int shuffleMode) {
if (isConnected()) {
return getImpl().setShuffleMode(shuffleMode);
@@ -1014,7 +1055,7 @@
boolean isConnected();
ListenableFuture<ControllerResult> play();
ListenableFuture<ControllerResult> pause();
- ListenableFuture<ControllerResult> prefetch();
+ ListenableFuture<ControllerResult> prepare();
ListenableFuture<ControllerResult> fastForward();
ListenableFuture<ControllerResult> rewind();
ListenableFuture<ControllerResult> seekTo(long pos);
@@ -1025,11 +1066,11 @@
ListenableFuture<ControllerResult> playFromSearch(@NonNull String query,
@Nullable Bundle extras);
ListenableFuture<ControllerResult> playFromUri(@NonNull Uri uri, @Nullable Bundle extras);
- ListenableFuture<ControllerResult> prefetchFromMediaId(@NonNull String mediaId,
+ ListenableFuture<ControllerResult> prepareFromMediaId(@NonNull String mediaId,
@Nullable Bundle extras);
- ListenableFuture<ControllerResult> prefetchFromSearch(@NonNull String query,
+ ListenableFuture<ControllerResult> prepareFromSearch(@NonNull String query,
@Nullable Bundle extras);
- ListenableFuture<ControllerResult> prefetchFromUri(@NonNull Uri uri,
+ ListenableFuture<ControllerResult> prepareFromUri(@NonNull Uri uri,
@Nullable Bundle extras);
ListenableFuture<ControllerResult> setVolumeTo(int value, @VolumeFlags int flags);
ListenableFuture<ControllerResult> adjustVolume(@VolumeDirection int direction,
@@ -1048,20 +1089,20 @@
ListenableFuture<ControllerResult> sendCustomCommand(@NonNull SessionCommand2 command,
@Nullable Bundle args);
@Nullable List<MediaItem2> getPlaylist();
- ListenableFuture<ControllerResult> setPlaylist(@NonNull List<MediaItem2> list,
+ ListenableFuture<ControllerResult> setPlaylist(@NonNull List<String> list,
@Nullable MediaMetadata2 metadata);
- ListenableFuture<ControllerResult> setMediaItem(@NonNull MediaItem2 item);
+ ListenableFuture<ControllerResult> setMediaItem(@NonNull String mediaId);
ListenableFuture<ControllerResult> updatePlaylistMetadata(
@Nullable MediaMetadata2 metadata);
@Nullable MediaMetadata2 getPlaylistMetadata();
- ListenableFuture<ControllerResult> addPlaylistItem(int index, @NonNull MediaItem2 item);
- ListenableFuture<ControllerResult> removePlaylistItem(@NonNull MediaItem2 item);
+ ListenableFuture<ControllerResult> addPlaylistItem(int index, @NonNull String mediaId);
+ ListenableFuture<ControllerResult> removePlaylistItem(@NonNull int index);
ListenableFuture<ControllerResult> replacePlaylistItem(int index,
- @NonNull MediaItem2 item);
+ @NonNull String mediaId);
MediaItem2 getCurrentMediaItem();
ListenableFuture<ControllerResult> skipToPreviousItem();
ListenableFuture<ControllerResult> skipToNextItem();
- ListenableFuture<ControllerResult> skipToPlaylistItem(@NonNull MediaItem2 item);
+ ListenableFuture<ControllerResult> skipToPlaylistItem(@NonNull int index);
@RepeatMode int getRepeatMode();
ListenableFuture<ControllerResult> setRepeatMode(@RepeatMode int repeatMode);
@ShuffleMode int getShuffleMode();
@@ -1166,7 +1207,8 @@
* @param args
* @return result of handling custom command
*/
- public @NonNull ControllerResult onCustomCommand(@NonNull MediaController2 controller,
+ @NonNull
+ public ControllerResult onCustomCommand(@NonNull MediaController2 controller,
@NonNull SessionCommand2 command, @Nullable Bundle args) {
return new ControllerResult(ControllerResult.RESULT_CODE_NOT_SUPPORTED);
}
@@ -1230,7 +1272,7 @@
* @param metadata new metadata
*/
public void onPlaylistChanged(@NonNull MediaController2 controller,
- @NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { }
+ @Nullable List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { }
/**
* Called when a playlist metadata is changed.
@@ -1295,12 +1337,6 @@
// The same as MediaController.PlaybackInfo
@VersionedParcelize
public static final class PlaybackInfo implements VersionedParcelable {
- private static final String KEY_PLAYBACK_TYPE = "android.media.audio_info.playback_type";
- private static final String KEY_CONTROL_TYPE = "android.media.audio_info.control_type";
- private static final String KEY_MAX_VOLUME = "android.media.audio_info.max_volume";
- private static final String KEY_CURRENT_VOLUME = "android.media.audio_info.current_volume";
- private static final String KEY_AUDIO_ATTRIBUTES = "android.media.audio_info.audio_attrs";
-
@ParcelField(1)
int mPlaybackType;
@ParcelField(2)
@@ -1357,6 +1393,7 @@
*
* @return The attributes for this session
*/
+ @Nullable
public AudioAttributesCompat getAudioAttributes() {
return mAudioAttrsCompat;
}
@@ -1416,36 +1453,10 @@
&& ObjectsCompat.equals(mAudioAttrsCompat, other.mAudioAttrsCompat);
}
- Bundle toBundle() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_PLAYBACK_TYPE, mPlaybackType);
- bundle.putInt(KEY_CONTROL_TYPE, mControlType);
- bundle.putInt(KEY_MAX_VOLUME, mMaxVolume);
- bundle.putInt(KEY_CURRENT_VOLUME, mCurrentVolume);
- if (mAudioAttrsCompat != null) {
- bundle.putBundle(KEY_AUDIO_ATTRIBUTES, mAudioAttrsCompat.toBundle());
- }
- return bundle;
- }
-
static PlaybackInfo createPlaybackInfo(int playbackType, AudioAttributesCompat attrs,
int controlType, int max, int current) {
return new PlaybackInfo(playbackType, attrs, controlType, max, current);
}
-
- static PlaybackInfo fromBundle(Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- final int volumeType = bundle.getInt(KEY_PLAYBACK_TYPE);
- final int volumeControl = bundle.getInt(KEY_CONTROL_TYPE);
- final int maxVolume = bundle.getInt(KEY_MAX_VOLUME);
- final int currentVolume = bundle.getInt(KEY_CURRENT_VOLUME);
- final AudioAttributesCompat attrs = AudioAttributesCompat.fromBundle(
- bundle.getBundle(KEY_AUDIO_ATTRIBUTES));
- return createPlaybackInfo(volumeType, attrs, volumeControl, maxVolume,
- currentVolume);
- }
}
/**
@@ -1589,7 +1600,8 @@
* @see #sendCustomCommand(SessionCommand2, Bundle)
* @return result of send custom command
*/
- public @Nullable Bundle getCustomCommandResult() {
+ @Nullable
+ public Bundle getCustomCommandResult() {
return mCustomCommandResult;
}
@@ -1608,7 +1620,8 @@
* current media item was {@code null}, or any other reason.
*/
@Override
- public @Nullable MediaItem2 getMediaItem() {
+ @Nullable
+ public MediaItem2 getMediaItem() {
return mItem;
}
}
diff --git a/media2/src/main/java/androidx/media2/MediaController2ImplBase.java b/media2/src/main/java/androidx/media2/MediaController2ImplBase.java
index 8c39126..73d4723 100644
--- a/media2/src/main/java/androidx/media2/MediaController2ImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaController2ImplBase.java
@@ -25,7 +25,7 @@
import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM;
import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_PAUSE;
import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_PLAY;
-import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_PREFETCH;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_PREPARE;
import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM;
import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM;
import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_SEEK_TO;
@@ -42,12 +42,14 @@
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI;
-import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID;
-import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH;
-import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_URI;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_REWIND;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SELECT_ROUTE;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SET_RATING;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SKIP_BACKWARD;
+import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SKIP_FORWARD;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO;
import static androidx.media2.SessionCommand2.COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO;
import static androidx.media2.SessionCommand2.COMMAND_CODE_VOLUME_ADJUST_VOLUME;
@@ -82,8 +84,6 @@
import androidx.media2.SessionCommand2.CommandCode;
import androidx.media2.SessionPlayer2.RepeatMode;
import androidx.media2.SessionPlayer2.ShuffleMode;
-import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
import com.google.common.util.concurrent.ListenableFuture;
@@ -96,7 +96,7 @@
new ControllerResult(RESULT_CODE_SKIPPED);
static final String TAG = "MC2ImplBase";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean DEBUG = true; //Log.isLoggable(TAG, Log.DEBUG);
@SuppressWarnings("WeakerAccess") /* synthetic access */
final MediaController2 mInstance;
@@ -302,11 +302,11 @@
}
@Override
- public ListenableFuture<ControllerResult> prefetch() {
- return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_PREFETCH, new RemoteSessionTask() {
+ public ListenableFuture<ControllerResult> prepare() {
+ return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_PREPARE, new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.prefetch(mControllerStub, seq);
+ iSession2.prepare(mControllerStub, seq);
}
});
}
@@ -333,6 +333,28 @@
}
@Override
+ public ListenableFuture<ControllerResult> skipForward() {
+ return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_SKIP_FORWARD,
+ new RemoteSessionTask() {
+ @Override
+ public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
+ iSession2.skipForward(mControllerStub, seq);
+ }
+ });
+ }
+
+ @Override
+ public ListenableFuture<ControllerResult> skipBackward() {
+ return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_SKIP_BACKWARD,
+ new RemoteSessionTask() {
+ @Override
+ public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
+ iSession2.skipBackward(mControllerStub, seq);
+ }
+ });
+ }
+
+ @Override
public ListenableFuture<ControllerResult> seekTo(final long pos) {
if (pos < 0) {
throw new IllegalArgumentException("position shouldn't be negative");
@@ -346,18 +368,6 @@
}
@Override
- public ListenableFuture<ControllerResult> skipForward() {
- // To match with KEYCODE_MEDIA_SKIP_FORWARD
- return null;
- }
-
- @Override
- public ListenableFuture<ControllerResult> skipBackward() {
- // To match with KEYCODE_MEDIA_SKIP_BACKWARD
- return null;
- }
-
- @Override
public ListenableFuture<ControllerResult> playFromMediaId(final @NonNull String mediaId,
final @Nullable Bundle extras) {
return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID,
@@ -394,37 +404,37 @@
}
@Override
- public ListenableFuture<ControllerResult> prefetchFromMediaId(final @NonNull String mediaId,
+ public ListenableFuture<ControllerResult> prepareFromMediaId(final @NonNull String mediaId,
final @Nullable Bundle extras) {
- return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID,
+ return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.prefetchFromMediaId(mControllerStub, seq, mediaId, extras);
+ iSession2.prepareFromMediaId(mControllerStub, seq, mediaId, extras);
}
});
}
@Override
- public ListenableFuture<ControllerResult> prefetchFromSearch(final @NonNull String query,
+ public ListenableFuture<ControllerResult> prepareFromSearch(final @NonNull String query,
final @Nullable Bundle extras) {
- return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH,
+ return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.prefetchFromSearch(mControllerStub, seq, query, extras);
+ iSession2.prepareFromSearch(mControllerStub, seq, query, extras);
}
});
}
@Override
- public ListenableFuture<ControllerResult> prefetchFromUri(final @NonNull Uri uri,
+ public ListenableFuture<ControllerResult> prepareFromUri(final @NonNull Uri uri,
final @Nullable Bundle extras) {
- return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_PREFETCH_FROM_URI,
+ return dispatchRemoteSessionTask(COMMAND_CODE_SESSION_PREPARE_FROM_URI,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.prefetchFromUri(mControllerStub, seq, uri, extras);
+ iSession2.prepareFromUri(mControllerStub, seq, uri, extras);
}
});
}
@@ -552,7 +562,7 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.setRating(mControllerStub, seq, mediaId,
- (ParcelImpl) ParcelUtils.toParcelable(rating));
+ MediaUtils2.toParcelable(rating));
}
});
}
@@ -563,8 +573,8 @@
return dispatchRemoteSessionTask(command, new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.onCustomCommand(mControllerStub, seq,
- (ParcelImpl) ParcelUtils.toParcelable(command), args);
+ iSession2.onCustomCommand(mControllerStub, seq, MediaUtils2.toParcelable(command),
+ args);
}
});
}
@@ -577,26 +587,24 @@
}
@Override
- public ListenableFuture<ControllerResult> setPlaylist(final @NonNull List<MediaItem2> list,
+ public ListenableFuture<ControllerResult> setPlaylist(final @NonNull List<String> list,
final @Nullable MediaMetadata2 metadata) {
return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_PLAYLIST, new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.setPlaylist(mControllerStub, seq,
- MediaUtils2.convertMediaItem2ListToParcelImplListSlice(list),
- (metadata == null) ? null : metadata.toBundle());
+ iSession2.setPlaylist(mControllerStub, seq, list,
+ MediaUtils2.toParcelable(metadata));
}
});
}
@Override
- public ListenableFuture<ControllerResult> setMediaItem(final MediaItem2 item) {
+ public ListenableFuture<ControllerResult> setMediaItem(final String mediaId) {
return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_MEDIA_ITEM,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.setMediaItem(mControllerStub, seq,
- (ParcelImpl) ParcelUtils.toParcelable(item));
+ iSession2.setMediaItem(mControllerStub, seq, mediaId);
}
});
}
@@ -609,7 +617,7 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.updatePlaylistMetadata(mControllerStub, seq,
- (metadata == null) ? null : metadata.toBundle());
+ MediaUtils2.toParcelable(metadata));
}
});
}
@@ -623,38 +631,35 @@
@Override
public ListenableFuture<ControllerResult> addPlaylistItem(final int index,
- final @NonNull MediaItem2 item) {
+ final @NonNull String mediaId) {
return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.addPlaylistItem(mControllerStub, seq, index,
- (ParcelImpl) ParcelUtils.toParcelable(item));
+ iSession2.addPlaylistItem(mControllerStub, seq, index, mediaId);
}
});
}
@Override
- public ListenableFuture<ControllerResult> removePlaylistItem(final @NonNull MediaItem2 item) {
+ public ListenableFuture<ControllerResult> removePlaylistItem(final @NonNull int index) {
return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.removePlaylistItem(mControllerStub, seq,
- (ParcelImpl) ParcelUtils.toParcelable(item));
+ iSession2.removePlaylistItem(mControllerStub, seq, index);
}
});
}
@Override
public ListenableFuture<ControllerResult> replacePlaylistItem(final int index,
- final @NonNull MediaItem2 item) {
+ final @NonNull String mediaId) {
return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.replacePlaylistItem(mControllerStub, seq, index,
- (ParcelImpl) ParcelUtils.toParcelable(item));
+ iSession2.replacePlaylistItem(mControllerStub, seq, index, mediaId);
}
});
}
@@ -689,13 +694,12 @@
}
@Override
- public ListenableFuture<ControllerResult> skipToPlaylistItem(final @NonNull MediaItem2 item) {
+ public ListenableFuture<ControllerResult> skipToPlaylistItem(final @NonNull int index) {
return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
new RemoteSessionTask() {
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
- iSession2.skipToPlaylistItem(mControllerStub, seq,
- (ParcelImpl) ParcelUtils.toParcelable(item));
+ iSession2.skipToPlaylistItem(mControllerStub, seq, index);
}
});
}
@@ -1132,7 +1136,7 @@
}
try {
iSession2.onControllerResult(mControllerStub, seq,
- (ParcelImpl) ParcelUtils.toParcelable(result));
+ MediaUtils2.toParcelable(result));
} catch (RemoteException e) {
Log.w(TAG, "Error in sending");
}
diff --git a/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java b/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java
index 4600b85..b81cf27 100644
--- a/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java
+++ b/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java
@@ -20,6 +20,7 @@
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
+import static androidx.media2.BaseResult2.RESULT_CODE_BAD_VALUE;
import static androidx.media2.MediaConstants2.ARGUMENT_COMMAND_CODE;
import static androidx.media2.MediaConstants2.ARGUMENT_ICONTROLLER_CALLBACK;
import static androidx.media2.MediaConstants2.ARGUMENT_PACKAGE_NAME;
@@ -55,6 +56,7 @@
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.GuardedBy;
@@ -75,9 +77,7 @@
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
import java.util.List;
-import java.util.UUID;
import java.util.concurrent.Executor;
// TODO: Find better way to return listenable future.
@@ -150,7 +150,7 @@
int mCurrentMediaItemIndex;
@GuardedBy("mLock")
@SuppressWarnings("WeakerAccess") /* synthetic access */
- MediaItem2 mSkipToPlaylistItem;
+ int mSkipToPlaylistIndex = -1;
@GuardedBy("mLock")
@SuppressWarnings("WeakerAccess") /* synthetic access */
long mBufferedPosition;
@@ -287,7 +287,7 @@
}
@Override
- public ListenableFuture<ControllerResult> prefetch() {
+ public ListenableFuture<ControllerResult> prepare() {
synchronized (mLock) {
if (!mConnected) {
Log.w(TAG, "Session isn't active", new IllegalStateException());
@@ -323,6 +323,18 @@
}
@Override
+ public ListenableFuture<ControllerResult> skipForward() {
+ // Unsupported action
+ return createFutureWithResult(RESULT_CODE_NOT_SUPPORTED);
+ }
+
+ @Override
+ public ListenableFuture<ControllerResult> skipBackward() {
+ // Unsupported action
+ return createFutureWithResult(RESULT_CODE_NOT_SUPPORTED);
+ }
+
+ @Override
public ListenableFuture<ControllerResult> seekTo(long pos) {
synchronized (mLock) {
if (!mConnected) {
@@ -335,18 +347,6 @@
}
@Override
- public ListenableFuture<ControllerResult> skipForward() {
- // To match with KEYCODE_MEDIA_SKIP_FORWARD
- return null;
- }
-
- @Override
- public ListenableFuture<ControllerResult> skipBackward() {
- // To match with KEYCODE_MEDIA_SKIP_BACKWARD
- return null;
- }
-
- @Override
public ListenableFuture<ControllerResult> playFromMediaId(@NonNull String mediaId,
@Nullable Bundle extras) {
synchronized (mLock) {
@@ -386,7 +386,7 @@
}
@Override
- public ListenableFuture<ControllerResult> prefetchFromMediaId(@NonNull String mediaId,
+ public ListenableFuture<ControllerResult> prepareFromMediaId(@NonNull String mediaId,
@Nullable Bundle extras) {
synchronized (mLock) {
if (!mConnected) {
@@ -399,7 +399,7 @@
}
@Override
- public ListenableFuture<ControllerResult> prefetchFromSearch(@NonNull String query,
+ public ListenableFuture<ControllerResult> prepareFromSearch(@NonNull String query,
@Nullable Bundle extras) {
synchronized (mLock) {
if (!mConnected) {
@@ -412,7 +412,7 @@
}
@Override
- public ListenableFuture<ControllerResult> prefetchFromUri(@NonNull Uri uri,
+ public ListenableFuture<ControllerResult> prepareFromUri(@NonNull Uri uri,
@Nullable Bundle extras) {
synchronized (mLock) {
if (!mConnected) {
@@ -595,18 +595,18 @@
Log.w(TAG, "Session isn't active", new IllegalStateException());
return null;
}
- return mPlaylist;
+ return (mPlaylist == null || mPlaylist.size() == 0) ? null : mPlaylist;
}
}
@Override
- public ListenableFuture<ControllerResult> setPlaylist(@NonNull List<MediaItem2> list,
+ public ListenableFuture<ControllerResult> setPlaylist(@NonNull List<String> list,
@Nullable MediaMetadata2 metadata) {
return createFutureWithResult(RESULT_CODE_NOT_SUPPORTED);
}
@Override
- public ListenableFuture<ControllerResult> setMediaItem(MediaItem2 item) {
+ public ListenableFuture<ControllerResult> setMediaItem(String mediaId) {
return createFutureWithResult(RESULT_CODE_NOT_SUPPORTED);
}
@@ -628,45 +628,46 @@
}
@Override
- public ListenableFuture<ControllerResult> addPlaylistItem(int index, @NonNull MediaItem2 item) {
+ public ListenableFuture<ControllerResult> addPlaylistItem(int index, @NonNull String mediaId) {
synchronized (mLock) {
if (!mConnected) {
Log.w(TAG, "Session isn't active", new IllegalStateException());
return createFutureWithResult(RESULT_CODE_DISCONNECTED);
}
mControllerCompat.addQueueItem(
- MediaUtils2.convertToMediaMetadataCompat(item.getMetadata()).getDescription(),
- index);
+ MediaUtils2.createMediaDescriptionCompat(mediaId), index);
}
return createFutureWithResult(RESULT_CODE_SUCCESS);
}
@Override
- public ListenableFuture<ControllerResult> removePlaylistItem(@NonNull MediaItem2 item) {
+ public ListenableFuture<ControllerResult> removePlaylistItem(@NonNull int index) {
synchronized (mLock) {
if (!mConnected) {
Log.w(TAG, "Session isn't active", new IllegalStateException());
return createFutureWithResult(RESULT_CODE_DISCONNECTED);
}
- mControllerCompat.removeQueueItem(
- MediaUtils2.convertToQueueItem(item).getDescription());
+ if (mQueue == null || index < 0 || index >= mQueue.size()) {
+ return createFutureWithResult(RESULT_CODE_BAD_VALUE);
+ }
+ mControllerCompat.removeQueueItem(mQueue.get(index).getDescription());
}
return createFutureWithResult(RESULT_CODE_SUCCESS);
}
@Override
public ListenableFuture<ControllerResult> replacePlaylistItem(int index,
- @NonNull MediaItem2 item) {
+ @NonNull String mediaId) {
synchronized (mLock) {
if (!mConnected) {
Log.w(TAG, "Session isn't active", new IllegalStateException());
return createFutureWithResult(RESULT_CODE_DISCONNECTED);
}
- if (mPlaylist == null || mPlaylist.size() <= index) {
+ if (mPlaylist == null || index < 0 || mPlaylist.size() <= index) {
return createFutureWithResult(RESULT_CODE_DISCONNECTED);
}
- removePlaylistItem(mPlaylist.get(index));
- addPlaylistItem(index, item);
+ removePlaylistItem(index);
+ addPlaylistItem(index, mediaId);
}
return createFutureWithResult(RESULT_CODE_SUCCESS);
}
@@ -707,15 +708,15 @@
}
@Override
- public ListenableFuture<ControllerResult> skipToPlaylistItem(@NonNull MediaItem2 item) {
+ public ListenableFuture<ControllerResult> skipToPlaylistItem(@NonNull int index) {
synchronized (mLock) {
if (!mConnected) {
Log.w(TAG, "Session isn't active", new IllegalStateException());
return createFutureWithResult(RESULT_CODE_DISCONNECTED);
}
- mSkipToPlaylistItem = item;
+ mSkipToPlaylistIndex = index;
mControllerCompat.getTransportControls().skipToQueueItem(
- MediaUtils2.convertToQueueItem(item).getQueueId());
+ mQueue.get(index).getQueueId());
}
return createFutureWithResult(RESULT_CODE_SUCCESS);
}
@@ -877,8 +878,15 @@
mRepeatMode = mControllerCompat.getRepeatMode();
mShuffleMode = mControllerCompat.getShuffleMode();
- mPlaylist = MediaUtils2.convertQueueItemListToMediaItem2List(
- mControllerCompat.getQueue());
+ mQueue = MediaUtils2.removeNullElements(mControllerCompat.getQueue());
+ if (mQueue == null || mQueue.size() == 0) {
+ // MediaSessionCompat can set queue as null or empty. However, SessionPlayer2 should
+ // not set playlist as null or empty. Therefore, we treat them as the same.
+ mQueue = null;
+ mPlaylist = null;
+ } else {
+ mPlaylist = MediaUtils2.convertQueueItemListToMediaItem2List(mQueue);
+ }
mPlaylistMetadata = MediaUtils2.convertToMediaMetadata2(
mControllerCompat.getQueueTitle());
@@ -971,27 +979,26 @@
return;
}
- if (mPlaylist == null) {
+ if (mQueue == null) {
mCurrentMediaItemIndex = -1;
mCurrentMediaItem = MediaUtils2.convertToMediaItem2(metadata);
return;
}
- String mediaId = metadata.getString(METADATA_KEY_MEDIA_ID);
if (mPlaybackStateCompat != null) {
- // If playback state is updated before, compare UUID using queue id and media id.
- UUID uuid = MediaUtils2.createUuidByQueueIdAndMediaId(
- mPlaybackStateCompat.getActiveQueueItemId(), mediaId);
- for (int i = 0; i < mPlaylist.size(); ++i) {
- MediaItem2 item = mPlaylist.get(i);
- if (item != null && uuid.equals(item.getUuid())) {
- mCurrentMediaItem = item;
+ // If playback state is updated before, compare use queue id and media id.
+ long queueId = mPlaybackStateCompat.getActiveQueueItemId();
+ for (int i = 0; i < mQueue.size(); ++i) {
+ QueueItem item = mQueue.get(i);
+ if (item.getQueueId() == queueId) {
+ mCurrentMediaItem = MediaUtils2.convertToMediaItem2(metadata);
mCurrentMediaItemIndex = i;
return;
}
}
}
+ String mediaId = metadata.getString(METADATA_KEY_MEDIA_ID);
if (mediaId == null) {
mCurrentMediaItemIndex = -1;
mCurrentMediaItem = MediaUtils2.convertToMediaItem2(metadata);
@@ -1000,21 +1007,22 @@
// Need to find the media item in the playlist using mediaId.
// Note that there can be multiple media items with the same media id.
- if (mSkipToPlaylistItem != null && mediaId.equals(mSkipToPlaylistItem.getMediaId())) {
+ if (mSkipToPlaylistIndex >= 0 && mSkipToPlaylistIndex < mQueue.size()
+ && TextUtils.equals(mediaId,
+ mQueue.get(mSkipToPlaylistIndex).getDescription().getMediaId())) {
// metadata changed after skipToPlaylistIItem() was called.
- mCurrentMediaItem = mSkipToPlaylistItem;
- mCurrentMediaItemIndex = mPlaylist.indexOf(mSkipToPlaylistItem);
- mSkipToPlaylistItem = null;
+ mCurrentMediaItem = MediaUtils2.convertToMediaItem2(metadata);
+ mCurrentMediaItemIndex = mSkipToPlaylistIndex;
+ mSkipToPlaylistIndex = -1;
return;
}
- MediaItem2 item;
// Find mediaId from the playlist.
- for (int i = 0; i < mPlaylist.size(); ++i) {
- item = mPlaylist.get(i);
- if (item != null && mediaId.equals(item.getMediaId())) {
+ for (int i = 0; i < mQueue.size(); ++i) {
+ QueueItem item = mQueue.get(i);
+ if (TextUtils.equals(mediaId, item.getDescription().getMediaId())) {
mCurrentMediaItemIndex = i;
- mCurrentMediaItem = item;
+ mCurrentMediaItem = MediaUtils2.convertToMediaItem2(metadata);
return;
}
}
@@ -1077,8 +1085,10 @@
@Override
public void onPlaybackStateChanged(final PlaybackStateCompat state) {
final PlaybackStateCompat prevState;
+ final MediaItem2 prevItem;
final MediaItem2 currentItem;
synchronized (mLock) {
+ prevItem = mCurrentMediaItem;
prevState = mPlaybackStateCompat;
mPlaybackStateCompat = state;
mPlayerState = MediaUtils2.convertToPlayerState(state);
@@ -1095,6 +1105,16 @@
}
currentItem = mCurrentMediaItem;
}
+
+ if (prevItem != currentItem) {
+ mCallbackExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onCurrentMediaItemChanged(mInstance, currentItem);
+ }
+ });
+ }
+
if (state == null) {
if (prevState != null) {
mCallbackExecutor.execute(new Runnable() {
@@ -1158,8 +1178,20 @@
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
+ final MediaItem2 prevItem;
+ final MediaItem2 currentItem;
synchronized (mLock) {
+ prevItem = mCurrentMediaItem;
setCurrentMediaItemLocked(metadata);
+ currentItem = mCurrentMediaItem;
+ }
+ if (prevItem != currentItem) {
+ mCallbackExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onCurrentMediaItemChanged(mInstance, currentItem);
+ }
+ });
}
}
@@ -1168,12 +1200,15 @@
final List<MediaItem2> playlist;
final MediaMetadata2 playlistMetadata;
synchronized (mLock) {
- mQueue = queue;
- mPlaylist = MediaUtils2.convertQueueItemListToMediaItem2List(queue);
- if (mPlaylist == null) {
- // MediaSessionCompat can set queue as null. However, SessionPlayer2 should not
- // set playlist as null. Therefore, we treat a null queue as an empty playlist.
- mPlaylist = new ArrayList<>();
+ mQueue = MediaUtils2.removeNullElements(queue);
+ if (mQueue == null || mQueue.size() == 0) {
+ // MediaSessionCompat can set queue as null or empty. However, SessionPlayer2
+ // should not set playlist as null or empty. Therefore, we treat them as the
+ // same.
+ mQueue = null;
+ mPlaylist = null;
+ } else {
+ mPlaylist = MediaUtils2.convertQueueItemListToMediaItem2List(mQueue);
}
playlist = mPlaylist;
playlistMetadata = mPlaylistMetadata;
diff --git a/media2/src/main/java/androidx/media2/MediaController2Stub.java b/media2/src/main/java/androidx/media2/MediaController2Stub.java
index df244e8..14f2050 100644
--- a/media2/src/main/java/androidx/media2/MediaController2Stub.java
+++ b/media2/src/main/java/androidx/media2/MediaController2Stub.java
@@ -30,7 +30,6 @@
import androidx.media2.MediaSession2.SessionResult;
import androidx.media2.SessionPlayer2.BuffState;
import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -50,6 +49,9 @@
@Override
public void onSessionResult(int seq, ParcelImpl sessionResult) {
+ if (sessionResult == null) {
+ return;
+ }
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -57,24 +59,36 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- SessionResult result = ParcelUtils.fromParcelable(sessionResult);
+ SessionResult result = MediaUtils2.fromParcelable(sessionResult);
+ if (result == null) {
+ return;
+ }
mSequencedFutureManager.setFutureResult(seq, ControllerResult.from(result));
}
@Override
public void onLibraryResult(int seq, ParcelImpl libraryResult) {
+ if (libraryResult == null) {
+ return;
+ }
try {
final MediaBrowser2 browser = getBrowser();
} catch (IllegalStateException e) {
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- LibraryResult result = ParcelUtils.fromParcelable(libraryResult);
+ LibraryResult result = MediaUtils2.fromParcelable(libraryResult);
+ if (result == null) {
+ return;
+ }
mSequencedFutureManager.setFutureResult(seq, BrowserResult.from(result));
}
@Override
public void onCurrentMediaItemChanged(ParcelImpl item) {
+ if (item == null) {
+ return;
+ }
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -82,7 +96,7 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- controller.notifyCurrentMediaItemChanged((MediaItem2) ParcelUtils.fromParcelable(item));
+ controller.notifyCurrentMediaItemChanged((MediaItem2) MediaUtils2.fromParcelable(item));
}
@Override
@@ -112,6 +126,9 @@
@Override
public void onBufferingStateChanged(ParcelImpl item, @BuffState int state,
long bufferedPositionMs) {
+ if (item == null) {
+ return;
+ }
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -119,7 +136,7 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- MediaItem2 item2 = (item == null) ? null : (MediaItem2) ParcelUtils.fromParcelable(item);
+ MediaItem2 item2 = MediaUtils2.fromParcelable(item);
if (item2 == null) {
Log.w(TAG, "onBufferingStateChanged(): Ignoring null item");
return;
@@ -128,7 +145,10 @@
}
@Override
- public void onPlaylistChanged(ParcelImplListSlice listSlice, Bundle metadataBundle) {
+ public void onPlaylistChanged(ParcelImplListSlice listSlice, ParcelImpl metadata) {
+ if (metadata == null) {
+ return;
+ }
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -138,16 +158,15 @@
}
List<MediaItem2> playlist =
MediaUtils2.convertParcelImplListSliceToMediaItem2List(listSlice);
- if (playlist == null) {
- Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist");
- return;
- }
- MediaMetadata2 metadata = MediaMetadata2.fromBundle(metadataBundle);
- controller.notifyPlaylistChanges(playlist, metadata);
+ controller.notifyPlaylistChanges(playlist,
+ (MediaMetadata2) MediaUtils2.fromParcelable(metadata));
}
@Override
- public void onPlaylistMetadataChanged(Bundle metadataBundle) throws RuntimeException {
+ public void onPlaylistMetadataChanged(ParcelImpl metadata) throws RuntimeException {
+ if (metadata == null) {
+ return;
+ }
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -155,8 +174,8 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- MediaMetadata2 metadata = MediaMetadata2.fromBundle(metadataBundle);
- controller.notifyPlaylistMetadataChanges(metadata);
+ controller.notifyPlaylistMetadataChanges(
+ (MediaMetadata2) MediaUtils2.fromParcelable(metadata));
}
@Override
@@ -197,6 +216,9 @@
@Override
public void onPlaybackInfoChanged(ParcelImpl playbackInfo) throws RuntimeException {
+ if (playbackInfo == null) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "onPlaybackInfoChanged");
}
@@ -207,7 +229,7 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- PlaybackInfo info = ParcelUtils.fromParcelable(playbackInfo);
+ PlaybackInfo info = MediaUtils2.fromParcelable(playbackInfo);
if (info == null) {
Log.w(TAG, "onPlaybackInfoChanged(): Ignoring null playbackInfo");
return;
@@ -245,6 +267,10 @@
ParcelImpl currentItem, long positionEventTimeMs, long positionMs, float playbackSpeed,
long bufferedPositionMs, ParcelImpl playbackInfo, int shuffleMode, int repeatMode,
ParcelImplListSlice listSlice, PendingIntent sessionActivity) {
+ if (sessionBinder == null || commandGroup == null || currentItem == null
+ || playbackInfo == null) {
+ return;
+ }
final MediaController2ImplBase controller = mController.get();
if (controller == null) {
if (DEBUG) {
@@ -255,10 +281,10 @@
List<MediaItem2> itemList =
MediaUtils2.convertParcelImplListSliceToMediaItem2List(listSlice);
controller.onConnectedNotLocked(sessionBinder,
- (SessionCommandGroup2) ParcelUtils.fromParcelable(commandGroup), playerState,
- (MediaItem2) ParcelUtils.fromParcelable(currentItem),
+ (SessionCommandGroup2) MediaUtils2.fromParcelable(commandGroup), playerState,
+ (MediaItem2) MediaUtils2.fromParcelable(currentItem),
positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
- (PlaybackInfo) ParcelUtils.fromParcelable(playbackInfo), repeatMode, shuffleMode,
+ (PlaybackInfo) MediaUtils2.fromParcelable(playbackInfo), repeatMode, shuffleMode,
itemList, sessionActivity);
}
@@ -293,7 +319,7 @@
}
List<CommandButton> layout = new ArrayList<>();
for (int i = 0; i < commandButtonList.size(); i++) {
- CommandButton button = ParcelUtils.fromParcelable(commandButtonList.get(i));
+ CommandButton button = MediaUtils2.fromParcelable(commandButtonList.get(i));
if (button != null) {
layout.add(button);
}
@@ -303,6 +329,9 @@
@Override
public void onAllowedCommandsChanged(ParcelImpl commands) {
+ if (commands == null) {
+ return;
+ }
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -314,7 +343,7 @@
// TODO(jaewan): Revisit here. Could be a bug
return;
}
- SessionCommandGroup2 commandGroup = ParcelUtils.fromParcelable(commands);
+ SessionCommandGroup2 commandGroup = MediaUtils2.fromParcelable(commands);
if (commandGroup == null) {
Log.w(TAG, "onAllowedCommandsChanged(): Ignoring null commands");
return;
@@ -324,6 +353,9 @@
@Override
public void onCustomCommand(int seq, ParcelImpl commandParcel, Bundle args) {
+ if (commandParcel == null) {
+ return;
+ }
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -331,7 +363,7 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- SessionCommand2 command = ParcelUtils.fromParcelable(commandParcel);
+ SessionCommand2 command = MediaUtils2.fromParcelable(commandParcel);
if (command == null) {
Log.w(TAG, "sendCustomCommand(): Ignoring null command");
return;
@@ -345,6 +377,9 @@
@Override
public void onSearchResultChanged(final String query, final int itemCount,
final ParcelImpl libraryParams) throws RuntimeException {
+ if (libraryParams == null) {
+ return;
+ }
if (TextUtils.isEmpty(query)) {
Log.w(TAG, "onSearchResultChanged(): Ignoring empty query");
return;
@@ -367,7 +402,7 @@
@Override
public void run() {
browser.getCallback().onSearchResultChanged(browser, query, itemCount,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
});
}
@@ -375,8 +410,11 @@
@Override
public void onChildrenChanged(final String parentId, final int itemCount,
final ParcelImpl libraryParams) {
- if (parentId == null) {
- Log.w(TAG, "onChildrenChanged(): Ignoring null parentId");
+ if (libraryParams == null) {
+ return;
+ }
+ if (TextUtils.isEmpty(parentId)) {
+ Log.w(TAG, "onChildrenChanged(): Ignoring empty parentId");
return;
}
if (itemCount < 0) {
@@ -396,8 +434,10 @@
browser.getCallbackExecutor().execute(new Runnable() {
@Override
public void run() {
+ // TODO (b/118472216): Find all ParcelUtils.fromParcelable usages, and null check
+ // before calling it.
browser.getCallback().onChildrenChanged(browser, parentId, itemCount,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
});
}
diff --git a/media2/src/main/java/androidx/media2/MediaInterface2.java b/media2/src/main/java/androidx/media2/MediaInterface2.java
index 2208c85..7b705fc 100644
--- a/media2/src/main/java/androidx/media2/MediaInterface2.java
+++ b/media2/src/main/java/androidx/media2/MediaInterface2.java
@@ -28,7 +28,7 @@
// TODO: relocate methods among different interfaces and classes.
interface SessionPlaybackControl {
- ListenableFuture<PlayerResult> prefetch();
+ ListenableFuture<PlayerResult> prepare();
ListenableFuture<PlayerResult> play();
ListenableFuture<PlayerResult> pause();
@@ -48,18 +48,17 @@
interface SessionPlaylistControl {
List<MediaItem2> getPlaylist();
MediaMetadata2 getPlaylistMetadata();
- ListenableFuture<PlayerResult> setPlaylist(List<MediaItem2> list,
- MediaMetadata2 metadata);
+ ListenableFuture<PlayerResult> setPlaylist(List<MediaItem2> list, MediaMetadata2 metadata);
ListenableFuture<PlayerResult> setMediaItem(MediaItem2 item);
ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata2 metadata);
MediaItem2 getCurrentMediaItem();
- ListenableFuture<PlayerResult> skipToPlaylistItem(MediaItem2 item);
+ ListenableFuture<PlayerResult> skipToPlaylistItem(int index);
ListenableFuture<PlayerResult> skipToPreviousItem();
ListenableFuture<PlayerResult> skipToNextItem();
ListenableFuture<PlayerResult> addPlaylistItem(int index, MediaItem2 item);
- ListenableFuture<PlayerResult> removePlaylistItem(MediaItem2 item);
+ ListenableFuture<PlayerResult> removePlaylistItem(int index);
ListenableFuture<PlayerResult> replacePlaylistItem(int index, MediaItem2 item);
int getRepeatMode();
@@ -69,9 +68,6 @@
}
// Common interface for session2 and controller2
- // TODO: consider to add fastForward, rewind.
interface SessionPlayer extends SessionPlaybackControl, SessionPlaylistControl {
- ListenableFuture<PlayerResult> skipForward();
- ListenableFuture<PlayerResult> skipBackward();
}
}
diff --git a/media2/src/main/java/androidx/media2/MediaItem2.java b/media2/src/main/java/androidx/media2/MediaItem2.java
index 289b320..a65db4f 100644
--- a/media2/src/main/java/androidx/media2/MediaItem2.java
+++ b/media2/src/main/java/androidx/media2/MediaItem2.java
@@ -16,23 +16,25 @@
package androidx.media2;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.os.Bundle;
-import android.os.ParcelUuid;
import android.text.TextUtils;
-import androidx.annotation.IntDef;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.core.util.Pair;
+import androidx.versionedparcelable.CustomVersionedParcelable;
+import androidx.versionedparcelable.NonParcelField;
import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
import androidx.versionedparcelable.VersionedParcelize;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.UUID;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* A class with information on a single media item with the metadata information. Here are use
@@ -48,35 +50,21 @@
* When it's shared across the processes, we cannot guarantee that they contain the right values
* because media items are application dependent especially for the metadata.
* <p>
- * When its subclass is sent between {@link MediaSession2}/{@link MediaController2} or
+ * When an object of the {@link MediaItem2}'s subclass is sent across the process between
+ * {@link MediaSession2}/{@link MediaController2} or
* {@link androidx.media2.MediaLibraryService2.MediaLibrarySession}/{@link MediaBrowser2}, the
- * subclass' data will be anonymized. The recipient need to translate if it's interested in playback
- * with it. See {@link MediaSession2.SessionCallback#onCreateMediaItem} for the detail.
+ * object will sent as if it's {@link MediaItem2}. The recipient cannot get the object with the
+ * subclasses' type. This will sanitize process specific information (e.g.
+ * {@link java.io.FileDescriptor}, {@link android.content.Context}, etc).
* <p>
* This object isn't a thread safe.
*/
-@VersionedParcelize
-public class MediaItem2 implements VersionedParcelable {
+@VersionedParcelize(isCustom = true)
+public class MediaItem2 extends CustomVersionedParcelable {
// intentionally less than long.MAX_VALUE.
// Declare this first to avoid 'illegal forward reference'.
static final long LONG_MAX = 0x7ffffffffffffffL;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP)
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
- public @interface Flags { }
-
- /**
- * Flag: Indicates that the item has children of its own.
- */
- public static final int FLAG_BROWSABLE = 1 << 0;
-
- /**
- * Flag: Indicates that the item is playable.
- */
- public static final int FLAG_PLAYABLE = 1 << 1;
-
/**
* Used when a position is unknown.
*
@@ -84,25 +72,20 @@
*/
public static final long POSITION_UNKNOWN = LONG_MAX;
- private static final String KEY_ID = "android.media.mediaitem2.id";
- private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
- private static final String KEY_UUID = "android.media.mediaitem2.uuid";
@ParcelField(1)
- String mMediaId;
- @ParcelField(2)
- int mFlags;
- @ParcelField(3)
- ParcelUuid mParcelUuid;
- @ParcelField(4)
MediaMetadata2 mMetadata;
- @ParcelField(5)
+ @ParcelField(2)
long mStartPositionMs = 0;
- @ParcelField(6)
+ @ParcelField(3)
long mEndPositionMs = POSITION_UNKNOWN;
- @ParcelField(7)
- long mDurationMs = SessionPlayer2.UNKNOWN_TIME;
+
+ @NonParcelField
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ @NonParcelField
+ private final List<Pair<OnMetadataChangedListener, Executor>> mListeners = new ArrayList<>();
/**
* Used for VersionedParcelable
@@ -117,50 +100,31 @@
// MediaItem2.
@SuppressWarnings("WeakerAccess") /* synthetic access */
MediaItem2(BuilderBase builder) {
- this(builder.mUuid, builder.mMediaId, builder.mFlags, builder.mMetadata,
- builder.mStartPositionMs, builder.mEndPositionMs, builder.mDurationMs);
+ this(builder.mMetadata, builder.mStartPositionMs, builder.mEndPositionMs);
+ }
+
+ MediaItem2(MediaItem2 item) {
+ this(item.mMetadata, item.mStartPositionMs, item.mEndPositionMs);
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- MediaItem2(@Nullable UUID uuid, @Nullable String mediaId, Integer flags,
- @Nullable MediaMetadata2 metadata, long startPositionMs, long endPositionMs,
- long durationMs) {
+ MediaItem2(@Nullable MediaMetadata2 metadata, long startPositionMs, long endPositionMs) {
if (startPositionMs > endPositionMs) {
throw new IllegalStateException("Illegal start/end position: "
+ startPositionMs + " : " + endPositionMs);
}
- if (durationMs != SessionPlayer2.UNKNOWN_TIME && endPositionMs != POSITION_UNKNOWN
- && endPositionMs > durationMs) {
- throw new IllegalStateException("endPositionMs shouldn't be greater than durationMs: "
- + " endPositionMs=" + endPositionMs + ", durationMs=" + durationMs);
- }
-
- mParcelUuid = new ParcelUuid((uuid != null) ? uuid : UUID.randomUUID());
- if (mediaId != null || durationMs > 0 || flags != null) {
- MediaMetadata2.Builder builder = metadata != null
- ? new MediaMetadata2.Builder(metadata) : new MediaMetadata2.Builder();
- if (mediaId != null) {
- builder.putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId);
+ if (metadata != null && metadata.containsKey(MediaMetadata2.METADATA_KEY_DURATION)) {
+ long durationMs = metadata.getLong(MediaMetadata2.METADATA_KEY_DURATION);
+ if (durationMs != SessionPlayer2.UNKNOWN_TIME && endPositionMs != POSITION_UNKNOWN
+ && endPositionMs > durationMs) {
+ throw new IllegalStateException("endPositionMs shouldn't be greater than"
+ + " duration in the metdata, endPositionMs=" + endPositionMs
+ + ", durationMs=" + durationMs);
}
- if (durationMs > 0) {
- builder.putLong(MediaMetadata2.METADATA_KEY_DURATION, durationMs);
- }
- if (flags != null) {
- builder.putLong(MediaMetadata2.METADATA_KEY_FLAGS, flags);
- }
- metadata = builder.build();
}
- if (metadata != null) {
- mediaId = metadata.getString(MediaMetadata2.METADATA_KEY_MEDIA_ID);
- durationMs = metadata.getLong(MediaMetadata2.METADATA_KEY_DURATION);
- flags = (int) metadata.getLong(MediaMetadata2.METADATA_KEY_FLAGS);
- }
- mMediaId = mediaId;
- mFlags = flags != null ? flags : 0;
mMetadata = metadata;
mStartPositionMs = startPositionMs;
mEndPositionMs = endPositionMs;
- mDurationMs = durationMs;
}
/**
@@ -172,12 +136,9 @@
// TODO(jaewan): Remove
public @NonNull Bundle toBundle() {
Bundle bundle = new Bundle();
- bundle.putString(KEY_ID, mMediaId);
- bundle.putInt(KEY_FLAGS, mFlags);
if (mMetadata != null) {
bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
}
- bundle.putParcelable(KEY_UUID, mParcelUuid);
return bundle;
}
@@ -193,38 +154,16 @@
if (bundle == null) {
return null;
}
- final ParcelUuid parcelUuid = bundle.getParcelable(KEY_UUID);
- return fromBundle(bundle, parcelUuid);
- }
-
- /**
- * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID} string.
- * <p>
- * {@link UUID} string can be null if it want to generate new one.
- *
- * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
- * @param parcelUuid A {@link ParcelUuid} string to override. Can be {@link null} for override.
- * @return The newly created MediaItem2
- */
- static MediaItem2 fromBundle(@NonNull Bundle bundle, @Nullable ParcelUuid parcelUuid) {
- if (bundle == null) {
- return null;
- }
- final UUID uuid = (parcelUuid != null) ? parcelUuid.getUuid() : null;
- final String id = bundle.getString(KEY_ID);
final Bundle metadataBundle = bundle.getBundle(KEY_METADATA);
final MediaMetadata2 metadata = metadataBundle != null
? MediaMetadata2.fromBundle(metadataBundle) : null;
- final int flags = bundle.getInt(KEY_FLAGS);
- return new MediaItem2(uuid, id, flags, metadata, 0, 0, 0);
+ return new MediaItem2(metadata, 0, 0);
}
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder("MediaItem2{");
- sb.append("mMediaId=").append(mMediaId);
- sb.append(", mFlags=").append(mFlags);
- sb.append(", mMetadata=").append(mMetadata);
+ final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
+ sb.append("{mMetadata=").append(mMetadata);
sb.append(", mStartPositionMs=").append(mStartPositionMs);
sb.append(", mEndPositionMs=").append(mEndPositionMs);
sb.append('}');
@@ -232,41 +171,30 @@
}
/**
- * Gets the flags of the item.
- */
- public @Flags int getFlags() {
- return mFlags;
- }
-
- /**
- * Checks whether this item is browsable.
- * @see #FLAG_BROWSABLE
- */
- public boolean isBrowsable() {
- return (mFlags & FLAG_BROWSABLE) != 0;
- }
-
- /**
- * Checks whether this item is playable.
- * @see #FLAG_PLAYABLE
- */
- public boolean isPlayable() {
- return (mFlags & FLAG_PLAYABLE) != 0;
- }
-
- /**
- * Sets a metadata. If the metadata is not {@code null}, its id should be matched with this
+ * Sets metadata. If the metadata is not {@code null}, its id should be matched with this
* instance's media id.
*
* @param metadata metadata to update
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
*/
public void setMetadata(@Nullable MediaMetadata2 metadata) {
- if (metadata != null && !TextUtils.equals(mMediaId, metadata.getMediaId())) {
+ if (metadata != null && !TextUtils.equals(getMediaId(), metadata.getMediaId())) {
throw new IllegalArgumentException("metadata's id should be matched with the mediaId");
}
mMetadata = metadata;
- if (metadata != null) {
- mDurationMs = metadata.getLong(MediaMetadata2.METADATA_KEY_DURATION);
+ List<Pair<OnMetadataChangedListener, Executor>> listeners = new ArrayList<>();
+ synchronized (mLock) {
+ listeners.addAll(mListeners);
+ }
+
+ for (Pair<OnMetadataChangedListener, Executor> pair : listeners) {
+ final OnMetadataChangedListener listener = pair.first;
+ pair.second.execute(new Runnable() {
+ @Override
+ public void run() {
+ listener.onMetadataChanged(MediaItem2.this);
+ }
+ });
}
}
@@ -301,27 +229,34 @@
* for the underlying media content.
*
* @return media Id from the session
+ * @hide
*/
+ // TODO: Remove
+ @RestrictTo(LIBRARY)
public @Nullable String getMediaId() {
- return mMediaId;
+ return mMetadata != null ? mMetadata.getString(MediaMetadata2.METADATA_KEY_MEDIA_ID) : null;
}
- @Override
- public int hashCode() {
- return mParcelUuid.hashCode();
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (!(obj instanceof MediaItem2)) {
- return false;
+ void addOnMetadataChangedListener(Executor executor, OnMetadataChangedListener listener) {
+ synchronized (mLock) {
+ for (Pair<OnMetadataChangedListener, Executor> pair : mListeners) {
+ if (pair.first == listener) {
+ return;
+ }
+ }
+ mListeners.add(new Pair<>(listener, executor));
}
- MediaItem2 other = (MediaItem2) obj;
- return mParcelUuid.equals(other.mParcelUuid);
}
- UUID getUuid() {
- return mParcelUuid.getUuid();
+ void removeOnMetadataChangedListener(OnMetadataChangedListener listener) {
+ synchronized (mLock) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ if (mListeners.get(i).first == listener) {
+ mListeners.remove(i);
+ return;
+ }
+ }
+ }
}
/**
@@ -333,55 +268,14 @@
@RestrictTo(LIBRARY_GROUP)
public static class BuilderBase<T extends BuilderBase> {
@SuppressWarnings("WeakerAccess") /* synthetic access */
- @Flags int mFlags;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- String mMediaId;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
MediaMetadata2 mMetadata;
@SuppressWarnings("WeakerAccess") /* synthetic access */
- UUID mUuid;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
long mStartPositionMs = 0;
@SuppressWarnings("WeakerAccess") /* synthetic access */
long mEndPositionMs = POSITION_UNKNOWN;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- long mDurationMs = SessionPlayer2.UNKNOWN_TIME;
-
- /**
- * Constructs a new {@link T}.
- *
- * @param flags flags whether it's playable and/or browsable.
- * @see #FLAG_BROWSABLE
- * @see #FLAG_PLAYABLE
- */
- public BuilderBase(@Flags int flags) {
- mFlags = flags;
- }
-
- /**
- * Set the media id of this instance. {@code null} for unset.
- * <p>
- * If used, this should be a persistent unique key for the underlying content so session
- * and controller can uniquely identify a media content.
- * <p>
- * If the metadata is set with the {@link #setMetadata(MediaMetadata2)} and it has
- * media id, id from {@link #setMediaId(String)} will be ignored and metadata's id will be
- * used instead.
- *
- * @param mediaId media id
- * @return this instance for chaining
- */
- public @NonNull T setMediaId(@Nullable String mediaId) {
- mMediaId = mediaId;
- return (T) this;
- }
/**
* Set the metadata of this instance. {@code null} for unset.
- * <p>
- * If the metadata is set with the {@link #setMetadata(MediaMetadata2)} and it has
- * media id, id from {@link #setMediaId(String)} will be ignored and metadata's id will be
- * used instead.
*
* @param metadata metadata
* @return this instance for chaining
@@ -421,11 +315,6 @@
return (T) this;
}
- T setUuid(UUID uuid) {
- mUuid = uuid;
- return (T) this;
- }
-
/**
* Build {@link MediaItem2}.
*
@@ -440,8 +329,29 @@
* Builder for {@link MediaItem2}.
*/
public static class Builder extends BuilderBase<BuilderBase> {
- public Builder(@Flags int flags) {
- super(flags);
+ /**
+ * Default constructor
+ */
+ public Builder() {
+ super();
}
}
+
+ interface OnMetadataChangedListener {
+ void onMetadataChanged(MediaItem2 item);
+ }
+
+ /**
+ * @hide
+ * @param isStream
+ */
+ @RestrictTo(LIBRARY)
+ @Override
+ public void onPreParceling(boolean isStream) {
+ if (getClass() != MediaItem2.class) {
+ throw new RuntimeException("MediaItem2's subclasses shouldn't be parcelized."
+ + " Use instead");
+ }
+ super.onPreParceling(isStream);
+ }
}
diff --git a/media2/src/main/java/androidx/media2/MediaLibraryService2.java b/media2/src/main/java/androidx/media2/MediaLibraryService2.java
index ad4da59..350e48a 100644
--- a/media2/src/main/java/androidx/media2/MediaLibraryService2.java
+++ b/media2/src/main/java/androidx/media2/MediaLibraryService2.java
@@ -83,6 +83,11 @@
public static final class MediaLibrarySession extends MediaSession2 {
/**
* Callback for the {@link MediaLibrarySession}.
+ * <p>
+ * When you return {@link LibraryResult} with media items,
+ * items must have valid {@link MediaMetadata2#METADATA_KEY_MEDIA_ID} and
+ * specify {@link MediaMetadata2#METADATA_KEY_BROWSABLE} and
+ * {@link MediaMetadata2#METADATA_KEY_PLAYABLE}.
*/
public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
/**
@@ -120,7 +125,7 @@
*
* @param session the session for this event
* @param controller controller
- * @param mediaId item id to get media item.
+ * @param mediaId non-empty media id of the requested item
* @return a library result with a media item with the id. A runtime exception
* will be thrown if an invalid result is returned.
* @see SessionCommand2#COMMAND_CODE_LIBRARY_GET_ITEM
@@ -139,7 +144,7 @@
*
* @param session the session for this event
* @param controller controller
- * @param parentId parent id to get children
+ * @param parentId non-empty parent id to get children
* @param page page number. Starts from {@code 0}.
* @param pageSize page size. Should be greater or equal to {@code 1}.
* @param params library params
@@ -149,8 +154,9 @@
* @see LibraryParams
*/
public @NonNull LibraryResult onGetChildren(@NonNull MediaLibrarySession session,
- @NonNull ControllerInfo controller, @NonNull String parentId, int page,
- int pageSize, @Nullable LibraryParams params) {
+ @NonNull ControllerInfo controller, @NonNull String parentId,
+ @IntRange(from = 0) int page, @IntRange(from = 1) int pageSize,
+ @Nullable LibraryParams params) {
return new LibraryResult(RESULT_CODE_NOT_SUPPORTED);
}
@@ -168,7 +174,7 @@
*
* @param session the session for this event
* @param controller controller
- * @param parentId parent id
+ * @param parentId non-empty parent id
* @param params library params
* @return result code
* @see SessionCommand2#COMMAND_CODE_LIBRARY_SUBSCRIBE
@@ -189,7 +195,7 @@
*
* @param session the session for this event
* @param controller controller
- * @param parentId parent id
+ * @param parentId non-empty parent id
* @return result code
* @see SessionCommand2#COMMAND_CODE_LIBRARY_UNSUBSCRIBE
*/
@@ -208,8 +214,8 @@
*
* @param session the session for this event
* @param controller controller
- * @param query The search query sent from the media browser. It contains keywords
- * separated by space.
+ * @param query The non-empty search query sent from the media browser.
+ * It contains keywords separated by space.
* @param params library params
* @return result code
* @see SessionCommand2#COMMAND_CODE_LIBRARY_SEARCH
@@ -234,7 +240,8 @@
*
* @param session the session for this event
* @param controller controller
- * @param query The search query which was previously sent through {@link #onSearch}.
+ * @param query The non-empty search query which was previously sent through
+ * {@link #onSearch}.
* @param page page number. Starts from {@code 0}.
* @param pageSize page size. Should be greater or equal to {@code 1}.
* @param params library params
@@ -245,7 +252,8 @@
*/
public @NonNull LibraryResult onGetSearchResult(
@NonNull MediaLibrarySession session, @NonNull ControllerInfo controller,
- @NonNull String query, int page, int pageSize, @Nullable LibraryParams params) {
+ @NonNull String query, @IntRange(from = 0) int page,
+ @IntRange(from = 1) int pageSize, @Nullable LibraryParams params) {
return new LibraryResult(RESULT_CODE_NOT_SUPPORTED);
}
}
@@ -322,7 +330,7 @@
* to get the list of children.
*
* @param controller controller to notify
- * @param parentId parent id with changes in its children
+ * @param parentId non-empty parent id with changes in its children
* @param itemCount number of children.
* @param params library params
*/
@@ -345,7 +353,7 @@
* Notify all controllers that subscribed to the parent about change in the parent's
* children, regardless of the library params supplied by
* {@link MediaBrowser2#subscribe(String, LibraryParams)}.
- * @param parentId parent id
+ * @param parentId non-empty parent id
* @param itemCount number of children
* @param params library params
*/
@@ -365,7 +373,7 @@
* Notify controller about change in the search result.
*
* @param controller controller to notify
- * @param query previously sent search query from the controller.
+ * @param query previously sent non-empty search query from the controller.
* @param itemCount the number of items that have been found in the search.
* @param params library params
*/
@@ -573,7 +581,7 @@
* @param recent {@code true} for recent items. {@code false} otherwise.
* @return this builder
*/
- public Builder setRecent(boolean recent) {
+ public @NonNull Builder setRecent(boolean recent) {
mRecent = recent;
return this;
}
@@ -588,7 +596,7 @@
* @param offline {@code true} for offline items. {@code false} otherwise.
* @return this builder
*/
- public Builder setOffline(boolean offline) {
+ public @NonNull Builder setOffline(boolean offline) {
mOffline = offline;
return this;
}
@@ -604,7 +612,7 @@
* @param suggested {@code true} for suggested items. {@code false} otherwise
* @return this builder
*/
- public Builder setSuggested(boolean suggested) {
+ public @NonNull Builder setSuggested(boolean suggested) {
mSuggested = suggested;
return this;
}
@@ -615,7 +623,7 @@
* @param extras The extras or null.
* @return this builder
*/
- public Builder setExtras(@Nullable Bundle extras) {
+ public @NonNull Builder setExtras(@Nullable Bundle extras) {
mBundle = extras;
return this;
}
@@ -625,7 +633,7 @@
*
* @return new LibraryParams
*/
- public LibraryParams build() {
+ public @NonNull LibraryParams build() {
return new LibraryParams(mBundle, mRecent, mOffline, mSuggested);
}
}
diff --git a/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java b/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java
index 6e7f24c..a6adcc3 100644
--- a/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java
+++ b/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java
@@ -81,6 +81,9 @@
return null;
}
final ControllerInfo controller = getCurrentController();
+ if (controller == null) {
+ return null;
+ }
if (getConnectedControllersManager().isAllowedCommand(controller,
SessionCommand2.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT)) {
// Call callbacks directly instead of execute on the executor. Here's the reason.
@@ -533,7 +536,7 @@
for (int i = 0; i < searchRequests.size(); i++) {
SearchRequest request = searchRequests.get(i);
int page = 0;
- int pageSize = 0;
+ int pageSize = Integer.MAX_VALUE;
if (request.mExtras != null) {
try {
request.mExtras.setClassLoader(
diff --git a/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java b/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java
index 3bf7f2b..97fae38 100644
--- a/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.os.RemoteException;
import android.support.v4.media.session.MediaSessionCompat.Token;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.GuardedBy;
@@ -154,13 +155,15 @@
int pageSize) {
returnedResult = ensureNonNullResult(returnedResult);
if (returnedResult.getResultCode() == RESULT_CODE_SUCCESS) {
- if (returnedResult.getMediaItems() == null) {
+ List<MediaItem2> items = returnedResult.getMediaItems();
+
+ if (items == null) {
if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
throw new RuntimeException("List shouldn't be null for the success");
}
return new LibraryResult(RESULT_CODE_UNKNOWN_ERROR);
}
- if (returnedResult.getMediaItems().size() > pageSize) {
+ if (items.size() > pageSize) {
if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
throw new RuntimeException("List shouldn't contain items more than pageSize"
+ ", size=" + returnedResult.getMediaItems().size()
@@ -168,6 +171,11 @@
}
return new LibraryResult(RESULT_CODE_UNKNOWN_ERROR);
}
+ for (MediaItem2 item : items) {
+ if (!isValidItem(item)) {
+ return new LibraryResult(RESULT_CODE_UNKNOWN_ERROR);
+ }
+ }
}
return returnedResult;
}
@@ -175,15 +183,52 @@
private LibraryResult ensureNonNullResultWithValidItem(LibraryResult returnedResult) {
returnedResult = ensureNonNullResult(returnedResult);
if (returnedResult.getResultCode() == RESULT_CODE_SUCCESS) {
- if (returnedResult.getMediaItem() == null) {
- if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
- throw new RuntimeException("Item shouldn't be null for the success");
- }
+ if (!isValidItem(returnedResult.getMediaItem())) {
return new LibraryResult(RESULT_CODE_UNKNOWN_ERROR);
}
}
return returnedResult;
}
+
+ private boolean isValidItem(MediaItem2 item) {
+ if (item == null) {
+ if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
+ throw new RuntimeException("Item shouldn't be null for the success");
+ }
+ return false;
+ }
+ if (TextUtils.isEmpty(item.getMediaId())) {
+ if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
+ throw new RuntimeException(
+ "Media ID of an item shouldn't be empty for the success");
+ }
+ return false;
+ }
+ MediaMetadata2 metadata = item.getMetadata();
+ if (metadata == null) {
+ if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
+ throw new RuntimeException(
+ "Metadata of an item shouldn't be null for the success");
+ }
+ return false;
+ }
+ if (!metadata.containsKey(MediaMetadata2.METADATA_KEY_BROWSABLE)) {
+ if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
+ throw new RuntimeException(
+ "METADATA_KEY_BROWSABLE should be specified in metadata of an item");
+ }
+ return false;
+ }
+ if (!metadata.containsKey(MediaMetadata2.METADATA_KEY_PLAYABLE)) {
+ if (THROW_EXCEPTION_FOR_INVALID_RETURN) {
+ throw new RuntimeException(
+ "METADATA_KEY_PLAYABLE should be specified in metadata of an item");
+ }
+ return false;
+ }
+ return true;
+ }
+
/**
* Called by {@link MediaSession2Stub#getLibraryRoot(IMediaController2, int, ParcelImpl)}.
*
diff --git a/media2/src/main/java/androidx/media2/MediaMetadata2.java b/media2/src/main/java/androidx/media2/MediaMetadata2.java
index 68303d4..49f0a45 100644
--- a/media2/src/main/java/androidx/media2/MediaMetadata2.java
+++ b/media2/src/main/java/androidx/media2/MediaMetadata2.java
@@ -16,7 +16,6 @@
package androidx.media2;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.graphics.Bitmap;
@@ -29,6 +28,7 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.StringDef;
import androidx.collection.ArrayMap;
+import androidx.media2.MediaLibraryService2.MediaLibrarySession;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelable;
@@ -39,12 +39,77 @@
import java.util.Set;
/**
- * Contains metadata about an item, such as the title, artist, etc.
+ * Contains metadata about an item, such as the title, artist, etc. This is optional, but you'd
+ * better to provide this as much as possible when you're using media widget and/or session APIs.
+ * <p>
+ * The media widget components build its UI based on the metadata here. For an example,
+ * {@link androidx.media.widget.MediaControlView2} will show title from the metadata.
+ * <p>
+ * The {@link MediaLibrarySession} would require some metadata values when it provides
+ * {@link MediaItem2}s to {@link MediaBrowser2}.
+ * <p>
+ * Topics covered here:
+ * <ol>
+ * <li><a href="#MediaId">Media ID</a>
+ * <li><a href="#Browsable">Browsable type</a>
+ * <li><a href="#Playable">Playable</a>
+ * <li><a href="#Duration">Duration</a>
+ * <li><a href="#UserRating">User rating</a>
+ * </ol>
+ * <a name="MediaId"></a>
+ * <h3>{@link MediaMetadata2#METADATA_KEY_MEDIA_ID Media ID}</h3>
+ * <p>
+ * If set, the media ID must be the persistent key for the underlying media contents, so
+ * {@link MediaController2} and {@link MediaBrowser2} can store the information and reuse it later.
+ * Some APIs requires a media ID (e.g. {@link MediaController2#setRating}, so you'd better specify
+ * one.
+ * <p>
+ * Typical example of using media ID is the URI of the contents, but use it with the caution because
+ * the metadata is shared across the process in plain text.
+ * <p>
+ * The {@link MediaLibrarySession} would require it for the library root, so {@link MediaBrowser2}
+ * can call subsequent {@link MediaBrowser2#getChildren} with the ID.
+ * <p>
+ * <a name="Browsable"></a>
+ * <h3>{@link MediaMetadata2#METADATA_KEY_BROWSABLE Browsable type}</h3>
+ * <p>
+ * Browsable defines whether the media item has children and type of children if any. With this,
+ * {@link MediaBrowser2} can know whether the subsequent {@link MediaBrowser2#getChildren} would
+ * successfuly run.
+ * <p>
+ * The {@link MediaLibrarySession} would require the explicit browsable type for the the media items
+ * returned by the
+ * {@link androidx.media2.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback}.
+ * <p>
+ * <a name="Playable"></a>
+ * <h3>{@link MediaMetadata2#METADATA_KEY_PLAYABLE Playable type}</h3>
+ * <p>
+ * Playable defines whether the media item can be played or not. It may be possible for a playlist
+ * to contain a media item which isn't playable in order to show a disabled media item.
+ * <p>
+ * The {@link MediaLibrarySession} would require the explicit playable value for the the media items
+ * returned by the
+ * {@link androidx.media2.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback}.
+ * <p>
+ * <a name="Duration"></a>
+ * <li><a href="#Duration">{@link MediaMetadata2#METADATA_KEY_DURATION Duration}</a>
+ * The duration is the length of the contents. The {@link MediaController2} can only get the
+ * duration through the metadata. This tells when would the playback ends, and also tells about the
+ * allowed range of {@link MediaController2#seekTo(long)}.
+ * <p>
+ * If it's not set by developer, {@link MediaSession2} would update the duration in the metadata
+ * with the {@link SessionPlayer2#getDuration()}.
+ * <p>
+ * <a name="UserRating"></a>
+ * <li><a href="#UserRating">{@link MediaMetadata2#METADATA_KEY_USER_RATING User rating}</a>
+ * <p>
+ * Prefer to have unrated user rating instead of {@code null}, so {@link MediaController2} can know
+ * the possible user rating type for calling {@link MediaController2#setRating(String, Rating2)}.
*/
// New version of MediaMetadata with following changes
// - Don't implement Parcelable for updatable support.
// - Also support MediaDescription features. MediaDescription is deprecated instead because
-// it was insufficient for controller to display media contents.
+// it was insufficient for controller to display media contents. (e.g. duration is missing)
@VersionedParcelize
public final class MediaMetadata2 implements VersionedParcelable {
private static final String TAG = "MediaMetadata2";
@@ -252,10 +317,12 @@
/**
* The metadata key for a {@link Rating2} typed value to retrieve the information about the
- * user's rating for the media.
+ * user's rating for the media. Prefer to have unrated user rating instead of {@code null}, so
+ * {@link MediaController2} can know the possible user rating type.
*
* @see Builder#putRating(String, Rating2)
* @see #getRating(String)
+ * @see <a href="#UserRating">User rating</a>
*/
public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
@@ -340,14 +407,14 @@
/**
* The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the
* information about the media ID of the content. This value is specific to the
- * service providing the content. If used, this should be a persistent
- * unique key for the underlying content. This ID is used by {@link MediaController2} and
- * {@link MediaBrowser2}.
+ * service providing the content. If used, this should be a persistent key for the underlying
+ * content. This ID is used by {@link MediaController2} and {@link MediaBrowser2}.
*
* @see Builder#putText(String, CharSequence)
* @see Builder#putString(String, String)
* @see #getText(String)
* @see #getString(String)
+ * @see <a href="#MediaID">Media ID</a>
*/
public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
@@ -390,66 +457,102 @@
"android.media.metadata.RADIO_PROGRAM_NAME";
/**
- * The metadata key for a {@link Long} typed value to retrieve the information about the
- * bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth
- * AVRCP 1.5. It should be one of the following:
+ * The metadata key for a {@link Long} typed value to retrieve the information about the type
+ * of browsable. It should be one of the following:
* <ul>
- * <li>{@link #BT_FOLDER_TYPE_MIXED}</li>
- * <li>{@link #BT_FOLDER_TYPE_TITLES}</li>
- * <li>{@link #BT_FOLDER_TYPE_ALBUMS}</li>
- * <li>{@link #BT_FOLDER_TYPE_ARTISTS}</li>
- * <li>{@link #BT_FOLDER_TYPE_GENRES}</li>
- * <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}</li>
- * <li>{@link #BT_FOLDER_TYPE_YEARS}</li>
+ * <li>{@link #BROWSABLE_TYPE_NONE}</li>
+ * <li>{@link #BROWSABLE_TYPE_MIXED}</li>
+ * <li>{@link #BROWSABLE_TYPE_TITLES}</li>
+ * <li>{@link #BROWSABLE_TYPE_ALBUMS}</li>
+ * <li>{@link #BROWSABLE_TYPE_ARTISTS}</li>
+ * <li>{@link #BROWSABLE_TYPE_GENRES}</li>
+ * <li>{@link #BROWSABLE_TYPE_PLAYLISTS}</li>
+ * <li>{@link #BROWSABLE_TYPE_YEARS}</li>
* </ul>
+ * <p>
+ * The values other than {@link #BROWSABLE_TYPE_NONE} mean that the media item has children.[
+ * <p>
+ * This matches with the bluetooth folder type of the media specified in the section 6.10.2.2 of
+ * the Bluetooth AVRCP 1.5.
*
* @see Builder#putLong(String, long)
* @see #getLong(String)
+ * @see <a href="#Browsable">Browsable</a>
*/
- public static final String METADATA_KEY_BT_FOLDER_TYPE =
+ public static final String METADATA_KEY_BROWSABLE =
"android.media.metadata.BT_FOLDER_TYPE";
/**
- * The type of folder that is unknown or contains media elements of mixed types as specified in
- * the section 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ * The type of browsable for non-browsable media item.
*/
- public static final long BT_FOLDER_TYPE_MIXED = 0;
+ public static final long BROWSABLE_TYPE_NONE = -1;
/**
- * The type of folder that contains media elements only as specified in the section 6.10.2.2 of
+ * The type of browsable that is unknown or contains media items of mixed types.
+ * <p>
+ * This value matches with the folder type 'Mixed' as specified in the section 6.10.2.2 of the
+ * Bluetooth AVRCP 1.5.
+ */
+ public static final long BROWSABLE_TYPE_MIXED = 0;
+
+ /**
+ * The type of browsable that only contains playable media items.
+ * <p>
+ * This value matches with the folder type 'Titles' as specified in the section 6.10.2.2 of the
+ * Bluetooth AVRCP 1.5.
+ */
+ public static final long BROWSABLE_TYPE_TITLES = 1;
+
+ /**
+ * The type of browsable that contains browsable items categorized by album.
+ * <p>
+ * This value matches with the folder type 'Albums' as specified in the section 6.10.2.2 of the
+ * Bluetooth AVRCP 1.5.
+ */
+ public static final long BROWSABLE_TYPE_ALBUMS = 2;
+
+ /**
+ * The type of browsable that contains browsable items categorized by artist.
+ * <p>
+ * This value matches with the folder type 'Artists' as specified in the section 6.10.2.2 of the
+ * Bluetooth AVRCP 1.5.
+ */
+ public static final long BROWSABLE_TYPE_ARTISTS = 3;
+
+ /**
+ * The type of browsable that contains browsable items categorized by genre.
+ * <p>
+ * This value matches with the folder type 'Genres' as specified in the section 6.10.2.2 of the
+ * Bluetooth AVRCP 1.5.
+ */
+ public static final long BROWSABLE_TYPE_GENRES = 4;
+
+ /**
+ * The type of browsable that contains browsable items categorized by playlist.
+ * <p>
+ * This value matches with the folder type 'Playlists' as specified in the section 6.10.2.2 of
* the Bluetooth AVRCP 1.5.
*/
- public static final long BT_FOLDER_TYPE_TITLES = 1;
+ public static final long BROWSABLE_TYPE_PLAYLISTS = 5;
/**
- * The type of folder that contains folders categorized by album as specified in the section
- * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ * The type of browsable that contains browsable items categorized by year.
+ * <p>
+ * This value matches with the folder type 'Years' as specified in the section 6.10.2.2 of the
+ * Bluetooth AVRCP 1.5.
*/
- public static final long BT_FOLDER_TYPE_ALBUMS = 2;
+ public static final long BROWSABLE_TYPE_YEARS = 6;
/**
- * The type of folder that contains folders categorized by artist as specified in the section
- * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ * The metadata key for a {@link Long} typed value to retrieve the information about whether
+ * the media is playable. A value of 0 indicates it is not a playable item.
+ * A value of 1 or non-zero indicates it is playable.
+ *
+ * @see Builder#putLong(String, long)
+ * @see #getLong(String)
+ * @see <a href="#Playable">Playable</a>
*/
- public static final long BT_FOLDER_TYPE_ARTISTS = 3;
-
- /**
- * The type of folder that contains folders categorized by genre as specified in the section
- * 6.10.2.2 of the Bluetooth AVRCP 1.5.
- */
- public static final long BT_FOLDER_TYPE_GENRES = 4;
-
- /**
- * The type of folder that contains folders categorized by playlist as specified in the section
- * 6.10.2.2 of the Bluetooth AVRCP 1.5.
- */
- public static final long BT_FOLDER_TYPE_PLAYLISTS = 5;
-
- /**
- * The type of folder that contains folders categorized by year as specified in the section
- * 6.10.2.2 of the Bluetooth AVRCP 1.5.
- */
- public static final long BT_FOLDER_TYPE_YEARS = 6;
+ public static final String METADATA_KEY_PLAYABLE = "android.media.metadata.playable";
/**
* The metadata key for a {@link Long} typed value to retrieve the information about whether
@@ -508,29 +611,6 @@
/**
* @hide
*/
- // TODO(jaewan): Unhide this, and revisit documentation of putLong()
- @RestrictTo(LIBRARY)
- public static final String METADATA_KEY_FLAGS = "android.media.metadata.FLAGS";
-
- /**
- * Flag: Indicates that the item has children of its own.
- * @hide
- */
- // TODO(jaewan): Unhide this
- @RestrictTo(LIBRARY)
- public static final int FLAG_BROWSABLE = 1 << 0;
-
- /**
- * Flag: Indicates that the item is playable.
- * @hide
- */
- // TODO(jaewan): Unhide this
- @RestrictTo(LIBRARY)
- public static final int FLAG_PLAYABLE = 1 << 1;
-
- /**
- * @hide
- */
@RestrictTo(LIBRARY_GROUP)
@StringDef({METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
@@ -546,8 +626,8 @@
*/
@RestrictTo(LIBRARY_GROUP)
@StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
- METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE,
- METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS, METADATA_KEY_FLAGS})
+ METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BROWSABLE,
+ METADATA_KEY_PLAYABLE, METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS})
@Retention(RetentionPolicy.SOURCE)
public @interface LongKey {}
@@ -575,11 +655,20 @@
@Retention(RetentionPolicy.SOURCE)
public @interface FloatKey {}
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @StringDef({METADATA_KEY_EXTRAS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BundleKey {}
+
static final int METADATA_TYPE_LONG = 0;
static final int METADATA_TYPE_TEXT = 1;
static final int METADATA_TYPE_BITMAP = 2;
static final int METADATA_TYPE_RATING = 3;
static final int METADATA_TYPE_FLOAT = 4;
+ static final int METADATA_TYPE_BUNDLE = 5;
static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
static {
@@ -614,10 +703,11 @@
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RADIO_FREQUENCY, METADATA_TYPE_FLOAT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RADIO_PROGRAM_NAME, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_BROWSABLE, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_PLAYABLE, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_FLAGS, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_EXTRAS, METADATA_TYPE_BUNDLE);
}
private static final @MediaMetadata2.TextKey
@@ -749,7 +839,7 @@
}
Rating2 rating = null;
try {
- rating = ParcelUtils.fromParcelable(mBundle.getParcelable(key));
+ rating = ParcelUtils.getVersionedParcelable(mBundle, key);
} catch (Exception e) {
// ignore, value was not a rating
Log.w(TAG, "Failed to retrieve a key as Rating.", e);
@@ -826,8 +916,9 @@
}
/**
- * Gets the bundle backing the metadata object. This is available to support
- * backwards compatibility. Apps should not modify the bundle directly.
+ * Gets the bundle backing the metadata object. This is available to support backwards
+ * compatibility. Apps shouldn't modify the bundle directly, nor share the metadata across the
+ * process with the bundle here.
*
* @return The Bundle backing this metadata.
*/
@@ -835,6 +926,11 @@
return mBundle;
}
+ @Override
+ public String toString() {
+ return mBundle.toString();
+ }
+
/**
* Creates the {@link MediaMetadata2} from the bundle that previously returned by
* {@link #toBundle()}.
@@ -897,6 +993,15 @@
}
/**
+ * Only for the backward compatibility.
+ *
+ * @param bundle
+ */
+ Builder(Bundle bundle) {
+ mBundle = new Bundle(bundle);
+ }
+
+ /**
* Put a CharSequence value into the metadata. Custom keys may be used,
* but if the METADATA_KEYs defined in this class are used they may only
* be one of the following:
@@ -994,7 +1099,8 @@
* <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
* <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
* <li>{@link #METADATA_KEY_YEAR}</li>
- * <li>{@link #METADATA_KEY_BT_FOLDER_TYPE}</li>
+ * <li>{@link #METADATA_KEY_BROWSABLE}</li>
+ * <li>{@link #METADATA_KEY_PLAYABLE}</li>
* <li>{@link #METADATA_KEY_ADVERTISEMENT}</li>
* <li>{@link #METADATA_KEY_DOWNLOAD_STATUS}</li>
* </ul>
@@ -1041,7 +1147,7 @@
+ " key cannot be used to put a Rating");
}
}
- mBundle.putParcelable(key, ParcelUtils.toParcelable(value));
+ ParcelUtils.putVersionedParcelable(mBundle, key, value);
return this;
}
@@ -1105,7 +1211,7 @@
* @param extras The extras to include with this description or null.
* @return The Builder to allow chaining
*/
- public Builder setExtras(@Nullable Bundle extras) {
+ public @NonNull Builder setExtras(@Nullable Bundle extras) {
mBundle.putBundle(METADATA_KEY_EXTRAS, extras);
return this;
}
diff --git a/media2/src/main/java/androidx/media2/XMediaPlayer.java b/media2/src/main/java/androidx/media2/MediaPlayer.java
similarity index 65%
rename from media2/src/main/java/androidx/media2/XMediaPlayer.java
rename to media2/src/main/java/androidx/media2/MediaPlayer.java
index 202aac7..9a3440b 100644
--- a/media2/src/main/java/androidx/media2/XMediaPlayer.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer.java
@@ -57,6 +57,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -64,7 +65,6 @@
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* A media player which plays {@link MediaItem2}s. The details on playback control and player states
@@ -77,7 +77,7 @@
* <a name="AudioFocusAndNoisyIntent"></a>
* <h3>Audio focus and noisy intent</h3>
* <p>
- * By default, {@link XMediaPlayer} handles audio focus and noisy intent with
+ * By default, {@link MediaPlayer} handles audio focus and noisy intent with
* {@link AudioAttributesCompat} set to this player. You need to call
* {@link #setAudioAttributes(AudioAttributesCompat)} set the audio attribute while in the
* {@link #PLAYER_STATE_IDLE}.
@@ -129,8 +129,8 @@
* <p>
*/
@TargetApi(Build.VERSION_CODES.P)
-public class XMediaPlayer extends SessionPlayer2 {
- private static final String TAG = "XMediaPlayer";
+public class MediaPlayer extends SessionPlayer2 {
+ private static final String TAG = "MediaPlayer";
/**
* Unspecified player error.
@@ -230,11 +230,11 @@
public static final int MEDIA_INFO_MEDIA_ITEM_REPEAT = 7;
/**
- * The player just finished prefetching a media item for playback.
+ * The player just finished preparing a media item for playback.
* @see #prepare()
* @see PlayerCallback#onInfo
*/
- public static final int MEDIA_INFO_PREFETCHED = 100;
+ public static final int MEDIA_INFO_PREPARED = 100;
/**
* The video is too complex for the decoder: it can't decode frames fast
@@ -360,7 +360,7 @@
MEDIA_INFO_MEDIA_ITEM_END,
MEDIA_INFO_MEDIA_ITEM_LIST_END,
MEDIA_INFO_MEDIA_ITEM_REPEAT,
- MEDIA_INFO_PREFETCHED,
+ MEDIA_INFO_PREPARED,
MEDIA_INFO_VIDEO_TRACK_LAGGING,
MEDIA_INFO_BUFFERING_START,
MEDIA_INFO_BUFFERING_END,
@@ -500,26 +500,110 @@
MediaPlayer2 mPlayer;
private ExecutorService mExecutor;
- /* A list for tracking the commands submitted to MediaPlayer2.*/
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final ArrayDeque<PendingCommand> mPendingCommands = new ArrayDeque<>();
- @GuardedBy("mPendingCommands")
- boolean mPreviewEnabled;
-
@SuppressWarnings("WeakerAccess") /* synthetic access */
static final class PendingCommand {
@SuppressWarnings("WeakerAccess") /* synthetic access */
- final @MediaPlayer2.CallCompleted int mCallType;
+ @MediaPlayer2.CallCompleted final int mCallType;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final ResolvableFuture mFuture;
@SuppressWarnings("WeakerAccess") /* synthetic access */
- PendingCommand(int mCallType, ResolvableFuture mFuture) {
- this.mCallType = mCallType;
- this.mFuture = mFuture;
+ PendingCommand(int callType, ResolvableFuture future) {
+ mCallType = callType;
+ mFuture = future;
}
}
+ /* A list for tracking the commands submitted to MediaPlayer2.*/
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @GuardedBy("mPendingCommands")
+ final ArrayDeque<PendingCommand> mPendingCommands = new ArrayDeque<>();
+
+ /**
+ * PendingFuture is a future for the result of execution which will be executed later via
+ * the onExecute() method.
+ */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ abstract static class PendingFuture<V extends PlayerResult>
+ extends AbstractResolvableFuture<V> {
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final boolean mIsSeekTo;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ boolean mExecuted = false;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ List<ResolvableFuture<V>> mFutures;
+
+ PendingFuture() {
+ mIsSeekTo = false;
+ }
+
+ PendingFuture(boolean isSeekTo) {
+ mIsSeekTo = isSeekTo;
+ }
+
+ @Override
+ public boolean set(@Nullable V value) {
+ return super.set(value);
+ }
+
+ @Override
+ public boolean setException(Throwable throwable) {
+ return super.setException(throwable);
+ }
+
+ public void execute() {
+ if (!mExecuted && !isCancelled()) {
+ mExecuted = true;
+ mFutures = onExecute();
+ setResultIfFinished();
+ }
+ }
+
+ public boolean setResultIfFinished() {
+ V result = null;
+ for (int i = 0; i < mFutures.size(); ++i) {
+ ResolvableFuture<V> future = mFutures.get(i);
+ if (!future.isDone() && !future.isCancelled()) {
+ return false;
+ }
+ try {
+ result = future.get();
+ int resultCode = result.getResultCode();
+ if (resultCode != RESULT_CODE_SUCCESS && resultCode != RESULT_CODE_SKIPPED) {
+ cancelFutures();
+ set(result);
+ return true;
+ }
+ } catch (Exception e) {
+ cancelFutures();
+ setException(e);
+ return true;
+ }
+ }
+ try {
+ set(result);
+ } catch (Exception e) {
+ setException(e);
+ }
+ return true;
+ }
+
+ abstract List<ResolvableFuture<V>> onExecute();
+
+ private void cancelFutures() {
+ for (ResolvableFuture<V> future : mFutures) {
+ if (!future.isCancelled() && !future.isDone()) {
+ future.cancel(true);
+ }
+ }
+ }
+ }
+
+ /* A list of pending operations within this MediaPlayer that will be executed sequentially. */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @GuardedBy("mPendingFutures")
+ final ArrayDeque<PendingFuture<? super PlayerResult>> mPendingFutures = new ArrayDeque<>();
+
private final Object mStateLock = new Object();
@GuardedBy("mStateLock")
private @PlayerState int mState;
@@ -528,27 +612,36 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
final AudioFocusHandler mAudioFocusHandler;
- private final Object mPlaylistLock = new Object();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Object mPlaylistLock = new Object();
@GuardedBy("mPlaylistLock")
- private ArrayList<MediaItem2> mPlaylist = new ArrayList<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ArrayList<MediaItem2> mPlaylist = new ArrayList<>();
@GuardedBy("mPlaylistLock")
- private ArrayList<MediaItem2> mShuffledList = new ArrayList<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ArrayList<MediaItem2> mShuffledList = new ArrayList<>();
@GuardedBy("mPlaylistLock")
- private MediaMetadata2 mPlaylistMetadata;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ MediaMetadata2 mPlaylistMetadata;
@GuardedBy("mPlaylistLock")
- private int mRepeatMode;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ int mRepeatMode;
@GuardedBy("mPlaylistLock")
- private int mShuffleMode;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ int mShuffleMode;
@GuardedBy("mPlaylistLock")
- private int mCurrentShuffleIdx;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ int mCurrentShuffleIdx;
@GuardedBy("mPlaylistLock")
- private MediaItem2 mCurPlaylistItem;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ MediaItem2 mCurPlaylistItem;
@GuardedBy("mPlaylistLock")
- private MediaItem2 mNextPlaylistItem;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ MediaItem2 mNextPlaylistItem;
@GuardedBy("mPlaylistLock")
private boolean mSetMediaItemCalled;
- public XMediaPlayer(Context context) {
+ public MediaPlayer(@NonNull Context context) {
mState = PLAYER_STATE_IDLE;
mPlayer = MediaPlayer2.create(context);
mExecutor = Executors.newFixedThreadPool(1);
@@ -559,7 +652,8 @@
}
@GuardedBy("mPendingCommands")
- private void addPendingCommandLocked(
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void addPendingCommandLocked(
int callType, final ResolvableFuture future, final Object token) {
final PendingCommand pendingCommand = new PendingCommand(callType, future);
mPendingCommands.add(pendingCommand);
@@ -578,31 +672,76 @@
}, mExecutor);
}
- @Override
- public ListenableFuture<PlayerResult> play() {
- // TODO: Make commands be executed sequentially
- if (mAudioFocusHandler.onPlayRequested()) {
- final ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.play();
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PLAY, future, token);
- }
- return future;
- } else {
- return createFutureForResultCodeInternal(RESULT_CODE_UNKNOWN_ERROR);
+ private void addPendingFuture(final PendingFuture pendingFuture) {
+ synchronized (mPendingFutures) {
+ pendingFuture.addListener(new Runnable() {
+ @Override
+ public void run() {
+ if (pendingFuture.isCancelled() && pendingFuture.mExecuted) {
+ for (Object f : pendingFuture.mFutures) {
+ ResolvableFuture future = (ResolvableFuture) f;
+ if (!future.isDone() && !future.isCancelled()) {
+ future.cancel(true);
+ }
+ }
+ pendingFuture.mFutures.clear();
+ }
+ }
+ }, mExecutor);
+ mPendingFutures.add(pendingFuture);
+ executePendingFuturesIfNeeded();
}
}
@Override
+ @NonNull
+ public ListenableFuture<PlayerResult> play() {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ // TODO: Make commands be executed sequentially
+ final ResolvableFuture<PlayerResult> future;
+ if (mAudioFocusHandler.onPlay()) {
+ if (mPlayer.getAudioAttributes() == null) {
+ futures.add(setPlayerVolumeInternal(0f));
+ }
+ future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.play();
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PLAY, future, token);
+ }
+ } else {
+ future = createFutureForResultCode(RESULT_CODE_UNKNOWN_ERROR);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
+ }
+
+ @Override
+ @NonNull
public ListenableFuture<PlayerResult> pause() {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- // TODO: Make commands be executed sequentially
- mAudioFocusHandler.onPauseRequested();
- synchronized (mPendingCommands) {
- Object token = mPlayer.pause();
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PAUSE, future, token);
- }
- return future;
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ // TODO: Make commands be executed sequentially
+ mAudioFocusHandler.onPause();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.pause();
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PAUSE, future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -612,59 +751,92 @@
* {@link PlayerResult} will be delivered when the command completes.
*/
@Override
+ @NonNull
public ListenableFuture<PlayerResult> prepare() {
- ListenableFuture ret;
- synchronized (mPendingCommands) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- Object token = mPlayer.prepare();
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PREPARE, future, token);
- if (mPreviewEnabled) {
- ResolvableFuture<PlayerResult> future2 = ResolvableFuture.create();
- Object token2 = mPlayer.pause();
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PAUSE, future2, token2);
- ret = CombindedCommandResultFuture.create(mExecutor, future, future2);
- } else {
- ret = future;
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.prepare();
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PREPARE, future, token);
+ }
+ // TODO: Changing buffering state is not correct. Think about changing MP2 event
+ // APIs for the initial buffering for prepare case.
+ setBufferingState(mPlayer.getCurrentMediaItem(),
+ BUFFERING_STATE_BUFFERING_AND_STARVED);
+ futures.add(future);
+ return futures;
}
- }
- // TODO: Changing buffering state is not correct. Think about changing MP2 event APIs for
- // the initial buffering for prepare case.
- setBufferingState(mPlayer.getCurrentMediaItem(), BUFFERING_STATE_BUFFERING_AND_STARVED);
- return ret;
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
- public ListenableFuture<PlayerResult> seekTo(long position) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.seekTo(position);
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SEEK_TO, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> seekTo(final long position) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(true) {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.seekTo(position);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SEEK_TO, future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
- public ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.setPlaybackParams(new PlaybackParams2.Builder(
- mPlayer.getPlaybackParams().getPlaybackParams())
- .setSpeed(playbackSpeed).build());
- addPendingCommandLocked(
- MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> setPlaybackSpeed(final float playbackSpeed) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.setPlaybackParams(new PlaybackParams2.Builder(
+ mPlayer.getPlaybackParams().getPlaybackParams())
+ .setSpeed(playbackSpeed).build());
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
+ @NonNull
@Override
- public ListenableFuture<PlayerResult> setAudioAttributes(AudioAttributesCompat attr) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.setAudioAttributes(attr);
- addPendingCommandLocked(
- MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, future, token);
- }
- return future;
+ public ListenableFuture<PlayerResult> setAudioAttributes(
+ @NonNull final AudioAttributesCompat attr) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.setAudioAttributes(attr);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_AUDIO_ATTRIBUTES,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
@@ -720,6 +892,7 @@
}
@Override
+ @Nullable
public AudioAttributesCompat getAudioAttributes() {
try {
return mPlayer.getAudioAttributes();
@@ -729,54 +902,80 @@
}
@Override
- public ListenableFuture<PlayerResult> setMediaItem(@NonNull MediaItem2 item) {
+ @NonNull
+ public ListenableFuture<PlayerResult> setMediaItem(@NonNull final MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- synchronized (mPlaylistLock) {
- mPlaylist.clear();
- mShuffledList.clear();
- mCurPlaylistItem = item;
- mNextPlaylistItem = null;
- mCurrentShuffleIdx = END_OF_PLAYLIST;
- }
- return setPlayerMediaItemsInternal(item, null);
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ synchronized (mPlaylistLock) {
+ mPlaylist.clear();
+ mShuffledList.clear();
+ mCurPlaylistItem = item;
+ mNextPlaylistItem = null;
+ mCurrentShuffleIdx = END_OF_PLAYLIST;
+ }
+ futures.addAll(setMediaItemsInternal(item, null));
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
+ @NonNull
@Override
public ListenableFuture<PlayerResult> setPlaylist(
@NonNull final List<MediaItem2> playlist, @Nullable final MediaMetadata2 metadata) {
if (playlist == null || playlist.isEmpty()) {
throw new IllegalArgumentException("playlist shouldn't be null or empty");
}
- MediaItem2 curItem;
- MediaItem2 nextItem;
- synchronized (mPlaylistLock) {
- mPlaylistMetadata = metadata;
- mPlaylist.clear();
- mShuffledList.clear();
- mPlaylist.addAll(playlist);
- applyShuffleModeLocked();
- mCurrentShuffleIdx = 0;
- updateAndGetCurrentNextItemIfNeededLocked();
- curItem = mCurPlaylistItem;
- nextItem = mNextPlaylistItem;
- }
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
- @Override
- public void callCallback(
- SessionPlayer2.PlayerCallback callback) {
- callback.onPlaylistChanged(XMediaPlayer.this, playlist, metadata);
+ for (MediaItem2 item : playlist) {
+ if (item == null) {
+ throw new IllegalArgumentException("playlist shouldn't contain null item");
}
- });
- if (curItem != null) {
- return setPlayerMediaItemsInternal(curItem, nextItem);
}
- return createFutureForResultCodeInternal(RESULT_CODE_SUCCESS);
+
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ MediaItem2 curItem;
+ MediaItem2 nextItem;
+ synchronized (mPlaylistLock) {
+ mPlaylistMetadata = metadata;
+ mPlaylist.clear();
+ mShuffledList.clear();
+ mPlaylist.addAll(playlist);
+ applyShuffleModeLocked();
+ mCurrentShuffleIdx = 0;
+ updateAndGetCurrentNextItemIfNeededLocked();
+ curItem = mCurPlaylistItem;
+ nextItem = mNextPlaylistItem;
+ }
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(
+ SessionPlayer2.PlayerCallback callback) {
+ callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
+ }
+ });
+ if (curItem != null) {
+ return setMediaItemsInternal(curItem, nextItem);
+ }
+ return createFuturesForResultCode(RESULT_CODE_SUCCESS);
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
+ @NonNull
@Override
- public ListenableFuture<PlayerResult> addPlaylistItem(int index, MediaItem2 item) {
+ public ListenableFuture<PlayerResult> addPlaylistItem(
+ final int index, @NonNull final MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
@@ -788,85 +987,109 @@
throw new IllegalStateException("The item is already in the playlist: " + item);
}
}
- Pair<MediaItem2, MediaItem2> updatedCurNextItem;
- synchronized (mPlaylistLock) {
- index = clamp(index, mPlaylist.size());
- int addedShuffleIdx = index;
- mPlaylist.add(index, item);
- if (mShuffleMode == SessionPlayer2.SHUFFLE_MODE_NONE) {
- mShuffledList.add(index, item);
- } else {
- // Add the item in random position of mShuffledList.
- addedShuffleIdx = (int) (Math.random() * (mShuffledList.size() + 1));
- mShuffledList.add(addedShuffleIdx, item);
- }
- if (addedShuffleIdx <= mCurrentShuffleIdx) {
- mCurrentShuffleIdx++;
- }
- updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
- }
- final List<MediaItem2> playlist = getPlaylist();
- final MediaMetadata2 metadata = getPlaylistMetadata();
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
- @Override
- public void callCallback(
- SessionPlayer2.PlayerCallback callback) {
- callback.onPlaylistChanged(XMediaPlayer.this, playlist, metadata);
- }
- });
- if (updatedCurNextItem.second == null) {
- return createFutureForResultCodeInternal(RESULT_CODE_SUCCESS);
- }
- return setNextMediaItemInternal(updatedCurNextItem.second);
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ Pair<MediaItem2, MediaItem2> updatedCurNextItem;
+ synchronized (mPlaylistLock) {
+ int clampedIndex = clamp(index, mPlaylist.size());
+ int addedShuffleIdx = clampedIndex;
+ mPlaylist.add(clampedIndex, item);
+ if (mShuffleMode == SessionPlayer2.SHUFFLE_MODE_NONE) {
+ mShuffledList.add(clampedIndex, item);
+ } else {
+ // Add the item in random position of mShuffledList.
+ addedShuffleIdx = (int) (Math.random() * (mShuffledList.size() + 1));
+ mShuffledList.add(addedShuffleIdx, item);
+ }
+ if (addedShuffleIdx <= mCurrentShuffleIdx) {
+ mCurrentShuffleIdx++;
+ }
+ updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
+ }
+ final List<MediaItem2> playlist = getPlaylist();
+ final MediaMetadata2 metadata = getPlaylistMetadata();
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(
+ SessionPlayer2.PlayerCallback callback) {
+ callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
+ }
+ });
+
+ if (updatedCurNextItem.second == null) {
+ return createFuturesForResultCode(RESULT_CODE_SUCCESS);
+ }
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ futures.add(setNextMediaItemInternal(updatedCurNextItem.second));
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
- public ListenableFuture<PlayerResult> removePlaylistItem(MediaItem2 item) {
+ @NonNull
+ public ListenableFuture<PlayerResult> removePlaylistItem(@NonNull final MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- int removedItemShuffleIdx;
- MediaItem2 curItem;
- MediaItem2 nextItem;
- Pair<MediaItem2, MediaItem2> updatedCurNextItem = null;
- synchronized (mPlaylistLock) {
- removedItemShuffleIdx = mShuffledList.indexOf(item);
- if (removedItemShuffleIdx >= 0) {
- mPlaylist.remove(item);
- mShuffledList.remove(removedItemShuffleIdx);
- if (removedItemShuffleIdx < mCurrentShuffleIdx) {
- mCurrentShuffleIdx--;
- }
- updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
- }
- curItem = mCurPlaylistItem;
- nextItem = mNextPlaylistItem;
- }
- if (removedItemShuffleIdx >= 0) {
- final List<MediaItem2> playlist = getPlaylist();
- final MediaMetadata2 metadata = getPlaylistMetadata();
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
- @Override
- public void callCallback(
- SessionPlayer2.PlayerCallback callback) {
- callback.onPlaylistChanged(XMediaPlayer.this, playlist, metadata);
- }
- });
- }
- if (updatedCurNextItem != null) {
- if (updatedCurNextItem.first != null) {
- return setPlayerMediaItemsInternal(curItem, nextItem);
- } else if (updatedCurNextItem.second != null) {
- return setNextMediaItemInternal(nextItem);
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ int removedItemShuffleIdx;
+ MediaItem2 curItem;
+ MediaItem2 nextItem;
+ Pair<MediaItem2, MediaItem2> updatedCurNextItem = null;
+ synchronized (mPlaylistLock) {
+ removedItemShuffleIdx = mShuffledList.indexOf(item);
+ if (removedItemShuffleIdx >= 0) {
+ mPlaylist.remove(item);
+ mShuffledList.remove(removedItemShuffleIdx);
+ if (removedItemShuffleIdx < mCurrentShuffleIdx) {
+ mCurrentShuffleIdx--;
+ }
+ updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
+ }
+ curItem = mCurPlaylistItem;
+ nextItem = mNextPlaylistItem;
+ }
+ if (removedItemShuffleIdx >= 0) {
+ final List<MediaItem2> playlist = getPlaylist();
+ final MediaMetadata2 metadata = getPlaylistMetadata();
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(
+ SessionPlayer2.PlayerCallback callback) {
+ callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
+ }
+ });
+ }
+
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ if (updatedCurNextItem != null) {
+ if (updatedCurNextItem.first != null) {
+ futures.addAll(setMediaItemsInternal(curItem, nextItem));
+ } else if (updatedCurNextItem.second != null) {
+ futures.add(setNextMediaItemInternal(nextItem));
+ }
+ } else {
+ futures.add(createFutureForResultCode(RESULT_CODE_SUCCESS));
+ }
+ return futures;
}
- }
- return createFutureForResultCodeInternal(RESULT_CODE_SUCCESS);
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
+ @NonNull
@Override
- public ListenableFuture<PlayerResult> replacePlaylistItem(int index, MediaItem2 item) {
+ public ListenableFuture<PlayerResult> replacePlaylistItem(
+ final int index, @NonNull final MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
@@ -879,167 +1102,230 @@
throw new IllegalStateException("The item is already in the playlist: " + item);
}
}
- MediaItem2 curItem;
- MediaItem2 nextItem;
- Pair<MediaItem2, MediaItem2> updatedCurNextItem = null;
- synchronized (mPlaylistLock) {
- if (index >= mPlaylist.size()) {
- return createFutureForResultCodeInternal(RESULT_CODE_BAD_VALUE);
- }
- int shuffleIdx = mShuffledList.indexOf(mPlaylist.get(index));
- mShuffledList.set(shuffleIdx, item);
- mPlaylist.set(index, item);
- updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
- curItem = mCurPlaylistItem;
- nextItem = mNextPlaylistItem;
- }
- // TODO: Should we notify current media item changed if it is replaced?
- final List<MediaItem2> playlist = getPlaylist();
- final MediaMetadata2 metadata = getPlaylistMetadata();
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
@Override
- public void callCallback(
- SessionPlayer2.PlayerCallback callback) {
- callback.onPlaylistChanged(XMediaPlayer.this, playlist, metadata);
- }
- });
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ MediaItem2 curItem;
+ MediaItem2 nextItem;
+ Pair<MediaItem2, MediaItem2> updatedCurNextItem = null;
+ synchronized (mPlaylistLock) {
+ if (index >= mPlaylist.size()) {
+ return createFuturesForResultCode(RESULT_CODE_BAD_VALUE);
+ }
+ int shuffleIdx = mShuffledList.indexOf(mPlaylist.get(index));
+ mShuffledList.set(shuffleIdx, item);
+ mPlaylist.set(index, item);
+ updatedCurNextItem = updateAndGetCurrentNextItemIfNeededLocked();
+ curItem = mCurPlaylistItem;
+ nextItem = mNextPlaylistItem;
+ }
+ // TODO: Should we notify current media item changed if it is replaced?
+ final List<MediaItem2> playlist = getPlaylist();
+ final MediaMetadata2 metadata = getPlaylistMetadata();
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(
+ SessionPlayer2.PlayerCallback callback) {
+ callback.onPlaylistChanged(MediaPlayer.this, playlist, metadata);
+ }
+ });
- if (updatedCurNextItem != null) {
- if (updatedCurNextItem.first != null) {
- return setPlayerMediaItemsInternal(curItem, nextItem);
- } else if (updatedCurNextItem.second != null) {
- return setNextMediaItemInternal(nextItem);
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ if (updatedCurNextItem != null) {
+ if (updatedCurNextItem.first != null) {
+ futures.addAll(setMediaItemsInternal(curItem, nextItem));
+ } else if (updatedCurNextItem.second != null) {
+ futures.add(setNextMediaItemInternal(nextItem));
+ }
+ } else {
+ futures.add(createFutureForResultCode(RESULT_CODE_SUCCESS));
+ }
+ return futures;
}
- }
-
- return createFutureForResultCodeInternal(RESULT_CODE_SUCCESS);
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
+ @NonNull
public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
- MediaItem2 curItem;
- MediaItem2 nextItem;
- synchronized (mPlaylistLock) {
- int prevShuffleIdx = mCurrentShuffleIdx - 1;
- if (prevShuffleIdx < 0) {
- if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
- prevShuffleIdx = mShuffledList.size() - 1;
- } else {
- return createFutureForResultCodeInternal(RESULT_CODE_INVALID_STATE);
- }
- }
- mCurrentShuffleIdx = prevShuffleIdx;
- updateAndGetCurrentNextItemIfNeededLocked();
- curItem = mCurPlaylistItem;
- nextItem = mNextPlaylistItem;
- }
- return setPlayerMediaItemsInternal(curItem, nextItem);
- }
-
- @Override
- public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
- MediaItem2 curItem;
- MediaItem2 nextItem;
- synchronized (mPlaylistLock) {
- int nextShuffleIdx = mCurrentShuffleIdx + 1;
- if (nextShuffleIdx >= mShuffledList.size()) {
- if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
- nextShuffleIdx = 0;
- } else {
- return createFutureForResultCodeInternal(RESULT_CODE_INVALID_STATE);
- }
- }
- mCurrentShuffleIdx = nextShuffleIdx;
- updateAndGetCurrentNextItemIfNeededLocked();
- curItem = mCurPlaylistItem;
- nextItem = mNextPlaylistItem;
- }
- return curItem != null ? setPlayerMediaItemsInternal(curItem, nextItem)
- : skipToNextInternal();
- }
-
- @Override
- public ListenableFuture<PlayerResult> skipToPlaylistItem(MediaItem2 item) {
- MediaItem2 curItem;
- MediaItem2 nextItem;
- synchronized (mPlaylistLock) {
- int newShuffleIdx = mShuffledList.indexOf(item);
- if (newShuffleIdx < 0) {
- return createFutureForResultCodeInternal(RESULT_CODE_BAD_VALUE);
- }
- mCurrentShuffleIdx = newShuffleIdx;
- updateAndGetCurrentNextItemIfNeededLocked();
- curItem = mCurPlaylistItem;
- nextItem = mNextPlaylistItem;
- }
- return setPlayerMediaItemsInternal(curItem, nextItem);
- }
-
- @Override
- public ListenableFuture<PlayerResult> updatePlaylistMetadata(final MediaMetadata2 metadata) {
- synchronized (mPlaylistLock) {
- mPlaylistMetadata = metadata;
- }
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
@Override
- public void callCallback(
- SessionPlayer2.PlayerCallback callback) {
- callback.onPlaylistMetadataChanged(XMediaPlayer.this, metadata);
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ MediaItem2 curItem;
+ MediaItem2 nextItem;
+ synchronized (mPlaylistLock) {
+ int prevShuffleIdx = mCurrentShuffleIdx - 1;
+ if (prevShuffleIdx < 0) {
+ if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
+ prevShuffleIdx = mShuffledList.size() - 1;
+ } else {
+ return createFuturesForResultCode(RESULT_CODE_INVALID_STATE);
+ }
+ }
+ mCurrentShuffleIdx = prevShuffleIdx;
+ updateAndGetCurrentNextItemIfNeededLocked();
+ curItem = mCurPlaylistItem;
+ nextItem = mNextPlaylistItem;
+ }
+ return setMediaItemsInternal(curItem, nextItem);
}
- });
-
- return createFutureForResultCodeInternal(RESULT_CODE_SUCCESS);
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
+ @NonNull
+ public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ MediaItem2 curItem;
+ MediaItem2 nextItem;
+ synchronized (mPlaylistLock) {
+ int nextShuffleIdx = mCurrentShuffleIdx + 1;
+ if (nextShuffleIdx >= mShuffledList.size()) {
+ if (mRepeatMode == REPEAT_MODE_ALL || mRepeatMode == REPEAT_MODE_GROUP) {
+ nextShuffleIdx = 0;
+ } else {
+ return createFuturesForResultCode(RESULT_CODE_INVALID_STATE);
+ }
+ }
+ mCurrentShuffleIdx = nextShuffleIdx;
+ updateAndGetCurrentNextItemIfNeededLocked();
+ curItem = mCurPlaylistItem;
+ nextItem = mNextPlaylistItem;
+ }
+ if (curItem != null) {
+ return setMediaItemsInternal(curItem, nextItem);
+ }
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ futures.add(skipToNextInternal());
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
+ }
+
+ @Override
+ @NonNull
+ public ListenableFuture<PlayerResult> skipToPlaylistItem(@NonNull final MediaItem2 item) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ MediaItem2 curItem;
+ MediaItem2 nextItem;
+ synchronized (mPlaylistLock) {
+ int newShuffleIdx = mShuffledList.indexOf(item);
+ if (newShuffleIdx < 0) {
+ return createFuturesForResultCode(RESULT_CODE_BAD_VALUE);
+ }
+ mCurrentShuffleIdx = newShuffleIdx;
+ updateAndGetCurrentNextItemIfNeededLocked();
+ curItem = mCurPlaylistItem;
+ nextItem = mNextPlaylistItem;
+ }
+ return setMediaItemsInternal(curItem, nextItem);
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<PlayerResult> updatePlaylistMetadata(
+ @Nullable final MediaMetadata2 metadata) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ synchronized (mPlaylistLock) {
+ mPlaylistMetadata = metadata;
+ }
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(
+ SessionPlayer2.PlayerCallback callback) {
+ callback.onPlaylistMetadataChanged(MediaPlayer.this, metadata);
+ }
+ });
+ return createFuturesForResultCode(RESULT_CODE_SUCCESS);
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
+ }
+
+ @Override
+ @NonNull
public ListenableFuture<PlayerResult> setRepeatMode(final int repeatMode) {
- if (repeatMode < SessionPlayer2.REPEAT_MODE_NONE
- || repeatMode > SessionPlayer2.REPEAT_MODE_GROUP) {
- return createFutureForResultCodeInternal(RESULT_CODE_BAD_VALUE);
- }
-
- boolean changed;
- synchronized (mPlaylistLock) {
- changed = mRepeatMode != repeatMode;
- mRepeatMode = repeatMode;
- }
- if (changed) {
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
- @Override
- public void callCallback(
- SessionPlayer2.PlayerCallback callback) {
- callback.onRepeatModeChanged(XMediaPlayer.this, repeatMode);
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ if (repeatMode < SessionPlayer2.REPEAT_MODE_NONE
+ || repeatMode > SessionPlayer2.REPEAT_MODE_GROUP) {
+ return createFuturesForResultCode(RESULT_CODE_BAD_VALUE);
}
- });
- }
- return createFutureForResultCodeInternal(RESULT_CODE_SUCCESS);
+
+ boolean changed;
+ synchronized (mPlaylistLock) {
+ changed = mRepeatMode != repeatMode;
+ mRepeatMode = repeatMode;
+ }
+ if (changed) {
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(
+ SessionPlayer2.PlayerCallback callback) {
+ callback.onRepeatModeChanged(MediaPlayer.this, repeatMode);
+ }
+ });
+ }
+ return createFuturesForResultCode(RESULT_CODE_SUCCESS);
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
+ @NonNull
public ListenableFuture<PlayerResult> setShuffleMode(final int shuffleMode) {
- if (shuffleMode < SessionPlayer2.SHUFFLE_MODE_NONE
- || shuffleMode > SessionPlayer2.SHUFFLE_MODE_GROUP) {
- return createFutureForResultCodeInternal(RESULT_CODE_BAD_VALUE);
- }
-
- boolean changed;
- synchronized (mPlaylistLock) {
- changed = mShuffleMode != shuffleMode;
- mShuffleMode = shuffleMode;
- }
- if (changed) {
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
- @Override
- public void callCallback(
- SessionPlayer2.PlayerCallback callback) {
- callback.onShuffleModeChanged(XMediaPlayer.this, shuffleMode);
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ if (shuffleMode < SessionPlayer2.SHUFFLE_MODE_NONE
+ || shuffleMode > SessionPlayer2.SHUFFLE_MODE_GROUP) {
+ return createFuturesForResultCode(RESULT_CODE_BAD_VALUE);
}
- });
- }
- return createFutureForResultCodeInternal(RESULT_CODE_SUCCESS);
+
+ boolean changed;
+ synchronized (mPlaylistLock) {
+ changed = mShuffleMode != shuffleMode;
+ mShuffleMode = shuffleMode;
+ }
+ if (changed) {
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(
+ SessionPlayer2.PlayerCallback callback) {
+ callback.onShuffleModeChanged(MediaPlayer.this, shuffleMode);
+ }
+ });
+ }
+ return createFuturesForResultCode(RESULT_CODE_SUCCESS);
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@Override
+ @Nullable
public List<MediaItem2> getPlaylist() {
synchronized (mPlaylistLock) {
return mPlaylist.isEmpty() ? null : new ArrayList<>(mPlaylist);
@@ -1047,6 +1333,7 @@
}
@Override
+ @Nullable
public MediaMetadata2 getPlaylistMetadata() {
synchronized (mPlaylistLock) {
return mPlaylistMetadata;
@@ -1068,6 +1355,7 @@
}
@Override
+ @Nullable
public MediaItem2 getCurrentMediaItem() {
return mPlayer.getCurrentMediaItem();
}
@@ -1089,19 +1377,26 @@
}
/**
- * Resets {@link XMediaPlayer} to its uninitialized state. After calling
+ * Resets {@link MediaPlayer} to its uninitialized state. After calling
* this method, you will have to initialize it again by setting the
* media item and calling {@link #prepare()}.
*/
public void reset() {
- mPlayer.reset();
+ // Cancel the pending futures.
+ synchronized (mPendingFutures) {
+ for (PendingFuture f : mPendingFutures) {
+ if (f.mExecuted && !f.isDone() && !f.isCancelled()) {
+ f.cancel(true);
+ }
+ }
+ mPendingFutures.clear();
+ }
+ // Cancel the pending commands.
synchronized (mPendingCommands) {
- // Cancel the pending futures.
for (PendingCommand c : mPendingCommands) {
c.mFuture.cancel(true);
}
mPendingCommands.clear();
- mPreviewEnabled = false;
}
synchronized (mStateLock) {
mState = PLAYER_STATE_IDLE;
@@ -1115,6 +1410,8 @@
mCurrentShuffleIdx = END_OF_PLAYLIST;
mSetMediaItemCalled = false;
}
+ mAudioFocusHandler.onReset();
+ mPlayer.reset();
}
/**
@@ -1136,13 +1433,23 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> setSurface(Surface surface) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.setSurface(surface);
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_SURFACE, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> setSurface(@Nullable final Surface surface) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.setSurface(surface);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_SURFACE, future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1157,14 +1464,18 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> setPlayerVolume(float volume) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.setPlayerVolume(volume);
- addPendingCommandLocked(
- MediaPlayer2.CALL_COMPLETED_SET_PLAYER_VOLUME, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> setPlayerVolume(final float volume) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ futures.add(setPlayerVolumeInternal(volume));
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1229,14 +1540,24 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> setPlaybackParams(@NonNull PlaybackParams2 params) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.setPlaybackParams(params);
- addPendingCommandLocked(
- MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> setPlaybackParams(@NonNull final PlaybackParams2 params) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.setPlaybackParams(params);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1244,6 +1565,7 @@
*
* @return the playback params.
*/
+ @NonNull
public PlaybackParams2 getPlaybackParams() {
return mPlayer.getPlaybackParams();
}
@@ -1265,14 +1587,24 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> seekTo(long msec, @SeekMode int mode) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- int mp2SeekMode = sSeekModeMap.getOrDefault(mode, SEEK_NEXT_SYNC);
- synchronized (mPendingCommands) {
- Object token = mPlayer.seekTo(msec, mp2SeekMode);
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SEEK_TO, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> seekTo(final long msec, @SeekMode final int mode) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(true) {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ int mp2SeekMode = sSeekModeMap.getOrDefault(mode, SEEK_NEXT_SYNC);
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.seekTo(msec, mp2SeekMode);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SEEK_TO, future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1315,14 +1647,24 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> setAudioSessionId(int sessionId) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.setAudioSessionId(sessionId);
- addPendingCommandLocked(
- MediaPlayer2.CALL_COMPLETED_SET_AUDIO_SESSION_ID, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> setAudioSessionId(final int sessionId) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.setAudioSessionId(sessionId);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_AUDIO_SESSION_ID,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1351,14 +1693,24 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> attachAuxEffect(int effectId) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.attachAuxEffect(effectId);
- addPendingCommandLocked(
- MediaPlayer2.CALL_COMPLETED_ATTACH_AUX_EFFECT, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> attachAuxEffect(final int effectId) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.attachAuxEffect(effectId);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_ATTACH_AUX_EFFECT,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
@@ -1376,14 +1728,24 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> setAuxEffectSendLevel(float level) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.setAuxEffectSendLevel(level);
- addPendingCommandLocked(
- MediaPlayer2.CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> setAuxEffectSendLevel(final float level) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.setAuxEffectSendLevel(level);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1391,6 +1753,7 @@
*
* @return List of track info. The total number of tracks is the size of the list.
*/
+ @NonNull
public List<TrackInfo> getTrackInfo() {
List<MediaPlayer2.TrackInfo> list = mPlayer.getTrackInfo();
List<TrackInfo> trackList = new ArrayList<>();
@@ -1449,13 +1812,24 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> selectTrack(int index) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.selectTrack(index);
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SELECT_TRACK, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> selectTrack(final int index) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.selectTrack(index);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SELECT_TRACK,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1472,20 +1846,34 @@
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link PlayerResult} will be delivered when the command completes.
*/
- public ListenableFuture<PlayerResult> deselectTrack(int index) {
- ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.deselectTrack(index);
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<PlayerResult> deselectTrack(final int index) {
+ PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>() {
+ @Override
+ List<ResolvableFuture<PlayerResult>> onExecute() {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.deselectTrack(index);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_DESELECT_TRACK,
+ future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
* Retrieves the DRM Info associated with the current media item.
*
* @throws IllegalStateException if called before being prepared
+ * @hide
*/
+ @Nullable
+ @RestrictTo(LIBRARY_GROUP)
public DrmInfo getDrmInfo() {
MediaPlayer2.DrmInfo info = mPlayer.getDrmInfo();
return info == null ? null : new DrmInfo(info);
@@ -1513,15 +1901,28 @@
* {@link PlayerCallback#onDrmInfo}.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link DrmResult} will be delivered when the command completes.
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
// This is an asynchronous call.
- public ResolvableFuture<DrmResult> prepareDrm(@NonNull UUID uuid) {
- ResolvableFuture<DrmResult> future = ResolvableFuture.create();
- synchronized (mPendingCommands) {
- Object token = mPlayer.prepareDrm(uuid);
- addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PREPARE_DRM, future, token);
- }
- return future;
+ @NonNull
+ public ListenableFuture<DrmResult> prepareDrm(@NonNull final UUID uuid) {
+ PendingFuture<DrmResult> pendingFuture = new PendingFuture<DrmResult>() {
+ @Override
+ List<ResolvableFuture<DrmResult>> onExecute() {
+ ArrayList<ResolvableFuture<DrmResult>> futures = new ArrayList<>();
+ ResolvableFuture<DrmResult> future = ResolvableFuture.create();
+ synchronized (mPendingCommands) {
+ Object token = mPlayer.prepareDrm(uuid);
+ addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_PREPARE_DRM, future, token);
+ }
+ futures.add(future);
+ return futures;
+ }
+ };
+
+ addPendingFuture(pendingFuture);
+ return pendingFuture;
}
/**
@@ -1532,7 +1933,9 @@
* A {@code reset()} call will release the DRM session implicitly.
*
* @throws NoDrmSchemeException if there is no active DRM session to release
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
public void releaseDrm() throws NoDrmSchemeException {
try {
mPlayer.releaseDrm();
@@ -1577,7 +1980,9 @@
* This may be {@code null} if no additional parameters are to be sent.
*
* @throws NoDrmSchemeException if there is no active DRM session
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
@NonNull
public MediaDrm.KeyRequest getDrmKeyRequest(
@Nullable byte[] keySetId, @Nullable byte[] initData,
@@ -1610,7 +2015,10 @@
* @throws NoDrmSchemeException if there is no active DRM session
* @throws DeniedByServerException if the response indicates that the
* server rejected the request
+ * @hide
*/
+ @Nullable
+ @RestrictTo(LIBRARY_GROUP)
public byte[] provideDrmKeyResponse(
@Nullable byte[] keySetId, @NonNull byte[] response)
throws NoDrmSchemeException, DeniedByServerException {
@@ -1626,7 +2034,9 @@
* keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}.
*
* @param keySetId identifies the saved key set to restore
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
public void restoreDrmKeys(@NonNull byte[] keySetId) throws NoDrmSchemeException {
try {
mPlayer.restoreDrmKeys(keySetId);
@@ -1643,7 +2053,9 @@
* Standard fields names are:
* {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
* {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
@NonNull
public String getDrmPropertyString(@NonNull String propertyName) throws NoDrmSchemeException {
try {
@@ -1662,7 +2074,9 @@
* Standard fields names are:
* {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
* {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
public void setDrmPropertyString(@NonNull String propertyName, @NonNull String value)
throws NoDrmSchemeException {
try {
@@ -1679,13 +2093,15 @@
* of {@link #prepareDrm(UUID uuid)}.
*
* @param listener the callback that will be run
+ * @hide
*/
- public void setOnDrmConfigHelper(final OnDrmConfigHelper listener) {
+ @RestrictTo(LIBRARY_GROUP)
+ public void setOnDrmConfigHelper(@Nullable final OnDrmConfigHelper listener) {
mPlayer.setOnDrmConfigHelper(listener == null ? null :
new MediaPlayer2.OnDrmConfigHelper() {
@Override
public void onDrmConfig(MediaPlayer2 mp, MediaItem2 item) {
- listener.onDrmConfig(XMediaPlayer.this, item);
+ listener.onDrmConfig(MediaPlayer.this, item);
}
});
}
@@ -1703,7 +2119,7 @@
notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
@Override
public void callCallback(SessionPlayer2.PlayerCallback callback) {
- callback.onPlayerStateChanged(XMediaPlayer.this, state);
+ callback.onPlayerStateChanged(MediaPlayer.this, state);
}
});
}
@@ -1719,7 +2135,7 @@
notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
@Override
public void callCallback(SessionPlayer2.PlayerCallback callback) {
- callback.onBufferingStateChanged(XMediaPlayer.this, item, state);
+ callback.onBufferingStateChanged(MediaPlayer.this, item, state);
}
});
}
@@ -1740,7 +2156,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- void notifyXMediaPlayerCallback(final XMediaPlayerCallbackNotifier notifier) {
+ void notifyMediaPlayerCallback(final MediaPlayerCallbackNotifier notifier) {
List<Pair<SessionPlayer2.PlayerCallback, Executor>> callbacks = getCallbacks();
for (Pair<SessionPlayer2.PlayerCallback, Executor> pair : callbacks) {
if (pair.first instanceof PlayerCallback) {
@@ -1759,26 +2175,33 @@
void callCallback(SessionPlayer2.PlayerCallback callback);
}
- private interface XMediaPlayerCallbackNotifier {
+ private interface MediaPlayerCallbackNotifier {
void callCallback(PlayerCallback callback);
}
- private ListenableFuture<PlayerResult> setPlayerMediaItemsInternal(
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ List<ResolvableFuture<PlayerResult>> setMediaItemsInternal(
@NonNull MediaItem2 curItem, @Nullable MediaItem2 nextItem) {
boolean setMediaItemCalled;
synchronized (mPlaylistLock) {
setMediaItemCalled = mSetMediaItemCalled;
}
- ListenableFuture<PlayerResult> future = !setMediaItemCalled
- ? setMediaItemInternal(curItem)
- : CombindedCommandResultFuture.create(mExecutor,
- setNextMediaItemInternal(curItem), skipToNextInternal());
- return nextItem == null ? future : CombindedCommandResultFuture.create(
- mExecutor, future, setNextMediaItemInternal(nextItem));
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ if (setMediaItemCalled) {
+ futures.add(setNextMediaItemInternal(curItem));
+ futures.add(skipToNextInternal());
+ } else {
+ futures.add(setMediaItemInternal(curItem));
+ }
+
+ if (nextItem != null) {
+ futures.add(setNextMediaItemInternal(nextItem));
+ }
+ return futures;
}
- private ListenableFuture<PlayerResult> setMediaItemInternal(MediaItem2 item) {
+ private ResolvableFuture<PlayerResult> setMediaItemInternal(MediaItem2 item) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
Object token = mPlayer.setMediaItem(item);
@@ -1790,7 +2213,8 @@
return future;
}
- private ListenableFuture<PlayerResult> setNextMediaItemInternal(MediaItem2 item) {
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ResolvableFuture<PlayerResult> setNextMediaItemInternal(MediaItem2 item) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
Object token = mPlayer.setNextMediaItem(item);
@@ -1800,7 +2224,8 @@
return future;
}
- private ListenableFuture<PlayerResult> skipToNextInternal() {
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ResolvableFuture<PlayerResult> skipToNextInternal() {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
Object token = mPlayer.skipToNext();
@@ -1810,21 +2235,33 @@
return future;
}
- private ListenableFuture<PlayerResult> createFutureForResultCodeInternal(int resultCode) {
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ResolvableFuture<PlayerResult> setPlayerVolumeInternal(float volume) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
- if (mPendingCommands.size() > 0) {
- // TODO: Find a better way to set the call type.
- addPendingCommandLocked(CALL_COMPLETE_PLAYLIST_BASE - resultCode, future, null);
- } else {
- future.set(new PlayerResult(resultCode, mPlayer.getCurrentMediaItem()));
- }
+ Object token = mPlayer.setPlayerVolume(volume);
+ addPendingCommandLocked(
+ MediaPlayer2.CALL_COMPLETED_SET_PLAYER_VOLUME, future, token);
}
return future;
}
- @SuppressWarnings("GuardedBy")
- private void applyShuffleModeLocked() {
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ResolvableFuture<PlayerResult> createFutureForResultCode(int resultCode) {
+ ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
+ future.set(new PlayerResult(resultCode, mPlayer.getCurrentMediaItem()));
+ return future;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ List<ResolvableFuture<PlayerResult>> createFuturesForResultCode(int resultCode) {
+ ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
+ futures.add(createFutureForResultCode(resultCode));
+ return futures;
+ }
+
+ @SuppressWarnings({"GuardedBy", "WeakerAccess"}) /* synthetic access */
+ void applyShuffleModeLocked() {
mShuffledList.clear();
mShuffledList.addAll(mPlaylist);
if (mShuffleMode == SessionPlayer2.SHUFFLE_MODE_ALL
@@ -1841,8 +2278,8 @@
* same, it will return null Pair. If non null Pair which contains two nulls, that means one of
* current and next item or both are changed to null.
*/
- @SuppressWarnings("GuardedBy")
- private Pair<MediaItem2, MediaItem2> updateAndGetCurrentNextItemIfNeededLocked() {
+ @SuppressWarnings({"GuardedBy", "WeakerAccess"}) /* synthetic access */
+ Pair<MediaItem2, MediaItem2> updateAndGetCurrentNextItemIfNeededLocked() {
MediaItem2 changedCurItem = null;
MediaItem2 changedNextItem = null;
if (mCurrentShuffleIdx < 0) {
@@ -1876,7 +2313,8 @@
}
// Clamps value to [0, maxValue]
- private static int clamp(int value, int maxValue) {
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ static int clamp(int value, int maxValue) {
if (value < 0) {
return 0;
}
@@ -1914,7 +2352,7 @@
@Override
public void callCallback(
SessionPlayer2.PlayerCallback callback) {
- callback.onSeekCompleted(XMediaPlayer.this, pos);
+ callback.onSeekCompleted(MediaPlayer.this, pos);
}
});
break;
@@ -1923,7 +2361,7 @@
@Override
public void callCallback(
SessionPlayer2.PlayerCallback callback) {
- callback.onCurrentMediaItemChanged(XMediaPlayer.this, item);
+ callback.onCurrentMediaItemChanged(MediaPlayer.this, item);
}
});
break;
@@ -1934,7 +2372,7 @@
@Override
public void callCallback(
SessionPlayer2.PlayerCallback callback) {
- callback.onPlaybackSpeedChanged(XMediaPlayer.this, speed);
+ callback.onPlaybackSpeedChanged(MediaPlayer.this, speed);
}
});
break;
@@ -1943,7 +2381,7 @@
notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
@Override
public void callCallback(SessionPlayer2.PlayerCallback callback) {
- callback.onAudioAttributesChanged(XMediaPlayer.this, attr);
+ callback.onAudioAttributesChanged(MediaPlayer.this, attr);
}
});
break;
@@ -1957,32 +2395,54 @@
status, DrmResult.RESULT_CODE_PREPARATION_ERROR);
expected.mFuture.set(new DrmResult(resultCode, item));
}
+ executePendingFuturesIfNeeded();
+ }
- PendingCommand command;
- // TODO: Make commands be executed sequentially
- while (true) {
- synchronized (mPendingCommands) {
- command = mPendingCommands.peekFirst();
+ private void executePendingFuturesIfNeeded() {
+ synchronized (mPendingFutures) {
+ PendingFuture<? super PlayerResult> pendingFuture;
+ while ((pendingFuture = mPendingFutures.peekFirst()) != null) {
+ if (pendingFuture.isCancelled()) {
+ mPendingFutures.removeFirst();
+ } else if (pendingFuture.mExecuted) {
+ // Set result and remove pending futures that are finished.
+ if (pendingFuture.setResultIfFinished()) {
+ mPendingFutures.removeFirst();
+ } else {
+ break;
+ }
+ } else {
+ pendingFuture.execute();
+ if (!pendingFuture.isDone()) {
+ break;
+ }
+ }
}
- if (command == null || command.mCallType > CALL_COMPLETE_PLAYLIST_BASE) {
- break;
+ // Execute skip futures earlier for making them be skipped.
+ Iterator<PendingFuture<? super PlayerResult>> it = mPendingFutures.iterator();
+ if (it.hasNext()) {
+ // The first future is being handled.
+ it.next();
}
- command.mFuture.set(new PlayerResult(
- CALL_COMPLETE_PLAYLIST_BASE - command.mCallType, item));
- synchronized (mPendingCommands) {
- mPendingCommands.removeFirst();
+ while (it.hasNext()) {
+ PendingFuture f = it.next();
+ if (!f.mIsSeekTo) {
+ break;
+ }
+ f.execute();
}
}
}
+
@SuppressWarnings("WeakerAccess") /* synthetic access */
class Mp2DrmCallback extends MediaPlayer2.DrmEventCallback {
@Override
public void onDrmInfo(
MediaPlayer2 mp, final MediaItem2 item, final MediaPlayer2.DrmInfo drmInfo) {
- notifyXMediaPlayerCallback(new XMediaPlayerCallbackNotifier() {
+ notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
@Override
public void callCallback(PlayerCallback callback) {
- callback.onDrmInfo(XMediaPlayer.this, item,
+ callback.onDrmInfo(MediaPlayer.this, item,
drmInfo == null ? null : new DrmInfo(drmInfo));
}
});
@@ -1999,10 +2459,10 @@
@Override
public void onVideoSizeChanged(
MediaPlayer2 mp, final MediaItem2 item, final int width, final int height) {
- notifyXMediaPlayerCallback(new XMediaPlayerCallbackNotifier() {
+ notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
@Override
public void callCallback(PlayerCallback callback) {
- callback.onVideoSizeChanged(XMediaPlayer.this, item, width, height);
+ callback.onVideoSizeChanged(MediaPlayer.this, item, width, height);
}
});
}
@@ -2010,10 +2470,10 @@
@Override
public void onTimedMetaDataAvailable(
MediaPlayer2 mp, final MediaItem2 item, final TimedMetaData2 data) {
- notifyXMediaPlayerCallback(new XMediaPlayerCallbackNotifier() {
+ notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
@Override
public void callCallback(PlayerCallback callback) {
- callback.onTimedMetaDataAvailable(XMediaPlayer.this, item, data);
+ callback.onTimedMetaDataAvailable(MediaPlayer.this, item, data);
}
});
}
@@ -2023,10 +2483,10 @@
MediaPlayer2 mp, final MediaItem2 item, final int what, final int extra) {
setState(PLAYER_STATE_ERROR);
setBufferingState(item, BUFFERING_STATE_UNKNOWN);
- notifyXMediaPlayerCallback(new XMediaPlayerCallbackNotifier() {
+ notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
@Override
public void callCallback(PlayerCallback callback) {
- callback.onError(XMediaPlayer.this, item, what, extra);
+ callback.onError(MediaPlayer.this, item, what, extra);
}
});
}
@@ -2052,16 +2512,16 @@
notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
@Override
public void callCallback(SessionPlayer2.PlayerCallback callback) {
- callback.onPlaybackCompleted(XMediaPlayer.this);
+ callback.onPlaybackCompleted(MediaPlayer.this);
}
});
break;
}
final int what = sInfoCodeMap.getOrDefault(mp2What, MEDIA_INFO_UNKNOWN);
- notifyXMediaPlayerCallback(new XMediaPlayerCallbackNotifier() {
+ notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
@Override
public void callCallback(PlayerCallback callback) {
- callback.onInfo(XMediaPlayer.this, item, what, extra);
+ callback.onInfo(MediaPlayer.this, item, what, extra);
}
});
}
@@ -2075,26 +2535,26 @@
@Override
public void onMediaTimeDiscontinuity(
MediaPlayer2 mp, final MediaItem2 item, final MediaTimestamp2 timestamp) {
- notifyXMediaPlayerCallback(new XMediaPlayerCallbackNotifier() {
+ notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
@Override
public void callCallback(PlayerCallback callback) {
- callback.onMediaTimeDiscontinuity(XMediaPlayer.this, item, timestamp);
+ callback.onMediaTimeDiscontinuity(MediaPlayer.this, item, timestamp);
}
});
}
@Override
public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
- // Ignore. XMediaPlayer does not use MediaPlayer2.notifyWhenCommandLabelReached().
+ // Ignore. MediaPlayer does not use MediaPlayer2.notifyWhenCommandLabelReached().
}
@Override
public void onSubtitleData(
MediaPlayer2 mp, final MediaItem2 item, final SubtitleData2 data) {
- notifyXMediaPlayerCallback(new XMediaPlayerCallbackNotifier() {
+ notifyMediaPlayerCallback(new MediaPlayerCallbackNotifier() {
@Override
public void callCallback(PlayerCallback callback) {
- callback.onSubtitleData(XMediaPlayer.this, item, data);
+ callback.onSubtitleData(MediaPlayer.this, item, data);
}
});
}
@@ -2117,7 +2577,7 @@
* @param height the height of the video
*/
public void onVideoSizeChanged(
- XMediaPlayer mp, MediaItem2 item, int width, int height) { }
+ @NonNull MediaPlayer mp, @NonNull MediaItem2 item, int width, int height) { }
/**
* Called to indicate available timed metadata
@@ -2135,8 +2595,8 @@
* @param item the MediaItem2 of this media item
* @param data the timed metadata sample associated with this event
*/
- public void onTimedMetaDataAvailable(
- XMediaPlayer mp, MediaItem2 item, TimedMetaData2 data) { }
+ public void onTimedMetaDataAvailable(@NonNull MediaPlayer mp,
+ @NonNull MediaItem2 item, @NonNull TimedMetaData2 data) { }
/**
* Called to indicate an error.
@@ -2147,8 +2607,8 @@
* @param extra an extra code, specific to the error. Typically
* implementation dependent.
*/
- public void onError(
- XMediaPlayer mp, MediaItem2 item, @MediaError int what, int extra) { }
+ public void onError(@NonNull MediaPlayer mp,
+ @NonNull MediaItem2 item, @MediaError int what, int extra) { }
/**
* Called to indicate an info or a warning.
@@ -2159,7 +2619,8 @@
* @param extra an extra code, specific to the info. Typically
* implementation dependent.
*/
- public void onInfo(XMediaPlayer mp, MediaItem2 item, @MediaInfo int what, int extra) { }
+ public void onInfo(@NonNull MediaPlayer mp,
+ @NonNull MediaItem2 item, @MediaInfo int what, int extra) { }
/**
* Called when a discontinuity in the normal progression of the media time is detected.
@@ -2181,8 +2642,8 @@
* @param timestamp the timestamp that correlates media time, system time and clock rate,
* or {@link MediaTimestamp2#TIMESTAMP_UNKNOWN} in an error case.
*/
- public void onMediaTimeDiscontinuity(
- XMediaPlayer mp, MediaItem2 item, MediaTimestamp2 timestamp) { }
+ public void onMediaTimeDiscontinuity(@NonNull MediaPlayer mp,
+ @NonNull MediaItem2 item, @NonNull MediaTimestamp2 timestamp) { }
/**
* Called when when a player subtitle track has new subtitle data available.
@@ -2190,8 +2651,8 @@
* @param item the MediaItem2 of this media item
* @param data the subtitle data
*/
- public void onSubtitleData(
- XMediaPlayer mp, MediaItem2 item, @NonNull SubtitleData2 data) { }
+ public void onSubtitleData(@NonNull MediaPlayer mp,
+ @NonNull MediaItem2 item, @NonNull SubtitleData2 data) { }
/**
* Called to indicate DRM info is available
@@ -2200,8 +2661,11 @@
* @param item the MediaItem2 of this media item
* @param drmInfo DRM info of the source including PSSH, and subset
* of crypto schemes supported by this device
+ * @hide
*/
- public void onDrmInfo(XMediaPlayer mp, MediaItem2 item, DrmInfo drmInfo) { }
+ @RestrictTo(LIBRARY_GROUP)
+ public void onDrmInfo(@NonNull MediaPlayer mp,
+ @NonNull MediaItem2 item, @NonNull DrmInfo drmInfo) { }
}
/**
@@ -2236,6 +2700,7 @@
* When the language is unknown or could not be determined,
* ISO-639-2 language code, "und", is returned.
*/
+ @NonNull
public String getLanguage() {
String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
return language == null ? "und" : language;
@@ -2245,6 +2710,7 @@
* Gets the {@link MediaFormat} of the track. If the format is
* unknown or could not be determined, null is returned.
*/
+ @Nullable
public MediaFormat getFormat() {
if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
|| mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
@@ -2288,13 +2754,16 @@
/**
* Encapsulates the DRM properties of the source.
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
public static final class DrmInfo {
private final MediaPlayer2.DrmInfo mMp2DrmInfo;
/**
* Returns the PSSH info of the media item for each supported DRM scheme.
*/
+ @NonNull
public Map<UUID, byte[]> getPssh() {
return mMp2DrmInfo.getPssh();
}
@@ -2304,6 +2773,7 @@
* It effectively identifies the subset of the source's DRM schemes which
* are supported by the device too.
*/
+ @NonNull
public List<UUID> getSupportedSchemes() {
return mMp2DrmInfo.getSupportedSchemes();
}
@@ -2322,23 +2792,27 @@
*
* The only allowed DRM calls in this listener are {@link #getDrmPropertyString}
* and {@link #setDrmPropertyString}.
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
public interface OnDrmConfigHelper {
/**
* Called to give the app the opportunity to configure DRM before the session is created
*
- * @param mp the {@code XMediaPlayer} associated with this callback
+ * @param mp the {@code MediaPlayer} associated with this callback
* @param item the MediaItem2 of this media item
*/
- void onDrmConfig(XMediaPlayer mp, MediaItem2 item);
+ void onDrmConfig(@NonNull MediaPlayer mp, @NonNull MediaItem2 item);
}
/**
* Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
* Extends MediaDrm.MediaDrmException
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
public static class NoDrmSchemeException extends MediaDrmException {
- public NoDrmSchemeException(String detailMessage) {
+ public NoDrmSchemeException(@Nullable String detailMessage) {
super(detailMessage);
}
}
@@ -2440,59 +2914,11 @@
public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
}
- static final class CombindedCommandResultFuture extends AbstractResolvableFuture<PlayerResult> {
- final ListenableFuture<PlayerResult>[] mFutures;
- AtomicInteger mSuccessCount = new AtomicInteger(0);
-
- public static CombindedCommandResultFuture create(Executor executor,
- ListenableFuture<PlayerResult>... futures) {
- return new CombindedCommandResultFuture(executor, futures);
- }
-
- private CombindedCommandResultFuture(Executor executor,
- ListenableFuture<PlayerResult>[] futures) {
- mFutures = futures;
- for (int i = 0; i < mFutures.length; ++i) {
- final int cur = i;
- mFutures[i].addListener(new Runnable() {
- @Override
- public void run() {
- try {
- PlayerResult result = mFutures[cur].get();
- int resultCode = result.getResultCode();
- if (resultCode != RESULT_CODE_SUCCESS
- && resultCode != RESULT_CODE_SKIPPED) {
- for (int j = 0; j < mFutures.length; ++j) {
- if (!mFutures[j].isCancelled() && !mFutures[j].isDone()
- && cur != j) {
- mFutures[j].cancel(true);
- }
- }
- set(result);
- } else {
- int cnt = mSuccessCount.incrementAndGet();
- if (cnt == mFutures.length) {
- set(result);
- }
- }
- } catch (Exception e) {
- for (int j = 0; j < mFutures.length; ++j) {
- if (!mFutures[j].isCancelled() && !mFutures[j].isDone()
- && cur != j) {
- mFutures[j].cancel(true);
- }
- }
- setException(e);
- }
- }
- }, executor);
- }
- }
- }
-
/**
* Result class of the asynchronous DRM APIs.
+ * @hide
*/
+ @RestrictTo(LIBRARY_GROUP)
public static class DrmResult extends PlayerResult {
/**
* The device required DRM provisioning but couldn't reach the provisioning server.
@@ -2538,7 +2964,7 @@
* @param resultCode result code. Recommends to use the standard code defined here.
* @param item media item when the operation is completed
*/
- public DrmResult(@DrmResultCode int resultCode, @Nullable MediaItem2 item) {
+ public DrmResult(@DrmResultCode int resultCode, @NonNull MediaItem2 item) {
super(resultCode, item);
}
@@ -2548,7 +2974,8 @@
* @return result code.
*/
@Override
- public @DrmResultCode int getResultCode() {
+ @DrmResultCode
+ public int getResultCode() {
return super.getResultCode();
}
}
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer2.java b/media2/src/main/java/androidx/media2/MediaPlayer2.java
index 2052654..4512c53 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer2.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer2.java
@@ -491,7 +491,7 @@
* @return a token which can be used to cancel the operation later with {@link #cancel}.
*/
// This is an asynchronous call.
- public abstract Object setSurface(Surface surface);
+ public abstract Object setSurface(@Nullable Surface surface);
/* Do not change these video scaling mode values below without updating
* their counterparts in system/window.h! Please do not forget to update
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java b/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
index 7a0f799..fac87a7 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
@@ -1760,11 +1760,19 @@
}
synchronized int getVideoWidth() {
- return getCurrentPlayer().getVideoWidth();
+ try {
+ return getCurrentPlayer().getVideoWidth();
+ } catch (IllegalStateException e) {
+ return 0;
+ }
}
synchronized int getVideoHeight() {
- return getCurrentPlayer().getVideoHeight();
+ try {
+ return getCurrentPlayer().getVideoHeight();
+ } catch (IllegalStateException e) {
+ return 0;
+ }
}
synchronized PersistableBundle getMetrics() {
diff --git a/media2/src/main/java/androidx/media2/MediaSession2.java b/media2/src/main/java/androidx/media2/MediaSession2.java
index 20b3162..7cc076f 100644
--- a/media2/src/main/java/androidx/media2/MediaSession2.java
+++ b/media2/src/main/java/androidx/media2/MediaSession2.java
@@ -335,22 +335,6 @@
}
/**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- public void skipForward() {
- mImpl.skipForward();
- }
-
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- public void skipBackward() {
- mImpl.skipBackward();
- }
-
- /**
* Notify routes information to a connected controller
*
* @param controller controller information
@@ -453,7 +437,7 @@
* @see SessionCommand2#COMMAND_CODE_PLAYER_PAUSE
* @see SessionCommand2#COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM
* @see SessionCommand2#COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM
- * @see SessionCommand2#COMMAND_CODE_PLAYER_PREFETCH
+ * @see SessionCommand2#COMMAND_CODE_PLAYER_PREPARE
* @see SessionCommand2#COMMAND_CODE_PLAYER_SEEK_TO
* @see SessionCommand2#COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM
* @see SessionCommand2#COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE
@@ -477,20 +461,18 @@
* Called when a controller has sent a command with a {@link MediaItem2} to add a new media
* item to this session. Being specific, this will be called for following APIs.
* <ol>
- * <li>{@link MediaController2#addPlaylistItem(int, MediaItem2)}
- * <li>{@link MediaController2#replacePlaylistItem(int, MediaItem2)}
+ * <li>{@link MediaController2#addPlaylistItem(int, String)}
+ * <li>{@link MediaController2#replacePlaylistItem(int, String)}
* <li>{@link MediaController2#setPlaylist(List, MediaMetadata2)}
- * <li>{@link MediaController2#setMediaItem(MediaItem2)}
+ * <li>{@link MediaController2#setMediaItem(String)}
* </ol>
- * Override this to translate incoming media items to be understood by your player. It's
- * highly recommended to create and return a new media item here. Otherwise session player
- * may reject the command.
+ * Override this to translate incoming {@code mediaId} to a {@link MediaItem2} to be
+ * understood by your player. For example, a player may only understand
+ * {@link FileMediaItem2}, {@link UriMediaItem2}, and {@link CallbackMediaItem2}. Check the
+ * documentation of the player that you're using.
* <p>
- * For example, a player may only understand {@link FileMediaItem2}, {@link UriMediaItem2},
- * and {@link CallbackMediaItem2} while the media item from the controller would be
- * sanitized not to contain subclass information. In that case you need to override this
- * method to create a media item for player from given media item.
- * <p>
+ * If the given media ID is valid, you should return the media item with the given media ID.
+ * If the ID doesn't match, an {@link RuntimeException} will be thrown.
* You may return {@code null} if the given item is invalid. Here's the behavior when it
* happens.
* <table border="0" cellspacing="0" cellpadding="0">
@@ -506,17 +488,17 @@
* This will be called on the same thread where {@link #onCommandRequest} and commands with
* the media controller will be executed.
* <p>
- * Default implementation simply returns the media item from the controller although the
- * player may ignore or throw an exception.
+ * Default implementation returns the {@code null}.
*
* @param session the session for this event
* @param controller controller information
- * @param item incoming media item from the controller
- * @return translated media item for player. Can be {@code null} to ignore
+ * @param mediaId non-empty media id for creating item with
+ * @return translated media item for player with the mediaId. Can be {@code null} to ignore.
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
*/
public @Nullable MediaItem2 onCreateMediaItem(@NonNull MediaSession2 session,
- @NonNull ControllerInfo controller, @NonNull MediaItem2 item) {
- return item;
+ @NonNull ControllerInfo controller, @NonNull String mediaId) {
+ return null;
}
/**
@@ -530,7 +512,7 @@
*
* @param session the session for this event
* @param controller controller information
- * @param mediaId media id from the controller
+ * @param mediaId non-empty media id
* @param rating new rating from the controller
* @see SessionCommand2#COMMAND_CODE_SESSION_SET_RATING
*/
@@ -569,7 +551,7 @@
*
* @param session the session for this event
* @param controller controller information
- * @param mediaId media id
+ * @param mediaId non-empty media id
* @param extras optional extra bundle
* @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID
* @hide
@@ -584,13 +566,10 @@
/**
* Called when a controller requested to begin playback from a search query through
* {@link MediaController2#playFromSearch(String, Bundle)}
- * <p>
- * An empty query indicates that the app may play any music. The implementation should
- * attempt to make a smart choice about what to play.
*
* @param session the session for this event
* @param controller controller information
- * @param query query string. Can be empty to indicate any suggested media
+ * @param query non-empty search query.
* @param extras optional extra bundle
* @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_SEARCH
* @hide
@@ -621,14 +600,14 @@
}
/**
- * Called when a controller requested to prefetch for playing a specific mediaId through
- * {@link MediaController2#prefetchFromMediaId(String, Bundle)}.
+ * Called when a controller requested to prepare for playing a specific mediaId through
+ * {@link MediaController2#prepareFromMediaId(String, Bundle)}.
* <p>
- * During the prefetch, a session should not hold audio focus in order to allow
+ * During the prepare, a session should not hold audio focus in order to allow
* other sessions play seamlessly. The state of playback should be updated to
- * {@link SessionPlayer2#PLAYER_STATE_PAUSED} after the prefetch is done.
+ * {@link SessionPlayer2#PLAYER_STATE_PAUSED} after the prepare is done.
* <p>
- * The playback of the prefetched content should start in the later calls of
+ * The playback of the prepared content should start in the later calls of
* {@link SessionPlayer2#play()}.
* <p>
* Override {@link #onPlayFromMediaId} to handle requests for starting
@@ -636,30 +615,27 @@
*
* @param session the session for this event
* @param controller controller information
- * @param mediaId media id to prefetch
+ * @param mediaId non-empty media id
* @param extras optional extra bundle
- * @see SessionCommand2#COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID
+ * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public @ResultCode int onPrefetchFromMediaId(@NonNull MediaSession2 session,
+ public @ResultCode int onPrepareFromMediaId(@NonNull MediaSession2 session,
@NonNull ControllerInfo controller, @NonNull String mediaId,
@Nullable Bundle extras) {
return RESULT_CODE_NOT_SUPPORTED;
}
/**
- * Called when a controller requested to prefetch playback from a search query through
- * {@link MediaController2#prefetchFromSearch(String, Bundle)}.
+ * Called when a controller requested to prepare playback from a search query through
+ * {@link MediaController2#prepareFromSearch(String, Bundle)}.
* <p>
- * An empty query indicates that the app may prefetch any music. The implementation should
- * attempt to make a smart choice about what to play.
- * <p>
- * During the prefetch, a session should not hold audio focus in order to allow
+ * During the prepare, a session should not hold audio focus in order to allow
* other sessions play seamlessly. The state of playback should be updated to
- * {@link SessionPlayer2#PLAYER_STATE_PAUSED} after the prefetch is done.
+ * {@link SessionPlayer2#PLAYER_STATE_PAUSED} after the prepare is done.
* <p>
- * The playback of the prefetched content should start in the later calls of
+ * The playback of the prepared content should start in the later calls of
* {@link SessionPlayer2#play()}.
* <p>
* Override {@link #onPlayFromSearch} to handle requests for starting playback without
@@ -667,27 +643,27 @@
*
* @param session the session for this event
* @param controller controller information
- * @param query query string. Can be empty to indicate any suggested media
+ * @param query non-empty search query
* @param extras optional extra bundle
- * @see SessionCommand2#COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH
+ * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public @ResultCode int onPrefetchFromSearch(@NonNull MediaSession2 session,
+ public @ResultCode int onPrepareFromSearch(@NonNull MediaSession2 session,
@NonNull ControllerInfo controller, @NonNull String query,
@Nullable Bundle extras) {
return RESULT_CODE_NOT_SUPPORTED;
}
/**
- * Called when a controller requested to prefetch a specific media item represented by a URI
- * through {@link MediaController2#prefetchFromUri(Uri, Bundle)}.
+ * Called when a controller requested to prepare a specific media item represented by a URI
+ * through {@link MediaController2#prepareFromUri(Uri, Bundle)}.
* <p>
- * During the prefetch, a session should not hold audio focus in order to allow
+ * During the prepare, a session should not hold audio focus in order to allow
* other sessions play seamlessly. The state of playback should be updated to
- * {@link SessionPlayer2#PLAYER_STATE_PAUSED} after the prefetch is done.
+ * {@link SessionPlayer2#PLAYER_STATE_PAUSED} after the prepare is done.
* <p>
- * The playback of the prefetched content should start in the later calls of
+ * The playback of the prepared content should start in the later calls of
* {@link SessionPlayer2#play()}.
* <p>
* Override {@link #onPlayFromUri} to handle requests for starting playback without
@@ -697,17 +673,19 @@
* @param controller controller information
* @param uri uri
* @param extras optional extra bundle
- * @see SessionCommand2#COMMAND_CODE_SESSION_PREFETCH_FROM_URI
+ * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_URI
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public @ResultCode int onPrefetchFromUri(@NonNull MediaSession2 session,
+ public @ResultCode int onPrepareFromUri(@NonNull MediaSession2 session,
@NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) {
return RESULT_CODE_NOT_SUPPORTED;
}
/**
- * Called when a controller called {@link MediaController2#fastForward()}
+ * Called when a controller called {@link MediaController2#fastForward()}.
+ * <p>
+ * It's recommended to increase the playback speed when this method is called.
*
* @param session the session for this event
* @param controller controller information
@@ -719,7 +697,9 @@
}
/**
- * Called when a controller called {@link MediaController2#rewind()}
+ * Called when a controller called {@link MediaController2#rewind()}.
+ * <p>
+ * It's recommended to decrease the playback speed when this method is called.
*
* @param session the session for this event
* @param controller controller information
@@ -731,6 +711,36 @@
}
/**
+ * Called when a controller called {@link MediaController2#skipForward()}.
+ * <p>
+ * It's recommended to seek forward within the current media item when this method
+ * is called.
+ *
+ * @param session the session for this event
+ * @param controller controller information
+ * @see SessionCommand2#COMMAND_CODE_SESSION_SKIP_FORWARD
+ */
+ public @ResultCode int onSkipForward(@NonNull MediaSession2 session,
+ @NonNull ControllerInfo controller) {
+ return RESULT_CODE_NOT_SUPPORTED;
+ }
+
+ /**
+ * Called when a controller called {@link MediaController2#skipBackward()}.
+ * <p>
+ * It's recommended to seek backward within the current media item when this method
+ * is called.
+ *
+ * @param session the session for this event
+ * @param controller controller information
+ * @see SessionCommand2#COMMAND_CODE_SESSION_SKIP_BACKWARD
+ */
+ public @ResultCode int onSkipBackward(@NonNull MediaSession2 session,
+ @NonNull ControllerInfo controller) {
+ return RESULT_CODE_NOT_SUPPORTED;
+ }
+
+ /**
* Called when a controller called {@link MediaController2#subscribeRoutesInfo()}
* Session app should notify the routes information by calling
* {@link MediaSession2#notifyRoutesInfoChanged(ControllerInfo, List)}.
diff --git a/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java b/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java
index 1dc73ad..e02d881 100644
--- a/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java
@@ -16,6 +16,7 @@
package androidx.media2;
+import static androidx.media2.BaseResult2.RESULT_CODE_BAD_VALUE;
import static androidx.media2.MediaSession2.ControllerCb;
import static androidx.media2.MediaSession2.ControllerInfo;
import static androidx.media2.MediaSession2.SessionCallback;
@@ -52,6 +53,7 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.concurrent.futures.AbstractResolvableFuture;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.core.util.ObjectsCompat;
import androidx.media.AudioAttributesCompat;
@@ -69,13 +71,14 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
class MediaSession2ImplBase implements MediaSession2Impl {
private static final String DEFAULT_MEDIA_SESSION_TAG_PREFIX = "android.media.session2.id";
private static final String DEFAULT_MEDIA_SESSION_TAG_DELIM = ".";
static final String TAG = "MS2ImplBase";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean DEBUG = true; //Log.isLoggable(TAG, Log.DEBUG);
// Note: This checks the uniqueness of a session ID only in single process.
// When the framework becomes able to check the uniqueness, this logic should be removed.
@@ -90,7 +93,8 @@
private final MediaSessionCompat mSessionCompat;
private final MediaSession2Stub mSession2Stub;
private final MediaSessionLegacyStub mSessionLegacyStub;
- private final Executor mCallbackExecutor;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Executor mCallbackExecutor;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final SessionCallback mCallback;
private final String mSessionId;
@@ -280,6 +284,9 @@
if (isClosed()) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, "Closing session, id=" + getId() + ", token=" + getToken());
+ }
synchronized (MediaSession2ImplBase.class) {
SESSION_ID_LIST.remove(mSessionId);
}
@@ -408,8 +415,8 @@
// Let dispatchPlayerTask() handle such cases.
return null;
}
- return XMediaPlayer.CombindedCommandResultFuture.create(DIRECT_EXECUTOR,
- prepareFuture, playFuture);
+ return CombinedCommandResultFuture.create(
+ DIRECT_EXECUTOR, prepareFuture, playFuture);
}
});
}
@@ -425,7 +432,7 @@
}
@Override
- public ListenableFuture<PlayerResult> prefetch() {
+ public ListenableFuture<PlayerResult> prepare() {
return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
@Override
public ListenableFuture<PlayerResult> run(SessionPlayer2 player) throws Exception {
@@ -445,18 +452,6 @@
}
@Override
- public ListenableFuture<PlayerResult> skipForward() {
- // To match with KEYCODE_MEDIA_SKIP_FORWARD
- return null;
- }
-
- @Override
- public ListenableFuture<PlayerResult> skipBackward() {
- // To match with KEYCODE_MEDIA_SKIP_BACKWARD
- return null;
- }
-
- @Override
public void notifyRoutesInfoChanged(@NonNull ControllerInfo controller,
@Nullable final List<Bundle> routes) {
dispatchRemoteControllerCallbackTask(controller, new RemoteControllerCallbackTask() {
@@ -586,14 +581,18 @@
}
@Override
- public ListenableFuture<PlayerResult> skipToPlaylistItem(final @NonNull MediaItem2 item) {
+ public ListenableFuture<PlayerResult> skipToPlaylistItem(final int index) {
return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
@Override
public ListenableFuture<PlayerResult> run(SessionPlayer2 player) throws Exception {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ if (index < 0) {
+ throw new IllegalArgumentException("index shouldn't be negative");
}
- return player.skipToPlaylistItem(item);
+ final List<MediaItem2> list = player.getPlaylist();
+ if (index >= list.size()) {
+ return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
+ }
+ return player.skipToPlaylistItem(list.get(index));
}
});
}
@@ -646,14 +645,18 @@
}
@Override
- public ListenableFuture<PlayerResult> removePlaylistItem(final @NonNull MediaItem2 item) {
+ public ListenableFuture<PlayerResult> removePlaylistItem(final int index) {
return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
@Override
public ListenableFuture<PlayerResult> run(SessionPlayer2 player) throws Exception {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ if (index < 0) {
+ throw new IllegalArgumentException("index shouldn't be negative");
}
- return player.removePlaylistItem(item);
+ final List<MediaItem2> list = player.getPlaylist();
+ if (index >= list.size()) {
+ return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
+ }
+ return player.removePlaylistItem(list.get(index));
}
});
}
@@ -1135,9 +1138,15 @@
private static class SessionPlayerCallback extends SessionPlayer2.PlayerCallback {
private final WeakReference<MediaSession2ImplBase> mSession;
+ private MediaItem2 mMediaItem;
+ private List<MediaItem2> mList;
+ private final CurrentMediaItemListener mCurrentItemChangedListener;
+ private final PlaylistItemListener mPlaylistItemChangedListener;
SessionPlayerCallback(MediaSession2ImplBase session) {
mSession = new WeakReference<>(session);
+ mCurrentItemChangedListener = new CurrentMediaItemListener(session);
+ mPlaylistItemChangedListener = new PlaylistItemListener(session);
}
@Override
@@ -1146,6 +1155,17 @@
if (session == null || session.getPlayer() != player || player == null) {
return;
}
+ synchronized (session.mLock) {
+ if (mMediaItem != null) {
+ mMediaItem.removeOnMetadataChangedListener(mCurrentItemChangedListener);
+ }
+ if (item != null) {
+ item.addOnMetadataChangedListener(session.mCallbackExecutor,
+ mCurrentItemChangedListener);
+ }
+ mMediaItem = item;
+ }
+
// Note: No sanity check whether the item is in the playlist.
updateDurationIfNeeded(player, item);
session.dispatchRemoteControllerCallbackTask(new RemoteControllerCallbackTask() {
@@ -1210,6 +1230,25 @@
@Override
public void onPlaylistChanged(final SessionPlayer2 player, final List<MediaItem2> list,
final MediaMetadata2 metadata) {
+ final MediaSession2ImplBase session = getSession();
+ if (session == null || session.getPlayer() != player || player == null) {
+ return;
+ }
+ synchronized (session.mLock) {
+ if (mList != null) {
+ for (int i = 0; i < mList.size(); i++) {
+ mList.get(i).removeOnMetadataChangedListener(mPlaylistItemChangedListener);
+ }
+ }
+ if (list != null) {
+ for (int i = 0; i < list.size(); i++) {
+ list.get(i).addOnMetadataChangedListener(session.mCallbackExecutor,
+ mPlaylistItemChangedListener);
+ }
+ }
+ mList = list;
+ }
+
dispatchRemoteControllerTask(player, new RemoteControllerCallbackTask() {
@Override
public void run(ControllerCb callback) throws RemoteException {
@@ -1334,6 +1373,9 @@
.putLong(MediaMetadata2.METADATA_KEY_DURATION, duration)
.putString(MediaMetadata2.METADATA_KEY_MEDIA_ID,
item.getMediaId())
+ .putLong(MediaMetadata2.METADATA_KEY_BROWSABLE,
+ MediaMetadata2.BROWSABLE_TYPE_NONE)
+ .putLong(MediaMetadata2.METADATA_KEY_PLAYABLE, 1)
.build();
}
if (metadata != null) {
@@ -1348,4 +1390,112 @@
}
}
}
+
+ static class CurrentMediaItemListener implements MediaItem2.OnMetadataChangedListener {
+ private final WeakReference<MediaSession2ImplBase> mSession;
+
+ CurrentMediaItemListener(MediaSession2ImplBase session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @Override
+ public void onMetadataChanged(final MediaItem2 item) {
+ final MediaSession2ImplBase session = mSession.get();
+ if (session == null || item == null) {
+ return;
+ }
+ final MediaItem2 currentItem = session.getCurrentMediaItem();
+ if (currentItem != null && item.equals(currentItem)) {
+ session.dispatchRemoteControllerCallbackTask(new RemoteControllerCallbackTask() {
+ @Override
+ public void run(ControllerCb callback) throws RemoteException {
+ callback.onCurrentMediaItemChanged(item);
+ }
+ });
+ }
+ }
+ }
+
+ static class PlaylistItemListener implements MediaItem2.OnMetadataChangedListener {
+ private final WeakReference<MediaSession2ImplBase> mSession;
+
+ PlaylistItemListener(MediaSession2ImplBase session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @Override
+ public void onMetadataChanged(final MediaItem2 item) {
+ final MediaSession2ImplBase session = mSession.get();
+ if (session == null || item == null) {
+ return;
+ }
+ final List<MediaItem2> list = session.getPlaylist();
+ if (list == null) {
+ return;
+ }
+ for (int i = 0; i < list.size(); i++) {
+ if (item.equals(list.get(i))) {
+ session.dispatchRemoteControllerCallbackTask(
+ new RemoteControllerCallbackTask() {
+ @Override
+ public void run(ControllerCb callback) throws RemoteException {
+ callback.onPlaylistChanged(list, session.getPlaylistMetadata());
+ }
+ });
+ return;
+ }
+ }
+ }
+ }
+
+ static final class CombinedCommandResultFuture<T extends BaseResult2>
+ extends AbstractResolvableFuture<T> {
+ final ListenableFuture<T>[] mFutures;
+ AtomicInteger mSuccessCount = new AtomicInteger(0);
+
+ public static <U extends BaseResult2> CombinedCommandResultFuture create(
+ Executor executor, ListenableFuture<U>... futures) {
+ return new CombinedCommandResultFuture<U>(executor, futures);
+ }
+
+ private CombinedCommandResultFuture(Executor executor,
+ ListenableFuture<T>[] futures) {
+ mFutures = futures;
+ for (int i = 0; i < mFutures.length; ++i) {
+ final int cur = i;
+ mFutures[i].addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ T result = mFutures[cur].get();
+ int resultCode = result.getResultCode();
+ if (resultCode != RESULT_CODE_SUCCESS
+ && resultCode != RESULT_CODE_SKIPPED) {
+ for (int j = 0; j < mFutures.length; ++j) {
+ if (!mFutures[j].isCancelled() && !mFutures[j].isDone()
+ && cur != j) {
+ mFutures[j].cancel(true);
+ }
+ }
+ set(result);
+ } else {
+ int cnt = mSuccessCount.incrementAndGet();
+ if (cnt == mFutures.length) {
+ set(result);
+ }
+ }
+ } catch (Exception e) {
+ for (int j = 0; j < mFutures.length; ++j) {
+ if (!mFutures[j].isCancelled() && !mFutures[j].isDone()
+ && cur != j) {
+ mFutures[j].cancel(true);
+ }
+ }
+ setException(e);
+ }
+ }
+ }, executor);
+ }
+ }
+ }
}
diff --git a/media2/src/main/java/androidx/media2/MediaSession2Stub.java b/media2/src/main/java/androidx/media2/MediaSession2Stub.java
index 58794d4..39ec86e 100644
--- a/media2/src/main/java/androidx/media2/MediaSession2Stub.java
+++ b/media2/src/main/java/androidx/media2/MediaSession2Stub.java
@@ -51,10 +51,10 @@
import androidx.media2.MediaSession2.ControllerInfo;
import androidx.media2.MediaSession2.MediaSession2Impl;
import androidx.media2.MediaSession2.SessionResult;
+import androidx.media2.MediaSession2ImplBase.PlayerTask;
import androidx.media2.SessionCommand2.CommandCode;
import androidx.media2.SessionPlayer2.PlayerResult;
import androidx.versionedparcelable.ParcelImpl;
-import androidx.versionedparcelable.ParcelUtils;
import com.google.common.util.concurrent.ListenableFuture;
@@ -174,7 +174,7 @@
final @CommandCode int commandCode,
final @NonNull SessionTask task) {
final ControllerInfo controller = mConnectedControllersManager.getController(
- caller == null ? null : caller.asBinder());
+ caller.asBinder());
if (mSessionImpl.isClosed() || controller == null) {
return;
}
@@ -296,6 +296,16 @@
// (e.g. add pagination or special way to deliver Bitmap)
// - DeadSystemException means that errors around it can be ignored.
Log.w(TAG, "Exception in " + controller.toString(), e);
+ } catch (Exception e) {
+ // Any random exception may be happen inside of the session player / callback.
+ if (task instanceof PlayerTask) {
+ sendPlayerResult(controller, seq,
+ new PlayerResult(PlayerResult.RESULT_CODE_UNKNOWN_ERROR, null));
+ } else if (task instanceof SessionCallbackTask) {
+ sendSessionResult(controller, seq, SessionResult.RESULT_CODE_UNKNOWN_ERROR);
+ } else if (task instanceof LibrarySessionCallbackTask) {
+ sendLibraryResult(controller, seq, LibraryResult.RESULT_CODE_UNKNOWN_ERROR);
+ }
}
}
});
@@ -359,14 +369,14 @@
// because IMediaController2 is oneway (i.e. async call) and Stub will
// use thread poll for incoming calls.
final int playerState = mSessionImpl.getPlayerState();
- final ParcelImpl currentItem = (ParcelImpl) ParcelUtils.toParcelable(
+ final ParcelImpl currentItem = MediaUtils2.toParcelable(
mSessionImpl.getCurrentMediaItem());
final long positionEventTimeMs = SystemClock.elapsedRealtime();
final long positionMs = mSessionImpl.getCurrentPosition();
final float playbackSpeed = mSessionImpl.getPlaybackSpeed();
final long bufferedPositionMs = mSessionImpl.getBufferedPosition();
final ParcelImpl playbackInfo =
- (ParcelImpl) ParcelUtils.toParcelable(mSessionImpl.getPlaybackInfo());
+ MediaUtils2.toParcelable(mSessionImpl.getPlaybackInfo());
final int repeatMode = mSessionImpl.getRepeatMode();
final int shuffleMode = mSessionImpl.getShuffleMode();
final PendingIntent sessionActivity = mSessionImpl.getSessionActivity();
@@ -384,7 +394,7 @@
}
try {
caller.onConnected(MediaSession2Stub.this,
- (ParcelImpl) ParcelUtils.toParcelable(allowedCommands),
+ MediaUtils2.toParcelable(allowedCommands),
playerState, currentItem, positionEventTimeMs, positionMs,
playbackSpeed, bufferedPositionMs, playbackInfo, repeatMode,
shuffleMode, playlistSlice, sessionActivity);
@@ -412,22 +422,21 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
@Nullable
MediaItem2 convertMediaItem2OnExecutor(ControllerInfo controller,
- @NonNull ParcelImpl itemParcelImpl) {
- if (itemParcelImpl == null) {
- return null;
- }
- MediaItem2 item = ParcelUtils.fromParcelable(itemParcelImpl);
- if (item == null) {
- Log.w(TAG, "Couldn't convert incoming parcelable to MediaItem2 ");
+ @NonNull String mediaId) {
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "Media ID shouldn't be null");
return null;
}
MediaItem2 newItem = mSessionImpl.getCallback().onCreateMediaItem(
- mSessionImpl.getInstance(), controller, item);
+ mSessionImpl.getInstance(), controller, mediaId);
if (newItem == null) {
- Log.w(TAG, "onCreateMediaItem(" + newItem + ") returned null");
- return null;
+ Log.w(TAG, "onCreateMediaItem(mediaId=" + mediaId + ") returned null. Ignoring");
+ } else if (newItem.getMetadata() == null
+ || !TextUtils.equals(mediaId,
+ newItem.getMetadata().getString(MediaMetadata2.METADATA_KEY_MEDIA_ID))) {
+ throw new RuntimeException("onCreateMediaItem(mediaId=" + mediaId + "): media ID in the"
+ + " returned media item should match");
}
- newItem.mParcelUuid = item.mParcelUuid;
return newItem;
}
@@ -438,29 +447,41 @@
@Override
public void connect(final IMediaController2 caller, int seq, final String callingPackage)
throws RuntimeException {
+ if (caller == null || TextUtils.isEmpty(callingPackage)) {
+ return;
+ }
connect(caller, callingPackage, Binder.getCallingPid(), Binder.getCallingUid());
}
@Override
public void release(final IMediaController2 caller, int seq) throws RemoteException {
- mConnectedControllersManager.removeController(caller == null ? null : caller.asBinder());
+ if (caller == null) {
+ return;
+ }
+ mConnectedControllersManager.removeController(caller.asBinder());
}
@Override
public void onControllerResult(final IMediaController2 caller, int seq,
final ParcelImpl controllerResult) {
+ if (caller == null || controllerResult == null) {
+ return;
+ }
SequencedFutureManager manager = mConnectedControllersManager.getSequencedFutureManager(
caller.asBinder());
if (manager == null) {
return;
}
- MediaController2.ControllerResult result = ParcelUtils.fromParcelable(controllerResult);
+ MediaController2.ControllerResult result = MediaUtils2.fromParcelable(controllerResult);
manager.setFutureResult(seq, SessionResult.from(result));
}
@Override
public void setVolumeTo(final IMediaController2 caller, int seq, final int value,
final int flags) throws RuntimeException {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_VOLUME_SET_VOLUME,
new SessionCallbackTask<Integer>() {
@Override
@@ -478,6 +499,9 @@
@Override
public void adjustVolume(IMediaController2 caller, int seq, final int direction,
final int flags) throws RuntimeException {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_VOLUME_ADJUST_VOLUME,
new SessionCallbackTask<Integer>() {
@Override
@@ -494,6 +518,9 @@
@Override
public void play(IMediaController2 caller, int seq) throws RuntimeException {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_PLAY,
new SessionPlayerTask() {
@Override
@@ -505,6 +532,9 @@
@Override
public void pause(IMediaController2 caller, int seq) throws RuntimeException {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_PAUSE,
new SessionPlayerTask() {
@Override
@@ -515,18 +545,24 @@
}
@Override
- public void prefetch(IMediaController2 caller, int seq) throws RuntimeException {
- dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_PREFETCH,
+ public void prepare(IMediaController2 caller, int seq) throws RuntimeException {
+ if (caller == null) {
+ return;
+ }
+ dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_PREPARE,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- return mSessionImpl.prefetch();
+ return mSessionImpl.prepare();
}
});
}
@Override
public void fastForward(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_FAST_FORWARD,
new SessionCallbackTask<Integer>() {
@Override
@@ -539,6 +575,9 @@
@Override
public void rewind(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_REWIND,
new SessionCallbackTask<Integer>() {
@Override
@@ -550,7 +589,40 @@
}
@Override
+ public void skipForward(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
+ dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_SKIP_FORWARD,
+ new SessionCallbackTask<Integer>() {
+ @Override
+ public Integer run(ControllerInfo controller) {
+ return mSessionImpl.getCallback().onSkipForward(
+ mSessionImpl.getInstance(), controller);
+ }
+ });
+ }
+
+ @Override
+ public void skipBackward(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
+ dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_SKIP_BACKWARD,
+ new SessionCallbackTask<Integer>() {
+ @Override
+ public Integer run(ControllerInfo controller) {
+ return mSessionImpl.getCallback().onSkipBackward(
+ mSessionImpl.getInstance(), controller);
+ }
+ });
+ }
+
+ @Override
public void seekTo(IMediaController2 caller, int seq, final long pos) throws RuntimeException {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SEEK_TO,
new SessionPlayerTask() {
@Override
@@ -563,7 +635,10 @@
@Override
public void onCustomCommand(final IMediaController2 caller, final int seq,
final ParcelImpl command, final Bundle args) {
- final SessionCommand2 sessionCommand = ParcelUtils.fromParcelable(command);
+ if (caller == null || command == null) {
+ return;
+ }
+ final SessionCommand2 sessionCommand = MediaUtils2.fromParcelable(command);
dispatchSessionTask(caller, seq, sessionCommand, new SessionCallbackTask<SessionResult>() {
@Override
public SessionResult run(final ControllerInfo controller) {
@@ -583,54 +658,63 @@
}
@Override
- public void prefetchFromUri(final IMediaController2 caller, int seq, final Uri uri,
+ public void prepareFromUri(final IMediaController2 caller, int seq, final Uri uri,
final Bundle extras) {
- dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_URI,
+ if (caller == null) {
+ return;
+ }
+ dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI,
new SessionCallbackTask<Integer>() {
@Override
public Integer run(ControllerInfo controller) {
if (uri == null) {
- Log.w(TAG, "prefetchFromUri(): Ignoring null uri from " + controller);
+ Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
return RESULT_CODE_BAD_VALUE;
}
- return mSessionImpl.getCallback().onPrefetchFromUri(
+ return mSessionImpl.getCallback().onPrepareFromUri(
mSessionImpl.getInstance(), controller, uri, extras);
}
});
}
@Override
- public void prefetchFromSearch(final IMediaController2 caller, int seq, final String query,
+ public void prepareFromSearch(final IMediaController2 caller, int seq, final String query,
final Bundle extras) {
- dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH,
+ if (caller == null) {
+ return;
+ }
+ dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH,
new SessionCallbackTask<Integer>() {
@Override
public Integer run(ControllerInfo controller) {
if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "prefetchFromSearch(): Ignoring empty query from "
+ Log.w(TAG, "prepareFromSearch(): Ignoring empty query from "
+ controller);
return RESULT_CODE_BAD_VALUE;
}
- return mSessionImpl.getCallback().onPrefetchFromSearch(
+ return mSessionImpl.getCallback().onPrepareFromSearch(
mSessionImpl.getInstance(), controller, query, extras);
}
});
}
@Override
- public void prefetchFromMediaId(final IMediaController2 caller, int seq, final String mediaId,
+ public void prepareFromMediaId(final IMediaController2 caller, int seq, final String mediaId,
final Bundle extras) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq,
- SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID,
+ SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID,
new SessionCallbackTask<Integer>() {
@Override
public Integer run(ControllerInfo controller) {
- if (mediaId == null) {
- Log.w(TAG, "prefetchFromMediaId(): Ignoring null mediaId from "
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "prepareFromMediaId(): Ignoring empty mediaId from "
+ controller);
return RESULT_CODE_BAD_VALUE;
}
- return mSessionImpl.getCallback().onPrefetchFromMediaId(
+ return mSessionImpl.getCallback().onPrepareFromMediaId(
mSessionImpl.getInstance(), controller, mediaId, extras);
}
});
@@ -639,6 +723,9 @@
@Override
public void playFromUri(final IMediaController2 caller, int seq, final Uri uri,
final Bundle extras) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI,
new SessionCallbackTask<Integer>() {
@Override
@@ -656,6 +743,9 @@
@Override
public void playFromSearch(final IMediaController2 caller, int seq, final String query,
final Bundle extras) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH,
new SessionCallbackTask<Integer>() {
@Override
@@ -673,13 +763,16 @@
@Override
public void playFromMediaId(final IMediaController2 caller, int seq, final String mediaId,
final Bundle extras) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID,
new SessionCallbackTask<Integer>() {
@Override
public Integer run(ControllerInfo controller) {
- if (mediaId == null) {
- Log.w(TAG,
- "playFromMediaId(): Ignoring null mediaId from " + controller);
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "playFromMediaId(): Ignoring empty mediaId from "
+ + controller);
return RESULT_CODE_BAD_VALUE;
}
return mSessionImpl.getCallback().onPlayFromMediaId(
@@ -691,18 +784,20 @@
@Override
public void setRating(final IMediaController2 caller, int seq, final String mediaId,
final ParcelImpl rating) {
- final Rating2 rating2 = ParcelUtils.fromParcelable(rating);
+ if (caller == null || rating == null) {
+ return;
+ }
+ final Rating2 rating2 = MediaUtils2.fromParcelable(rating);
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_SET_RATING,
new SessionCallbackTask<Integer>() {
@Override
public Integer run(ControllerInfo controller) {
- if (mediaId == null) {
- Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "setRating(): Ignoring empty mediaId from " + controller);
return RESULT_CODE_BAD_VALUE;
}
if (rating2 == null) {
- Log.w(TAG,
- "setRating(): Ignoring null ratingBundle from " + controller);
+ Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
return RESULT_CODE_BAD_VALUE;
}
return mSessionImpl.getCallback().onSetRating(
@@ -713,6 +808,9 @@
@Override
public void setPlaybackSpeed(final IMediaController2 caller, int seq, final float speed) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SET_SPEED,
new SessionPlayerTask() {
@Override
@@ -723,17 +821,19 @@
}
@Override
- public void setPlaylist(final IMediaController2 caller, int seq,
- final ParcelImplListSlice listSlice, final Bundle metadata) {
+ public void setPlaylist(final IMediaController2 caller, int seq, final List<String> playlist,
+ final ParcelImpl metadata) {
+ if (caller == null || metadata == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SET_PLAYLIST,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- if (listSlice == null) {
+ if (playlist == null) {
Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
- List<ParcelImpl> playlist = listSlice.getList();
List<MediaItem2> list = new ArrayList<>();
for (int i = 0; i < playlist.size(); i++) {
MediaItem2 item = convertMediaItem2OnExecutor(controller,
@@ -742,22 +842,26 @@
list.add(item);
}
}
- return mSessionImpl.setPlaylist(list, MediaMetadata2.fromBundle(metadata));
+ return mSessionImpl.setPlaylist(list,
+ (MediaMetadata2) MediaUtils2.fromParcelable(metadata));
}
});
}
@Override
- public void setMediaItem(final IMediaController2 caller, int seq, final ParcelImpl mediaItem) {
+ public void setMediaItem(final IMediaController2 caller, int seq, final String mediaId) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- if (mediaItem == null) {
- Log.w(TAG, "setMediaItem(): Ignoring null item from " + controller);
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "setMediaItem(): Ignoring empty mediaId from " + controller);
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
- MediaItem2 item = convertMediaItem2OnExecutor(controller, mediaItem);
+ MediaItem2 item = convertMediaItem2OnExecutor(controller, mediaId);
if (item == null) {
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
@@ -768,29 +872,36 @@
@Override
public void updatePlaylistMetadata(final IMediaController2 caller, int seq,
- final Bundle metadata) {
+ final ParcelImpl metadata) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
return mSessionImpl.updatePlaylistMetadata(
- MediaMetadata2.fromBundle(metadata));
+ (MediaMetadata2) MediaUtils2.fromParcelable(metadata));
}
});
}
@Override
public void addPlaylistItem(IMediaController2 caller, int seq, final int index,
- final ParcelImpl mediaItem) {
+ final String mediaId) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- if (mediaItem == null) {
- Log.w(TAG, "addPlaylistItem(): Ignoring null item from " + controller);
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "addPlaylistItem(): Ignoring empty mediaId from "
+ + controller);
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
- MediaItem2 item = convertMediaItem2OnExecutor(controller, mediaItem);
+ MediaItem2 item = convertMediaItem2OnExecutor(controller, mediaId);
if (item == null) {
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
@@ -800,31 +911,35 @@
}
@Override
- public void removePlaylistItem(IMediaController2 caller, int seq, final ParcelImpl mediaItem) {
+ public void removePlaylistItem(IMediaController2 caller, int seq, final int index) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- MediaItem2 item = ParcelUtils.fromParcelable(mediaItem);
- // Note: MediaItem2 has hidden UUID to identify it across the processes.
- return mSessionImpl.removePlaylistItem(item);
+ return mSessionImpl.removePlaylistItem(index);
}
});
}
@Override
public void replacePlaylistItem(IMediaController2 caller, int seq, final int index,
- final ParcelImpl mediaItem) {
+ final String mediaId) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- if (mediaItem == null) {
- Log.w(TAG, "replacePlaylistItem(): Ignoring null item from "
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "replacePlaylistItem(): Ignoring empty mediaId from "
+ controller);
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
- MediaItem2 item = convertMediaItem2OnExecutor(controller, mediaItem);
+ MediaItem2 item = convertMediaItem2OnExecutor(controller, mediaId);
if (item == null) {
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
@@ -834,25 +949,29 @@
}
@Override
- public void skipToPlaylistItem(IMediaController2 caller, int seq, final ParcelImpl mediaItem) {
+ public void skipToPlaylistItem(IMediaController2 caller, int seq, final int index) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
new SessionPlayerTask() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- if (mediaItem == null) {
- Log.w(TAG, "skipToPlaylistItem(): Ignoring null mediaItem from "
+ if (index < 0) {
+ Log.w(TAG, "skipToPlaylistItem(): Ignoring negative index from "
+ controller);
return PlayerResult.createFuture(RESULT_CODE_BAD_VALUE);
}
- // Note: MediaItem2 has hidden UUID to identify it across the processes.
- return mSessionImpl.skipToPlaylistItem(
- (MediaItem2) ParcelUtils.fromParcelable(mediaItem));
+ return mSessionImpl.skipToPlaylistItem(index);
}
});
}
@Override
public void skipToPreviousItem(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq,
SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
new SessionPlayerTask() {
@@ -865,6 +984,9 @@
@Override
public void skipToNextItem(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq,
SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
new SessionPlayerTask() {
@@ -877,6 +999,9 @@
@Override
public void setRepeatMode(IMediaController2 caller, int seq, final int repeatMode) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SET_REPEAT_MODE,
new SessionPlayerTask() {
@Override
@@ -888,6 +1013,9 @@
@Override
public void setShuffleMode(IMediaController2 caller, int seq, final int shuffleMode) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE,
new SessionPlayerTask() {
@Override
@@ -899,6 +1027,9 @@
@Override
public void subscribeRoutesInfo(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq, SessionCommand2.COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO,
new SessionCallbackTask<Integer>() {
@Override
@@ -911,6 +1042,9 @@
@Override
public void unsubscribeRoutesInfo(IMediaController2 caller, int seq) {
+ if (caller == null) {
+ return;
+ }
dispatchSessionTask(caller, seq,
SessionCommand2.COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO,
new SessionCallbackTask<Integer>() {
@@ -924,7 +1058,11 @@
@Override
public void selectRoute(IMediaController2 caller, int seq, final Bundle route) {
+ if (caller == null) {
+ return;
+ }
if (MediaUtils2.isUnparcelableBundle(route)) {
+ // TODO (b/118472216): Prevent app crash from illegal binder call.
throw new RuntimeException("Unexpected route bundle: " + route);
}
dispatchSessionTask(caller, seq,
@@ -953,13 +1091,16 @@
@Override
public void getLibraryRoot(final IMediaController2 caller, int seq,
final ParcelImpl libraryParams) throws RuntimeException {
+ if (caller == null || libraryParams == null) {
+ return;
+ }
dispatchLibrarySessionTask(caller, seq,
SessionCommand2.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT,
new LibrarySessionCallbackTask<LibraryResult>() {
@Override
public LibraryResult run(ControllerInfo controller) {
return getLibrarySession().onGetLibraryRootOnExecutor(controller,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
});
}
@@ -984,6 +1125,9 @@
public void getChildren(final IMediaController2 caller, int seq, final String parentId,
final int page, final int pageSize, final ParcelImpl libraryParams)
throws RuntimeException {
+ if (caller == null || libraryParams == null) {
+ return;
+ }
dispatchLibrarySessionTask(caller, seq, SessionCommand2.COMMAND_CODE_LIBRARY_GET_CHILDREN,
new LibrarySessionCallbackTask<LibraryResult>() {
@Override
@@ -1003,7 +1147,7 @@
}
return getLibrarySession().onGetChildrenOnExecutor(controller, parentId,
page, pageSize,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
});
}
@@ -1011,6 +1155,9 @@
@Override
public void search(IMediaController2 caller, int seq, final String query,
final ParcelImpl libraryParams) {
+ if (caller == null || libraryParams == null) {
+ return;
+ }
dispatchLibrarySessionTask(caller, seq, SessionCommand2.COMMAND_CODE_LIBRARY_SEARCH,
new LibrarySessionCallbackTask<Integer>() {
@Override
@@ -1020,7 +1167,7 @@
return LibraryResult.RESULT_CODE_BAD_VALUE;
}
return getLibrarySession().onSearchOnExecutor(controller, query,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
});
}
@@ -1028,6 +1175,9 @@
@Override
public void getSearchResult(final IMediaController2 caller, int seq, final String query,
final int page, final int pageSize, final ParcelImpl libraryParams) {
+ if (caller == null || libraryParams == null) {
+ return;
+ }
dispatchLibrarySessionTask(caller, seq,
SessionCommand2.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT,
new LibrarySessionCallbackTask<LibraryResult>() {
@@ -1050,7 +1200,7 @@
}
return getLibrarySession().onGetSearchResultOnExecutor(controller,
query, page, pageSize,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
});
}
@@ -1058,6 +1208,9 @@
@Override
public void subscribe(final IMediaController2 caller, int seq, final String parentId,
final ParcelImpl libraryParams) {
+ if (caller == null || libraryParams == null) {
+ return;
+ }
dispatchLibrarySessionTask(caller, seq, SessionCommand2.COMMAND_CODE_LIBRARY_SUBSCRIBE,
new LibrarySessionCallbackTask<Integer>() {
@Override
@@ -1068,19 +1221,22 @@
}
return getLibrarySession().onSubscribeOnExecutor(
controller, parentId,
- (LibraryParams) ParcelUtils.fromParcelable(libraryParams));
+ (LibraryParams) MediaUtils2.fromParcelable(libraryParams));
}
});
}
@Override
public void unsubscribe(final IMediaController2 caller, int seq, final String parentId) {
+ if (caller == null) {
+ return;
+ }
dispatchLibrarySessionTask(caller, seq, SessionCommand2.COMMAND_CODE_LIBRARY_UNSUBSCRIBE,
new LibrarySessionCallbackTask<Integer>() {
@Override
public Integer run(ControllerInfo controller) {
- if (parentId == null) {
- Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
+ if (TextUtils.isEmpty(parentId)) {
+ Log.w(TAG, "unsubscribe(): Ignoring empty parentId from " + controller);
return LibraryResult.RESULT_CODE_BAD_VALUE;
}
return getLibrarySession().onUnsubscribeOnExecutor(controller, parentId);
@@ -1132,8 +1288,7 @@
if (result == null) {
result = new SessionResult(RESULT_CODE_UNKNOWN_ERROR, null);
}
- mIControllerCallback.onSessionResult(seq,
- (ParcelImpl) ParcelUtils.toParcelable(result));
+ mIControllerCallback.onSessionResult(seq, MediaUtils2.toParcelable(result));
}
@Override
@@ -1141,8 +1296,7 @@
if (result == null) {
result = new LibraryResult(LibraryResult.RESULT_CODE_UNKNOWN_ERROR);
}
- mIControllerCallback.onLibraryResult(seq,
- (ParcelImpl) ParcelUtils.toParcelable(result));
+ mIControllerCallback.onLibraryResult(seq, MediaUtils2.toParcelable(result));
}
@Override
@@ -1153,20 +1307,18 @@
@Override
void onPlaybackInfoChanged(PlaybackInfo info) throws RemoteException {
- mIControllerCallback.onPlaybackInfoChanged((ParcelImpl) ParcelUtils.toParcelable(info));
+ mIControllerCallback.onPlaybackInfoChanged(MediaUtils2.toParcelable(info));
}
@Override
void onAllowedCommandsChanged(SessionCommandGroup2 commands) throws RemoteException {
- mIControllerCallback.onAllowedCommandsChanged(
- (ParcelImpl) ParcelUtils.toParcelable(commands));
+ mIControllerCallback.onAllowedCommandsChanged(MediaUtils2.toParcelable(commands));
}
@Override
void sendCustomCommand(int seq, SessionCommand2 command, Bundle args)
throws RemoteException {
- mIControllerCallback.onCustomCommand(seq,
- (ParcelImpl) ParcelUtils.toParcelable(command), args);
+ mIControllerCallback.onCustomCommand(seq, MediaUtils2.toParcelable(command), args);
}
@Override
@@ -1184,8 +1336,7 @@
@Override
void onBufferingStateChanged(MediaItem2 item, int bufferingState, long bufferedPositionMs)
throws RemoteException {
- mIControllerCallback.onBufferingStateChanged(
- (ParcelImpl) ParcelUtils.toParcelable(item),
+ mIControllerCallback.onBufferingStateChanged(MediaUtils2.toParcelable(item),
bufferingState, bufferedPositionMs);
}
@@ -1197,8 +1348,7 @@
@Override
void onCurrentMediaItemChanged(MediaItem2 item) throws RemoteException {
- mIControllerCallback.onCurrentMediaItemChanged(
- (ParcelImpl) ParcelUtils.toParcelable(item));
+ mIControllerCallback.onCurrentMediaItemChanged(MediaUtils2.toParcelable(item));
}
@Override
@@ -1210,10 +1360,10 @@
SessionCommand2.COMMAND_CODE_PLAYER_GET_PLAYLIST)) {
mIControllerCallback.onPlaylistChanged(
MediaUtils2.convertMediaItem2ListToParcelImplListSlice(playlist),
- metadata == null ? null : metadata.toBundle());
+ MediaUtils2.toParcelable(metadata));
} else if (mConnectedControllersManager.isAllowedCommand(controller,
SessionCommand2.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA)) {
- mIControllerCallback.onPlaylistMetadataChanged(metadata.toBundle());
+ mIControllerCallback.onPlaylistMetadataChanged(MediaUtils2.toParcelable(metadata));
}
}
@@ -1223,7 +1373,7 @@
getCallbackBinder());
if (mConnectedControllersManager.isAllowedCommand(controller,
SessionCommand2.COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA)) {
- mIControllerCallback.onPlaylistMetadataChanged(metadata.toBundle());
+ mIControllerCallback.onPlaylistMetadataChanged(MediaUtils2.toParcelable(metadata));
}
}
@@ -1251,14 +1401,14 @@
void onChildrenChanged(String parentId, int itemCount, LibraryParams params)
throws RemoteException {
mIControllerCallback.onChildrenChanged(parentId, itemCount,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
}
@Override
void onSearchResultChanged(String query, int itemCount, LibraryParams params)
throws RemoteException {
mIControllerCallback.onSearchResultChanged(query, itemCount,
- (ParcelImpl) ParcelUtils.toParcelable(params));
+ MediaUtils2.toParcelable(params));
}
@Override
diff --git a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
index 98aaea8..284c36b 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
@@ -119,24 +119,26 @@
@Override
public void onPrepare() {
- dispatchSessionTask(SessionCommand2.COMMAND_CODE_PLAYER_PREFETCH, new SessionTask() {
+ dispatchSessionTask(SessionCommand2.COMMAND_CODE_PLAYER_PREPARE, new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
- mSessionImpl.prefetch();
+ mSessionImpl.prepare();
}
});
}
@Override
public void onPrepareFromMediaId(final String mediaId, final Bundle extras) {
- if (mediaId == null) {
- return;
- }
- dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID,
+ dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID,
new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
- mSessionImpl.getCallback().onPrefetchFromMediaId(mSessionImpl.getInstance(),
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "onPrepareFromMediaId(): Ignoring empty mediaId from "
+ + controller);
+ return;
+ }
+ mSessionImpl.getCallback().onPrepareFromMediaId(mSessionImpl.getInstance(),
controller, mediaId, extras);
}
});
@@ -144,14 +146,16 @@
@Override
public void onPrepareFromSearch(final String query, final Bundle extras) {
- if (query == null) {
- return;
- }
- dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH,
+ dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH,
new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
- mSessionImpl.getCallback().onPrefetchFromSearch(mSessionImpl.getInstance(),
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "onPrepareFromSearch(): Ignoring empty query from "
+ + controller);
+ return;
+ }
+ mSessionImpl.getCallback().onPrepareFromSearch(mSessionImpl.getInstance(),
controller, query, extras);
}
});
@@ -162,11 +166,11 @@
if (uri == null) {
return;
}
- dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PREFETCH_FROM_URI,
+ dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI,
new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
- mSessionImpl.getCallback().onPrefetchFromUri(mSessionImpl.getInstance(),
+ mSessionImpl.getCallback().onPrepareFromUri(mSessionImpl.getInstance(),
controller, uri, extras);
}
});
@@ -184,13 +188,15 @@
@Override
public void onPlayFromMediaId(final String mediaId, final Bundle extras) {
- if (mediaId == null) {
- return;
- }
dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID,
new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "onPlayFromMediaId(): Ignoring empty mediaId from "
+ + controller);
+ return;
+ }
mSessionImpl.getCallback().onPlayFromMediaId(mSessionImpl.getInstance(),
controller, mediaId, extras);
}
@@ -199,13 +205,15 @@
@Override
public void onPlayFromSearch(final String query, final Bundle extras) {
- if (query == null) {
- return;
- }
dispatchSessionTask(SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH,
new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "onPlayFromSearch(): Ignoring empty query from "
+ + controller);
+ return;
+ }
mSessionImpl.getCallback().onPlayFromSearch(mSessionImpl.getInstance(),
controller, query, extras);
}
@@ -290,7 +298,7 @@
}
@Override
- public void onSkipToQueueItem(final long id) {
+ public void onSkipToQueueItem(final long queueId) {
dispatchSessionTask(SessionCommand2.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
new SessionTask() {
@Override
@@ -299,13 +307,9 @@
if (playlist == null) {
return;
}
- for (int i = 0; i < playlist.size(); i++) {
- MediaItem2 item = playlist.get(i);
- if (item != null && item.getUuid().getMostSignificantBits() == id) {
- mSessionImpl.skipToPlaylistItem(item);
- break;
- }
- }
+ // Use queueId as an index as we've published {@link QueueItem} as so.
+ // see: {@link MediaUtils2#convertToQueueItemList}.
+ mSessionImpl.skipToPlaylistItem((int) queueId);
}
});
}
@@ -393,18 +397,7 @@
@Override
public void onAddQueueItem(final MediaDescriptionCompat description) {
- if (description == null) {
- return;
- }
- dispatchSessionTask(SessionCommand2.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
- new SessionTask() {
- @Override
- public void run(ControllerInfo controller) throws RemoteException {
- // Add the item at the end of the playlist.
- mSessionImpl.addPlaylistItem(Integer.MAX_VALUE,
- MediaUtils2.convertToMediaItem2(description));
- }
- });
+ onAddQueueItem(description, Integer.MAX_VALUE);
}
@Override
@@ -416,8 +409,14 @@
new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
- mSessionImpl.addPlaylistItem(index,
- MediaUtils2.convertToMediaItem2(description));
+ String mediaId = description.getMediaId();
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "onAddQueueItem(): Media ID shouldn't be empty");
+ return;
+ }
+ MediaItem2 newItem = mSessionImpl.getCallback().onCreateMediaItem(
+ mSessionImpl.getInstance(), controller, mediaId);
+ mSessionImpl.addPlaylistItem(index, newItem);
}
});
}
@@ -431,14 +430,16 @@
new SessionTask() {
@Override
public void run(ControllerInfo controller) throws RemoteException {
- // Note: Here we cannot simply call
- // removePlaylistItem(MediaUtils2.convertToMediaItem2(description)),
- // because the result of the method will have different UUID.
+ String mediaId = description.getMediaId();
+ if (TextUtils.isEmpty(mediaId)) {
+ Log.w(TAG, "onRemoveQueueItem(): Media ID shouldn't be null");
+ return;
+ }
List<MediaItem2> playlist = mSessionImpl.getPlaylist();
for (int i = 0; i < playlist.size(); i++) {
MediaItem2 item = playlist.get(i);
- if (TextUtils.equals(item.getMediaId(), description.getMediaId())) {
- mSessionImpl.removePlaylistItem(item);
+ if (TextUtils.equals(item.getMediaId(), mediaId)) {
+ mSessionImpl.removePlaylistItem(i);
return;
}
}
@@ -446,6 +447,21 @@
});
}
+ @Override
+ public void onRemoveQueueItemAt(final int index) {
+ dispatchSessionTask(SessionCommand2.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
+ new SessionTask() {
+ @Override
+ public void run(ControllerInfo controller) throws RemoteException {
+ if (index < 0) {
+ Log.w(TAG, "onRemoveQueueItem(): index shouldn't be negative");
+ return;
+ }
+ mSessionImpl.removePlaylistItem(index);
+ }
+ });
+ }
+
ControllerInfo getControllersForAll() {
return mControllerInfoForAll;
}
@@ -514,7 +530,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- void handleTaskOnExecutor(@Nullable final ControllerInfo controller,
+ void handleTaskOnExecutor(@NonNull final ControllerInfo controller,
@Nullable final SessionCommand2 sessionCommand, @CommandCode final int commandCode,
@NonNull final SessionTask task) {
SessionCommand2 command;
diff --git a/media2/src/main/java/androidx/media2/MediaSessionService2.java b/media2/src/main/java/androidx/media2/MediaSessionService2.java
index dadcae3..06a7b7e 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionService2.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionService2.java
@@ -232,7 +232,7 @@
@CallSuper
@Nullable
@Override
- public IBinder onBind(Intent intent) {
+ public IBinder onBind(@NonNull Intent intent) {
return mImpl.onBind(intent);
}
diff --git a/media2/src/main/java/androidx/media2/MediaSessionService2ImplBase.java b/media2/src/main/java/androidx/media2/MediaSessionService2ImplBase.java
index 3d64264..eff821b 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionService2ImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionService2ImplBase.java
@@ -239,6 +239,11 @@
}
return;
}
+ if (DEBUG) {
+ Log.d(TAG, "Handling incoming connection request from the controller"
+ + ", controller=" + packageName);
+
+ }
final MediaSession2 session;
try {
session = service.onGetSession();
diff --git a/media2/src/main/java/androidx/media2/MediaTimestamp2.java b/media2/src/main/java/androidx/media2/MediaTimestamp2.java
index 613ceee..5b43367 100644
--- a/media2/src/main/java/androidx/media2/MediaTimestamp2.java
+++ b/media2/src/main/java/androidx/media2/MediaTimestamp2.java
@@ -22,6 +22,7 @@
import android.media.MediaTimestamp;
import android.os.Build;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
/**
@@ -39,13 +40,13 @@
* When the anchor frame is a just-rendered one, the media time stands for
* current position of the playback or recording.
*
- * @see XMediaPlayer#getTimestamp
+ * @see MediaPlayer#getTimestamp
*/
public final class MediaTimestamp2 {
/**
* An unknown media timestamp value
*/
- public static final MediaTimestamp2
+ public static final @NonNull MediaTimestamp2
TIMESTAMP_UNKNOWN = new MediaTimestamp2(-1, -1, 0.0f);
/**
diff --git a/media2/src/main/java/androidx/media2/MediaUtils2.java b/media2/src/main/java/androidx/media2/MediaUtils2.java
index 945a563..bbeb639 100644
--- a/media2/src/main/java/androidx/media2/MediaUtils2.java
+++ b/media2/src/main/java/androidx/media2/MediaUtils2.java
@@ -16,7 +16,11 @@
package androidx.media2;
-import static androidx.media2.MediaItem2.FLAG_PLAYABLE;
+import static android.support.v4.media.MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE;
+
+import static androidx.media2.MediaMetadata2.BROWSABLE_TYPE_MIXED;
+import static androidx.media2.MediaMetadata2.BROWSABLE_TYPE_NONE;
+import static androidx.media2.MediaMetadata2.METADATA_KEY_BROWSABLE;
import static androidx.media2.MediaMetadata2.METADATA_KEY_DISPLAY_DESCRIPTION;
import static androidx.media2.MediaMetadata2.METADATA_KEY_DISPLAY_ICON;
import static androidx.media2.MediaMetadata2.METADATA_KEY_DISPLAY_ICON_URI;
@@ -24,6 +28,7 @@
import static androidx.media2.MediaMetadata2.METADATA_KEY_DISPLAY_TITLE;
import static androidx.media2.MediaMetadata2.METADATA_KEY_MEDIA_ID;
import static androidx.media2.MediaMetadata2.METADATA_KEY_MEDIA_URI;
+import static androidx.media2.MediaMetadata2.METADATA_KEY_PLAYABLE;
import static androidx.media2.MediaMetadata2.METADATA_KEY_TITLE;
import android.annotation.SuppressLint;
@@ -40,7 +45,9 @@
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.text.TextUtils;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.media.AudioAttributesCompat;
@@ -49,10 +56,10 @@
import androidx.media2.MediaSession2.CommandButton;
import androidx.versionedparcelable.ParcelImpl;
import androidx.versionedparcelable.ParcelUtils;
+import androidx.versionedparcelable.VersionedParcelable;
import java.util.ArrayList;
import java.util.List;
-import java.util.UUID;
import java.util.concurrent.Executor;
/**
@@ -88,8 +95,8 @@
if (item2 == null) {
return null;
}
+ int flags = 0;
MediaDescriptionCompat descCompat;
-
MediaMetadata2 metadata = item2.getMetadata();
if (metadata == null) {
descCompat = new MediaDescriptionCompat.Builder()
@@ -121,8 +128,14 @@
}
descCompat = builder.build();
+
+ boolean browsable = metadata.containsKey(METADATA_KEY_BROWSABLE)
+ && metadata.getLong(METADATA_KEY_BROWSABLE) != BROWSABLE_TYPE_NONE;
+ boolean playable = metadata.getLong(METADATA_KEY_PLAYABLE) != 0;
+ flags = (browsable ? MediaItem.FLAG_BROWSABLE : 0)
+ | (playable ? MediaItem.FLAG_PLAYABLE : 0);
}
- return new MediaItem(descCompat, item2.getFlags());
+ return new MediaItem(descCompat, flags);
}
/**
@@ -149,9 +162,9 @@
if (item == null) {
return null;
}
- MediaMetadata2 metadata2 = convertToMediaMetadata2(item.getDescription());
- return new MediaItem2.Builder(item.getFlags())
- .setMediaId(item.getMediaId())
+ MediaMetadata2 metadata2 = convertToMediaMetadata2(item.getDescription(),
+ item.isBrowsable(), item.isPlayable());
+ return new MediaItem2.Builder()
.setMetadata(metadata2)
.build();
}
@@ -165,41 +178,36 @@
}
// descriptionCompat cannot be null
MediaDescriptionCompat descriptionCompat = item.getDescription();
- MediaMetadata2 metadata2 = convertToMediaMetadata2(descriptionCompat);
- return new MediaItem2.Builder(FLAG_PLAYABLE)
+ MediaMetadata2 metadata2 = convertToMediaMetadata2(descriptionCompat, false, true);
+ return new MediaItem2.Builder()
.setMetadata(metadata2)
- .setUuid(createUuidByQueueIdAndMediaId(item.getQueueId(),
- descriptionCompat.getMediaId()))
.build();
}
/**
- * Create a {@link UUID} with queue id and media id.
- */
- public static UUID createUuidByQueueIdAndMediaId(long queueId, String mediaId) {
- return new UUID(queueId, (mediaId == null) ? 0 : mediaId.hashCode());
- }
-
- /**
- * Convert a {@link MediaMetadataCompat} to a {@link MediaItem2}.
+ * Convert a {@link MediaMetadataCompat} from the {@link MediaControllerCompat#getMetadata()}
+ * to a {@link MediaItem2}.
*/
public static MediaItem2 convertToMediaItem2(MediaMetadataCompat metadataCompat) {
- MediaMetadata2 metadata2 = convertToMediaMetadata2(metadataCompat);
- if (metadata2 == null) {
+ if (metadataCompat == null) {
return null;
}
- return new MediaItem2.Builder(FLAG_PLAYABLE).setMetadata(metadata2).build();
+ // Item is from the MediaControllerCompat, so forcefully set the playable.
+ MediaMetadata2 metadata2 = new MediaMetadata2.Builder(metadataCompat.getBundle())
+ .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
+ .putLong(METADATA_KEY_PLAYABLE, 1).build();
+ return new MediaItem2.Builder().setMetadata(metadata2).build();
}
/**
* Convert a {@link MediaDescriptionCompat} to a {@link MediaItem2}.
*/
public static MediaItem2 convertToMediaItem2(MediaDescriptionCompat descriptionCompat) {
- MediaMetadata2 metadata2 = convertToMediaMetadata2(descriptionCompat);
+ MediaMetadata2 metadata2 = convertToMediaMetadata2(descriptionCompat, false, true);
if (metadata2 == null) {
return null;
}
- return new MediaItem2.Builder(FLAG_PLAYABLE).setMetadata(metadata2).build();
+ return new MediaItem2.Builder().setMetadata(metadata2).build();
}
/**
@@ -234,20 +242,18 @@
}
/**
- * Convert a {@link MediaItem2} to a {@link QueueItem}.
+ * Creates {@link MediaDescriptionCompat} with the id
*/
- public static QueueItem convertToQueueItem(MediaItem2 item) {
- if (item == null) {
+ public static MediaDescriptionCompat createMediaDescriptionCompat(String mediaId) {
+ if (TextUtils.isEmpty(mediaId)) {
return null;
}
- MediaDescriptionCompat description = (item.getMetadata() == null)
- ? new MediaDescriptionCompat.Builder().setMediaId(item.getMediaId()).build()
- : convertToMediaMetadataCompat(item.getMetadata()).getDescription();
- return new QueueItem(description, item.getUuid().getMostSignificantBits());
+ return new MediaDescriptionCompat.Builder().setMediaId(mediaId).build();
}
/**
- * Convert a list of {@link MediaItem2} to a list of {@link QueueItem}.
+ * Convert a list of {@link MediaItem2} to a list of {@link QueueItem}. The index of the item
+ * would be used as the queue ID to match the behavior of {@link MediaController2}.
*/
public static List<QueueItem> convertToQueueItemList(List<MediaItem2> items) {
if (items == null) {
@@ -255,10 +261,11 @@
}
List<QueueItem> result = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
- QueueItem queueItem = convertToQueueItem(items.get(i));
- if (queueItem != null) {
- result.add(queueItem);
- }
+ MediaItem2 item = items.get(i);
+ MediaDescriptionCompat description = (item.getMetadata() == null)
+ ? new MediaDescriptionCompat.Builder().setMediaId(item.getMediaId()).build()
+ : convertToMediaMetadataCompat(item.getMetadata()).getDescription();
+ result.add(new QueueItem(description, i));
}
return result;
}
@@ -276,7 +283,7 @@
for (int i = 0; i < parcelImplList.size(); i++) {
final ParcelImpl itemParcelImpl = parcelImplList.get(i);
if (itemParcelImpl != null) {
- mediaItem2List.add((MediaItem2) ParcelUtils.fromParcelable(itemParcelImpl));
+ mediaItem2List.add((MediaItem2) fromParcelable(itemParcelImpl));
}
}
return mediaItem2List;
@@ -312,9 +319,13 @@
* Creates a {@link MediaMetadata2} from the {@link MediaDescriptionCompat}.
*
* @param descCompat A {@link MediaDescriptionCompat} object.
- * @return The newly created {@link MediaMetadata2} object.
+ * @param browsable {@code true} if it's from {@link MediaItem} with browable flag.
+ * @param playable {@code true} if it's from {@link MediaItem} with playable flag, or from
+ * {@link QueueItem}.
+ * @return
*/
- public static MediaMetadata2 convertToMediaMetadata2(MediaDescriptionCompat descCompat) {
+ private static MediaMetadata2 convertToMediaMetadata2(MediaDescriptionCompat descCompat,
+ boolean browsable, boolean playable) {
if (descCompat == null) {
return null;
}
@@ -349,7 +360,7 @@
Bundle bundle = descCompat.getExtras();
if (bundle != null) {
- metadata2Builder.setExtras(descCompat.getExtras());
+ metadata2Builder.setExtras(bundle);
}
Uri mediaUri = descCompat.getMediaUri();
@@ -357,20 +368,18 @@
metadata2Builder.putText(METADATA_KEY_MEDIA_URI, mediaUri.toString());
}
- return metadata2Builder.build();
- }
-
- /**
- * Creates a {@link MediaMetadata2} from the {@link MediaMetadataCompat}.
- *
- * @param metadataCompat A {@link MediaMetadataCompat} object.
- * @return The newly created {@link MediaMetadata2} object.
- */
- public static MediaMetadata2 convertToMediaMetadata2(MediaMetadataCompat metadataCompat) {
- if (metadataCompat == null) {
- return null;
+ if (bundle != null && bundle.containsKey(EXTRA_BT_FOLDER_TYPE)) {
+ metadata2Builder.putLong(METADATA_KEY_BROWSABLE,
+ bundle.getLong(EXTRA_BT_FOLDER_TYPE));
+ } else if (browsable) {
+ metadata2Builder.putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED);
+ } else {
+ metadata2Builder.putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE);
}
- return new MediaMetadata2(metadataCompat.getBundle());
+
+ metadata2Builder.putLong(METADATA_KEY_PLAYABLE, playable ? 1 : 0);
+
+ return metadata2Builder.build();
}
/**
@@ -381,7 +390,10 @@
return null;
}
return new MediaMetadata2.Builder()
- .putString(METADATA_KEY_TITLE, queueTitle.toString()).build();
+ .putString(METADATA_KEY_TITLE, queueTitle.toString())
+ .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
+ .putLong(METADATA_KEY_PLAYABLE, 1)
+ .build();
}
/**
@@ -413,14 +425,6 @@
}
/**
- * Creates a {@link MediaMetadataCompat} from the {@link MediaDescriptionCompat}.
- */
- public static MediaMetadataCompat convertToMediaMetadataCompat(
- MediaDescriptionCompat description) {
- return convertToMediaMetadataCompat(convertToMediaMetadata2(description));
- }
-
- /**
* Creates a {@link Rating2} from the {@link RatingCompat}.
*
* @param ratingCompat A {@link RatingCompat} object.
@@ -500,7 +504,7 @@
List<ParcelImpl> parcelImplList = new ArrayList<>();
for (int i = 0; i < commandButtonList.size(); i++) {
final CommandButton commandButton = commandButtonList.get(i);
- parcelImplList.add((ParcelImpl) ParcelUtils.toParcelable(commandButton));
+ parcelImplList.add(toParcelable(commandButton));
}
return parcelImplList;
}
@@ -517,7 +521,7 @@
for (int i = 0; i < mediaItem2List.size(); i++) {
final MediaItem2 item = mediaItem2List.get(i);
if (item != null) {
- final ParcelImpl itemParcelImpl = (ParcelImpl) ParcelUtils.toParcelable(item);
+ final ParcelImpl itemParcelImpl = toParcelable(item);
itemParcelableList.add(itemParcelImpl);
}
}
@@ -693,4 +697,68 @@
rootHints.putBoolean(BrowserRoot.EXTRA_SUGGESTED, params.isSuggested());
return rootHints;
}
+
+ /**
+ * Removes all null elements from the list and returns it.
+ *
+ * @param list
+ * @return
+ */
+ public static <T> List<T> removeNullElements(@Nullable List<T> list) {
+ if (list == null) {
+ return null;
+ }
+ List<T> newList = new ArrayList<>();
+ for (T item : list) {
+ if (item != null) {
+ newList.add(item);
+ }
+ }
+ return newList;
+ }
+
+ /**
+ * Media2 version of {@link ParcelUtils#toParcelable(VersionedParcelable)}.
+ * <p>
+ * This sanitizes {@link MediaItem2}'s subclass information.
+ *
+ * @param item
+ * @return
+ */
+ public static ParcelImpl toParcelable(VersionedParcelable item) {
+ if (item instanceof MediaItem2) {
+ return new MediaItemParcelImpl((MediaItem2) item);
+ }
+ return (ParcelImpl) ParcelUtils.toParcelable(item);
+ }
+
+ /**
+ * Media2 version of {@link ParcelUtils#fromParcelable(Parcelable)}.
+ */
+ @SuppressWarnings("TypeParameterUnusedInFormals")
+ public static <T extends VersionedParcelable> T fromParcelable(ParcelImpl p) {
+ return ParcelUtils.<T>fromParcelable(p);
+ }
+
+ private static class MediaItemParcelImpl extends ParcelImpl {
+ private final MediaItem2 mItem;
+
+ MediaItemParcelImpl(MediaItem2 item) {
+ // Up-cast (possibly MediaItem2's subclass object) item to MediaItem2 for the
+ // writeToParcel(). The copied media item will be only used when it's sent across the
+ // process.
+ super(new MediaItem2(item));
+
+ // Keeps the original copy for local binder to send the original item.
+ // When local binder is used (i.e. binder call happens in a single process),
+ // writeToParcel() wouldn't happen for the Parcelable object and the same object will
+ // be sent through the binder call.
+ mItem = item;
+ }
+
+ @Override
+ public MediaItem2 getVersionedParcel() {
+ return mItem;
+ }
+ }
}
diff --git a/media2/src/main/java/androidx/media2/PlaybackParams2.java b/media2/src/main/java/androidx/media2/PlaybackParams2.java
index b262dea..c2bb67c 100644
--- a/media2/src/main/java/androidx/media2/PlaybackParams2.java
+++ b/media2/src/main/java/androidx/media2/PlaybackParams2.java
@@ -23,6 +23,8 @@
import android.os.Build;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
@@ -32,8 +34,8 @@
/**
* Structure for common playback params.
*
- * Used by {@link XMediaPlayer} {@link XMediaPlayer#getPlaybackParams()} and
- * {@link XMediaPlayer#setPlaybackParams(PlaybackParams2)}
+ * Used by {@link MediaPlayer} {@link MediaPlayer#getPlaybackParams()} and
+ * {@link MediaPlayer#setPlaybackParams(PlaybackParams2)}
* to control playback behavior.
* <p> <strong>audio fallback mode:</strong>
* select out-of-range parameter handling.
@@ -96,7 +98,7 @@
/**
* Returns the audio fallback mode. {@code null} if a value is not set.
*/
- public @AudioFallbackMode Integer getAudioFallbackMode() {
+ public @AudioFallbackMode @Nullable Integer getAudioFallbackMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
return mPlaybackParams.getAudioFallbackMode();
@@ -111,7 +113,7 @@
/**
* Returns the pitch factor. {@code null} if a value is not set.
*/
- public Float getPitch() {
+ public @Nullable Float getPitch() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
return mPlaybackParams.getPitch();
@@ -126,7 +128,7 @@
/**
* Returns the speed factor. {@code null} if a value is not set.
*/
- public Float getSpeed() {
+ public @Nullable Float getSpeed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
return mPlaybackParams.getSpeed();
@@ -185,7 +187,7 @@
*
* @return this <code>Builder</code> instance.
*/
- public Builder setAudioFallbackMode(@AudioFallbackMode int audioFallbackMode) {
+ public @NonNull Builder setAudioFallbackMode(@AudioFallbackMode int audioFallbackMode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPlaybackParams.setAudioFallbackMode(audioFallbackMode);
} else {
@@ -200,7 +202,7 @@
* @return this <code>Builder</code> instance.
* @throws IllegalArgumentException if the pitch is negative.
*/
- public Builder setPitch(float pitch) {
+ public @NonNull Builder setPitch(float pitch) {
if (pitch < 0.f) {
throw new IllegalArgumentException("pitch must not be negative");
}
@@ -217,7 +219,7 @@
*
* @return this <code>Builder</code> instance.
*/
- public Builder setSpeed(float speed) {
+ public @NonNull Builder setSpeed(float speed) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPlaybackParams.setSpeed(speed);
} else {
@@ -230,7 +232,7 @@
* Takes the values of the Builder object and creates a PlaybackParams2 object.
* @return PlaybackParams2 object with values from the Builder.
*/
- public PlaybackParams2 build() {
+ public @NonNull PlaybackParams2 build() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return new PlaybackParams2(mPlaybackParams);
} else {
diff --git a/media2/src/main/java/androidx/media2/SessionCommand2.java b/media2/src/main/java/androidx/media2/SessionCommand2.java
index 25376d7..d5f66e8 100644
--- a/media2/src/main/java/androidx/media2/SessionCommand2.java
+++ b/media2/src/main/java/androidx/media2/SessionCommand2.java
@@ -81,7 +81,7 @@
@IntDef({COMMAND_CODE_CUSTOM,
COMMAND_CODE_PLAYER_PLAY,
COMMAND_CODE_PLAYER_PAUSE,
- COMMAND_CODE_PLAYER_PREFETCH,
+ COMMAND_CODE_PLAYER_PREPARE,
COMMAND_CODE_PLAYER_SEEK_TO,
COMMAND_CODE_PLAYER_SET_SPEED,
COMMAND_CODE_PLAYER_GET_PLAYLIST,
@@ -102,12 +102,14 @@
COMMAND_CODE_VOLUME_ADJUST_VOLUME,
COMMAND_CODE_SESSION_FAST_FORWARD,
COMMAND_CODE_SESSION_REWIND,
+ COMMAND_CODE_SESSION_SKIP_FORWARD,
+ COMMAND_CODE_SESSION_SKIP_BACKWARD,
COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID,
COMMAND_CODE_SESSION_PLAY_FROM_SEARCH,
COMMAND_CODE_SESSION_PLAY_FROM_URI,
- COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID,
- COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH,
- COMMAND_CODE_SESSION_PREFETCH_FROM_URI,
+ COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID,
+ COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH,
+ COMMAND_CODE_SESSION_PREPARE_FROM_URI,
COMMAND_CODE_SESSION_SET_RATING,
COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO,
COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO,
@@ -158,7 +160,7 @@
public static final int COMMAND_CODE_PLAYER_PAUSE = 10001;
/**
- * Command code for {@link MediaController2#prefetch()}.
+ * Command code for {@link MediaController2#prepare()}.
* <p>
* Command would be sent directly to the player if the session doesn't reject the request
* through the {@link SessionCallback#onCommandRequest(MediaSession2, ControllerInfo,
@@ -166,7 +168,7 @@
* <p>
* Code version is {@link #COMMAND_VERSION_1}.
*/
- public static final int COMMAND_CODE_PLAYER_PREFETCH = 10002;
+ public static final int COMMAND_CODE_PLAYER_PREPARE = 10002;
/**
* Command code for {@link MediaController2#seekTo(long)}.
@@ -210,7 +212,7 @@
public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006;
/**
- * Command code for {@link MediaController2#skipToPlaylistItem(MediaItem2)}.
+ * Command code for {@link MediaController2#skipToPlaylistItem(int)}.
* <p>
* Command would be sent directly to the player if the session doesn't reject the request
* through the
@@ -274,7 +276,7 @@
public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012;
/**
- * Command code for {@link MediaController2#addPlaylistItem(int, MediaItem2)}.
+ * Command code for {@link MediaController2#addPlaylistItem(int, String)}.
* <p>
* Command would be sent directly to the player if the session doesn't reject the request
* through the
@@ -285,7 +287,7 @@
public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013;
/**
- * Command code for {@link MediaController2#addPlaylistItem(int, MediaItem2)}.
+ * Command code for {@link MediaController2#addPlaylistItem(int, String)}.
* <p>
* Command would be sent directly to the player if the session doesn't reject the request
* through the
@@ -296,7 +298,7 @@
public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014;
/**
- * Command code for {@link MediaController2#replacePlaylistItem(int, MediaItem2)}.
+ * Command code for {@link MediaController2#replacePlaylistItem(int, String)}.
* <p>
* Command would be sent directly to the player if the session doesn't reject the request
* through the
@@ -326,7 +328,7 @@
public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017;
/**
- * Command code for {@link MediaController2#setMediaItem(MediaItem2)}.
+ * Command code for {@link MediaController2#setMediaItem(String)}.
* <p>
* Command would be sent directly to the player if the session doesn't reject the request
* through the
@@ -403,13 +405,27 @@
public static final int COMMAND_CODE_SESSION_REWIND = 40001;
/**
+ * Command code for {@link MediaController2#skipForward()}.
+ * <p>
+ * Code version is {@link #COMMAND_VERSION_1}.
+ */
+ public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002;
+
+ /**
+ * Command code for {@link MediaController2#skipBackward()}.
+ * <p>
+ * Code version is {@link #COMMAND_VERSION_1}.
+ */
+ public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003;
+
+ /**
* Command code for {@link MediaController2#playFromMediaId(String, Bundle)}.
* <p>
* Code version is {@link #COMMAND_VERSION_1}.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID = 40002;
+ public static final int COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID = 40004;
/**
* Command code for {@link MediaController2#playFromSearch(String, Bundle)}.
@@ -418,7 +434,7 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_PLAY_FROM_SEARCH = 40003;
+ public static final int COMMAND_CODE_SESSION_PLAY_FROM_SEARCH = 40005;
/**
* Command code for {@link MediaController2#playFromUri(Uri, Bundle)}.
@@ -427,41 +443,41 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_PLAY_FROM_URI = 40004;
+ public static final int COMMAND_CODE_SESSION_PLAY_FROM_URI = 40006;
/**
- * Command code for {@link MediaController2#prefetchFromMediaId(String, Bundle)}.
+ * Command code for {@link MediaController2#prepareFromMediaId(String, Bundle)}.
* <p>
* Code version is {@link #COMMAND_VERSION_1}.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_PREFETCH_FROM_MEDIA_ID = 40005;
+ public static final int COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID = 40007;
/**
- * Command code for {@link MediaController2#prefetchFromSearch(String, Bundle)}.
+ * Command code for {@link MediaController2#prepareFromSearch(String, Bundle)}.
* <p>
* Code version is {@link #COMMAND_VERSION_1}.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_PREFETCH_FROM_SEARCH = 40006;
+ public static final int COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH = 40008;
/**
- * Command code for {@link MediaController2#prefetchFromUri(Uri, Bundle)}.
+ * Command code for {@link MediaController2#prepareFromUri(Uri, Bundle)}.
* <p>
* Code version is {@link #COMMAND_VERSION_1}.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_PREFETCH_FROM_URI = 40007;
+ public static final int COMMAND_CODE_SESSION_PREPARE_FROM_URI = 40009;
/**
* Command code for {@link MediaController2#setRating(String, Rating2)}.
* <p>
* Code version is {@link #COMMAND_VERSION_1}.
*/
- public static final int COMMAND_CODE_SESSION_SET_RATING = 40008;
+ public static final int COMMAND_CODE_SESSION_SET_RATING = 40010;
/**
* Command code for {@link MediaController2#subscribeRoutesInfo()}
@@ -470,7 +486,7 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO = 40009;
+ public static final int COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO = 40011;
/**
* Command code for {@link MediaController2#unsubscribeRoutesInfo()}
@@ -479,7 +495,7 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO = 40010;
+ public static final int COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO = 40012;
/**
* Command code for {@link MediaController2#selectRoute(Bundle)}}
@@ -488,7 +504,7 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public static final int COMMAND_CODE_SESSION_SELECT_ROUTE = 40011;
+ public static final int COMMAND_CODE_SESSION_SELECT_ROUTE = 40013;
static {
VERSION_SESSION_COMMANDS_MAP.put(COMMAND_VERSION_1,
diff --git a/media2/src/main/java/androidx/media2/SessionCommandGroup2.java b/media2/src/main/java/androidx/media2/SessionCommandGroup2.java
index d9b04b63..2e67ae1 100644
--- a/media2/src/main/java/androidx/media2/SessionCommandGroup2.java
+++ b/media2/src/main/java/androidx/media2/SessionCommandGroup2.java
@@ -196,7 +196,7 @@
* {@link SessionCommandGroup2} object.
* @param commandGroup
*/
- public Builder(SessionCommandGroup2 commandGroup) {
+ public Builder(@NonNull SessionCommandGroup2 commandGroup) {
mCommands = commandGroup.getCommands();
}
@@ -205,7 +205,7 @@
*
* @param command A command to add. Shouldn't be {@code null}.
*/
- public @NonNull Builder addCommand(SessionCommand2 command) {
+ public @NonNull Builder addCommand(@NonNull SessionCommand2 command) {
if (command == null) {
throw new IllegalArgumentException("command shouldn't be null");
}
@@ -239,7 +239,7 @@
*
* @param version command version
* @see SessionCommand2#COMMAND_VERSION_1
- * @see MediaSession2.SessionCallback#onConnect
+ * @see MediaSession2.SessionCallback#onConnect(MediaSession2, MediaSession2.ControllerInfo)
*/
public @NonNull Builder addAllPredefinedCommands(@CommandVersion int version) {
if (version != COMMAND_VERSION_1) {
diff --git a/media2/src/main/java/androidx/media2/SessionPlayer2.java b/media2/src/main/java/androidx/media2/SessionPlayer2.java
index cb14cbb..fbef7c4 100644
--- a/media2/src/main/java/androidx/media2/SessionPlayer2.java
+++ b/media2/src/main/java/androidx/media2/SessionPlayer2.java
@@ -43,15 +43,39 @@
/**
* Base interface for all media players that want media session.
* <p>
- * Methods here may be the asynchronous calls depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
+ * APIs that return {@link ListenableFuture} should be the asynchronous calls and shouldn't block
+ * the calling thread. This guarantees the APIs are safe to be called on the main thread.
*
* <p>Topics covered here are:
* <ol>
+ * <li><a href="#BestPractices">Best practices</a>
* <li><a href="#PlayerStates">Player states</a>
- * <li><a href="#Invalid_States">Invalid method calls</a>
+ * <li><a href="#InvalidStates">Invalid method calls</a>
* </ol>
*
+ * <h3 id="BestPractices">Best practices</h3>
+ *
+ * Here are best practices when implementing/using SessionPlayer2:
+ *
+ * <ul>
+ * <li>When updating UI, you should respond to {@link PlayerCallback} invocations instead of
+ * {@link PlayerResult} objects since the player can be controlled by others.
+ * <li>When a SessionPlayer2 object is no longer being used, call {@link #close()} as soon as
+ * possible to release the resources used by the internal player engine associated with the
+ * SessionPlayer2. For example, if a player uses hardware decoder, other player instances may
+ * fallback to software decoders or fail to play. You cannot use SessionPlayer2 instance after
+ * you call {@link #close()}. There is no way to reuse the instance.
+ * <li>The current playback position can be retrieved with a call to {@link #getCurrentPosition()},
+ * which is helpful for applications such as a music player that need to keep track of the playback
+ * progress.
+ * <li>The playback position can be adjusted with a call to {@link #seekTo(long)}. Although the
+ * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
+ * while to finish, especially for audio/video being streamed.
+ * <li>You can call {@link #seekTo(long)} from the {@link #PLAYER_STATE_PAUSED}. In these cases, if
+ * you are playing a video stream and the requested position is valid, one video frame may be
+ * displayed.
+ * </ul>
+ *
* <h3 id="PlayerStates">Player states</h3>
* The playback control of audio/video files is managed as a state machine. The SessionPlayer2
* defines four states:
@@ -62,10 +86,7 @@
* {@link #setPlaylist(List, MediaMetadata2)}. Check returned {@link ListenableFuture} for
* potential error.
* <p>
- * Calling {@link #prepare()} transfers this object to {@link #PLAYER_STATE_PAUSED}. Note
- * that {@link #prepare()} is asynchronous, so wait for the returned
- * {@link ListenableFuture} or
- * {@link PlayerCallback#onPlayerStateChanged(SessionPlayer2, int)}.
+ * Calling {@link #prepare()} transfers this object to {@link #PLAYER_STATE_PAUSED}.
*
* <li>{@link #PLAYER_STATE_PAUSED}: State when the audio/video playback is paused.
* <p>
@@ -91,34 +112,12 @@
* In general, playback might fail due to various reasons such as unsupported audio/video
* format, poorly interleaved audio/video, resolution too high, streaming timeout, and
* others. In addition, due to programming errors, a playback control operation might be
- * performed from an <a href="#invalid_state">invalid state</a>. In these cases the player
+ * performed from an <a href="#InvalidStates">invalid state</a>. In these cases the player
* may transition to this state.
* </ol>
* <p>
*
- * Here are best practices when implementing/using SessionPlayer2:
- *
- * <ul>
- * <li>When updating UI, you should respond to {@link PlayerCallback} invocations instead of
- * {@link PlayerResult} objects since the player can be controlled by others.
- * <li>When a SessionPlayer2 object is no longer being used, call {@link #close()} as soon as
- * possible to release the resources used by the internal player engine associated with the
- * SessionPlayer2. Failure to call {@link #close()} may cause subsequent instances of SessionPlayer2
- * objects to fallback to software implementations or fail altogether. You cannot use SessionPlayer2
- * after you call {@link #close()}. There is no way to bring it back to any other state.
- * <li>The current playback position can be retrieved with a call to {@link #getCurrentPosition()},
- * which is helpful for applications such as a Music player that need to keep track of the playback
- * progress.
- * <li>The playback position can be adjusted with a call to {@link #seekTo(long)}. Although the
- * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
- * while to finish, especially for audio/video being streamed. Wait for the return value or
- * {@link PlayerCallback#onSeekCompleted(SessionPlayer2, long)}.
- * <li>You can call {@link #seekTo(long)} from the {@link #PLAYER_STATE_PAUSED}. In these cases, if
- * you are playing a video stream and the requested position is valid, one video frame may be
- * displayed.
- * </ul>
- *
- * <h3 id="Invalid_States">Invalid method calls</h3>
+ * <h3 id="InvalidStates">Invalid method calls</h3>
* The only method you safely call from the {@link #PLAYER_STATE_ERROR} is {@link #close()}.
* Any other methods might throw an exception or return meaningless data.
* <p>
@@ -141,7 +140,7 @@
* </table>
*/
// Previously MediaSessionCompat.Callback.
-// Players can extend this directly (e.g. XMediaPlayer) or create wrapper and control underlying
+// Players can extend this directly (e.g. MediaPlayer) or create wrapper and control underlying
// player.
// Preferably it can be interface, but API guideline requires to use abstract class.
@TargetApi(Build.VERSION_CODES.P)
@@ -284,34 +283,22 @@
/**
* Plays the playback.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
*/
public abstract @NonNull ListenableFuture<PlayerResult> play();
/**
* Pauses playback.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
*/
public abstract @NonNull ListenableFuture<PlayerResult> pause();
/**
* Prepares the media items for playback. During this time, the player may allocate resources
* required to play, such as audio and video decoders.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
*/
public abstract @NonNull ListenableFuture<PlayerResult> prepare();
/**
* Seeks to the specified position. Moves the playback head to the specified position.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
*
* @param position the new playback position in ms. The value should be in the range of start
* and end positions defined in {@link MediaItem2}.
@@ -323,9 +310,6 @@
* <p>
* After changing the playback speed, it is recommended to query the actual speed supported
* by the player, see {@link #getPlaybackSpeed()}.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
*
* @param playbackSpeed playback speed
*/
@@ -336,9 +320,6 @@
* <p>
* You must call this method in {@link #PLAYER_STATE_IDLE} in order for the audio attributes to
* become effective thereafter.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
*
* @param attributes non-null <code>AudioAttributes</code>.
*/
@@ -347,9 +328,6 @@
/**
* Gets the current player state.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
*
* @return the current player state
* @see PlayerCallback#onPlayerStateChanged(SessionPlayer2, int)
@@ -401,10 +379,8 @@
/**
* Sets a list of {@link MediaItem2} with metadata. Ensure uniqueness of each {@link MediaItem2}
- * in the playlist so the session can uniquely identity individual items.
- * <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
+ * in the playlist so the session can uniquely identity individual items. All
+ * {@link MediaItem2}s shouldn't be {@code null} as well.
* <p>
* It's recommended to fill {@link MediaMetadata2} in each {@link MediaItem2} especially for the
* duration information with the key {@link MediaMetadata2#METADATA_KEY_DURATION}. Without the
@@ -432,9 +408,6 @@
/**
* Sets a {@link MediaItem2} for playback.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* It's recommended to fill {@link MediaMetadata2} in each {@link MediaItem2} especially for the
* duration information with the key {@link MediaMetadata2#METADATA_KEY_DURATION}. Without the
* duration information in the metadata, session will do extra work to get the duration and send
@@ -456,9 +429,6 @@
* the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
* the playlist.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation may not change the currently playing media item.
* If index is less than or equal to the current index of the playlist,
* the current index of the playlist will be increased correspondingly.
@@ -477,9 +447,6 @@
/**
* Removes the media item from the playlist
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation may not change the currently playing media item even when it's removed.
* <p>
* The implementation must notify registered callbacks with
@@ -497,9 +464,6 @@
* Replaces the media item at index in the playlist. This can be also used to update metadata of
* an item.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer2, List, MediaMetadata2)} when it's
* completed.
@@ -514,9 +478,6 @@
/**
* Skips to the previous item in the playlist.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer2, MediaItem2)} when it's
* completed.
@@ -528,9 +489,6 @@
/**
* Skips to the next item in the playlist.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer2, MediaItem2)} when it's
* completed.
@@ -542,9 +500,6 @@
/**
* Skips to the the media item.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer2, MediaItem2)} when it's
* completed.
@@ -558,9 +513,6 @@
/**
* Updates the playlist metadata while keeping the playlist as-is.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistMetadataChanged(SessionPlayer2, MediaMetadata2)} when it's
* completed.
@@ -574,9 +526,6 @@
/**
* Sets the repeat mode.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onRepeatModeChanged(SessionPlayer2, int)} when it's completed.
*
@@ -593,9 +542,6 @@
/**
* Sets the shuffle mode.
* <p>
- * This may be the asynchronous call depending on the implementation. Wait with returned
- * {@link ListenableFuture} or callback for the completion.
- * <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onShuffleModeChanged(SessionPlayer2, int)} when it's completed.
*
@@ -776,8 +722,8 @@
* @see #getPlaylist()
* @see #getPlaylistMetadata()
*/
- public void onPlaylistChanged(@NonNull SessionPlayer2 player, List<MediaItem2> list,
- @Nullable MediaMetadata2 metadata) {
+ public void onPlaylistChanged(@NonNull SessionPlayer2 player,
+ @Nullable List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
}
/**
diff --git a/media2/src/main/java/androidx/media2/SubtitleData2.java b/media2/src/main/java/androidx/media2/SubtitleData2.java
index 3ff4fc8..e3f0df2 100644
--- a/media2/src/main/java/androidx/media2/SubtitleData2.java
+++ b/media2/src/main/java/androidx/media2/SubtitleData2.java
@@ -28,7 +28,7 @@
/**
* Class encapsulating subtitle data, as received through the
- * {@link XMediaPlayer.PlayerCallback#onSubtitleData} interface.
+ * {@link MediaPlayer.PlayerCallback#onSubtitleData} interface.
* The subtitle data includes:
* <ul>
* <li> the track index</li>
@@ -38,15 +38,15 @@
* </ul>
* The data is stored in a byte-array, and is encoded in one of the supported in-band
* subtitle formats. The subtitle encoding is determined by the MIME type of the
- * {@link XMediaPlayer.TrackInfo} of the subtitle track, one of
+ * {@link MediaPlayer.TrackInfo} of the subtitle track, one of
* {@link #MIMETYPE_TEXT_CEA_608}, {@link #MIMETYPE_TEXT_CEA_708},
* {@link #MIMETYPE_TEXT_VTT}.
* <p>
- * Here is an example of iterating over the tracks of a {@link XMediaPlayer}, and checking which
+ * Here is an example of iterating over the tracks of a {@link MediaPlayer}, and checking which
* encoding is used for the subtitle tracks:
* <p>
* <pre class="prettyprint">
- * XMediaPlayer mp = new XMediaPlayer(context);
+ * MediaPlayer mp = new MediaPlayer(context);
* // prepare the player with a valid media item.
* …
*
@@ -65,8 +65,8 @@
* }
* </pre>
* <p>
- * @see XMediaPlayer#registerPlayerCallback(Executor, SessionPlayer2.PlayerCallback)
- * @see XMediaPlayer.PlayerCallback#onSubtitleData(XMediaPlayer, MediaItem2, SubtitleData2)
+ * @see MediaPlayer#registerPlayerCallback(Executor, SessionPlayer2.PlayerCallback)
+ * @see MediaPlayer.PlayerCallback#onSubtitleData(MediaPlayer, MediaItem2, SubtitleData2)
*/
public final class SubtitleData2 {
private static final String TAG = "SubtitleData2";
@@ -112,7 +112,7 @@
/**
* Returns the index of the MediaPlayer track which contains this subtitle data.
- * @return an index in the array returned by {@link XMediaPlayer#getTrackInfo()}.
+ * @return an index in the array returned by {@link MediaPlayer#getTrackInfo()}.
*/
public int getTrackIndex() {
return mTrackIndex;
diff --git a/media2/src/main/java/androidx/media2/TimedMetaData2.java b/media2/src/main/java/androidx/media2/TimedMetaData2.java
index 62b9811..5421511 100644
--- a/media2/src/main/java/androidx/media2/TimedMetaData2.java
+++ b/media2/src/main/java/androidx/media2/TimedMetaData2.java
@@ -32,7 +32,7 @@
* <li> raw uninterpreted byte-array extracted directly from the container. </li>
* </ul>
*
- * @see XMediaPlayer.PlayerCallback#onTimedMetaDataAvailable
+ * @see MediaPlayer.PlayerCallback#onTimedMetaDataAvailable
*/
public class TimedMetaData2 {
private static final String TAG = "TimedMetaData";
diff --git a/media2/src/main/java/androidx/media2/UriMediaItem2.java b/media2/src/main/java/androidx/media2/UriMediaItem2.java
index f67205a..3c364e1 100644
--- a/media2/src/main/java/androidx/media2/UriMediaItem2.java
+++ b/media2/src/main/java/androidx/media2/UriMediaItem2.java
@@ -23,6 +23,7 @@
import androidx.annotation.Nullable;
import androidx.core.util.Preconditions;
import androidx.versionedparcelable.NonParcelField;
+import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelize;
import java.net.CookieHandler;
@@ -37,10 +38,13 @@
* Structure for media item descriptor for {@link Uri}.
* <p>
* Users should use {@link Builder} to create {@link UriMediaItem2}.
+ * <p>
+ * You cannot directly send this object across the process through {@link ParcelUtils}. See
+ * {@link MediaItem2} for detail.
*
* @see MediaItem2
*/
-@VersionedParcelize
+@VersionedParcelize(isCustom = true)
public class UriMediaItem2 extends MediaItem2 {
@NonParcelField
@SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -153,7 +157,6 @@
*/
public Builder(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) {
- super(FLAG_PLAYABLE);
Preconditions.checkNotNull(context, "context cannot be null");
Preconditions.checkNotNull(uri, "uri cannot be null");
mUri = uri;
@@ -181,7 +184,7 @@
* @return A new UriMediaItem2 with values supplied by the Builder.
*/
@Override
- public UriMediaItem2 build() {
+ public @NonNull UriMediaItem2 build() {
return new UriMediaItem2(this);
}
}
diff --git a/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java b/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
index 411c7a5..9e7f850 100644
--- a/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
+++ b/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
@@ -70,7 +70,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Wraps an ExoPlayer instance and provides methods and notifies events like those in the
@@ -248,8 +250,8 @@
// groups.
switch (state) {
case Player.STATE_IDLE:
- case Player.STATE_ENDED:
return MediaPlayer2.PLAYER_STATE_IDLE;
+ case Player.STATE_ENDED:
case Player.STATE_BUFFERING:
return MediaPlayer2.PLAYER_STATE_PAUSED;
case Player.STATE_READY:
@@ -669,17 +671,42 @@
mFileDescriptor = fileDescriptor;
}
- public void close() {
- try {
- if (mFileDescriptor != null) {
- FileDescriptorUtil.close(mFileDescriptor);
- } else if (mMediaItem instanceof CallbackMediaItem2) {
- ((CallbackMediaItem2) mMediaItem).getDataSourceCallback2().close();
- }
- } catch (IOException e) {
- Log.w(TAG, "Error closing media item " + mMediaItem, e);
+ }
+
+ private static final class FileDescriptorRegistry {
+
+ private static final class Entry {
+ public final Object mLock;
+
+ public int mMediaItemCount;
+
+ Entry() {
+ mLock = new Object();
}
}
+
+ private final Map<FileDescriptor, Entry> mEntries;
+
+ FileDescriptorRegistry() {
+ mEntries = new HashMap<>();
+ }
+
+ public Object registerMediaItemAndGetLock(FileDescriptor fileDescriptor) {
+ if (!mEntries.containsKey(fileDescriptor)) {
+ mEntries.put(fileDescriptor, new Entry());
+ }
+ Entry entry = Preconditions.checkNotNull(mEntries.get(fileDescriptor));
+ entry.mMediaItemCount++;
+ return entry.mLock;
+ }
+
+ public void unregisterMediaItem(FileDescriptor fileDescriptor) {
+ Entry entry = Preconditions.checkNotNull(mEntries.get(fileDescriptor));
+ if (--entry.mMediaItemCount == 0) {
+ mEntries.remove(fileDescriptor);
+ }
+ }
+
}
private static final class MediaItemQueue {
@@ -689,6 +716,7 @@
private final DataSource.Factory mDataSourceFactory;
private final ConcatenatingMediaSource mConcatenatingMediaSource;
private final ArrayDeque<MediaItemInfo> mMediaItemInfos;
+ private final FileDescriptorRegistry mFileDescriptorRegistry;
private long mStartPlayingTimeNs;
private long mCurrentMediaItemPlayingTimeUs;
@@ -700,12 +728,13 @@
mDataSourceFactory = new DefaultDataSourceFactory(context, userAgent);
mConcatenatingMediaSource = new ConcatenatingMediaSource();
mMediaItemInfos = new ArrayDeque<>();
+ mFileDescriptorRegistry = new FileDescriptorRegistry();
mStartPlayingTimeNs = -1;
}
public void clear() {
while (!mMediaItemInfos.isEmpty()) {
- mMediaItemInfos.remove().close();
+ releaseMediaItem(mMediaItemInfos.remove());
}
}
@@ -725,7 +754,7 @@
mConcatenatingMediaSource.removeMediaSourceRange(
/* fromIndex= */ 1, /* toIndex= */ size);
while (mMediaItemInfos.size() > 1) {
- mMediaItemInfos.removeLast().close();
+ releaseMediaItem(mMediaItemInfos.removeLast());
}
}
@@ -736,7 +765,10 @@
return;
}
try {
- appendMediaItem(mediaItem2, mDataSourceFactory, mMediaItemInfos, mediaSources);
+ appendMediaItem(
+ mediaItem2,
+ mMediaItemInfos,
+ mediaSources);
} catch (IOException e) {
mListener.onError(mediaItem2, MEDIA_ERROR_UNKNOWN);
}
@@ -769,7 +801,7 @@
public void skipToNext() {
// TODO(b/68398926): Play the start position of the next media item.
- mMediaItemInfos.removeFirst().close();
+ releaseMediaItem(mMediaItemInfos.removeFirst());
mConcatenatingMediaSource.removeMediaSource(0);
}
@@ -807,7 +839,7 @@
mListener.onMediaItem2Ended(getCurrentMediaItem());
}
for (int i = 0; i < windowIndex; i++) {
- mMediaItemInfos.removeFirst().close();
+ releaseMediaItem(mMediaItemInfos.removeFirst());
}
if (isPeriodTransition) {
mListener.onMediaItem2StartedAsNext(getCurrentMediaItem());
@@ -825,20 +857,23 @@
* Appends a media source and associated information for the given media item to the
* collections provided.
*/
- private static void appendMediaItem(
+ private void appendMediaItem(
MediaItem2 mediaItem2,
- DataSource.Factory dataSourceFactory,
Collection<MediaItemInfo> mediaItemInfos,
Collection<MediaSource> mediaSources) throws IOException {
+ DataSource.Factory dataSourceFactory = mDataSourceFactory;
// Create a data source for reading from the file descriptor, if needed.
FileDescriptor fileDescriptor = null;
if (mediaItem2 instanceof FileMediaItem2) {
FileMediaItem2 fileMediaItem2 = (FileMediaItem2) mediaItem2;
+ // TODO(b/68398926): Remove dup'ing the file descriptor once FileMediaItem2 does it.
+ Object lock = mFileDescriptorRegistry.registerMediaItemAndGetLock(
+ fileMediaItem2.getFileDescriptor());
fileDescriptor = FileDescriptorUtil.dup(fileMediaItem2.getFileDescriptor());
long offset = fileMediaItem2.getFileDescriptorOffset();
long length = fileMediaItem2.getFileDescriptorLength();
dataSourceFactory =
- FileDescriptorDataSource.getFactory(fileDescriptor, offset, length);
+ FileDescriptorDataSource.getFactory(fileDescriptor, offset, length, lock);
}
// Create a source for the item.
@@ -852,10 +887,14 @@
long endPosition = mediaItem2.getEndPosition();
if (startPosition != 0L || endPosition != MediaItem2.POSITION_UNKNOWN) {
durationProvidingMediaSource = new DurationProvidingMediaSource(mediaSource);
+ // Disable the initial discontinuity to give seamless transitions to clips.
mediaSource = new ClippingMediaSource(
durationProvidingMediaSource,
C.msToUs(startPosition),
- C.msToUs(endPosition));
+ C.msToUs(endPosition),
+ /* enableInitialDiscontinuity= */ false,
+ /* allowDynamicClippingUpdates= */ false,
+ /* relativeToDefaultPosition= */ true);
}
mediaSources.add(mediaSource);
@@ -863,6 +902,24 @@
new MediaItemInfo(mediaItem2, durationProvidingMediaSource, fileDescriptor));
}
+ private void releaseMediaItem(MediaItemInfo mediaItemInfo) {
+ MediaItem2 mediaItem = mediaItemInfo.mMediaItem;
+ try {
+ if (mediaItem instanceof FileMediaItem2) {
+ FileDescriptorUtil.close(mediaItemInfo.mFileDescriptor);
+ // TODO(b/68398926): Remove separate file descriptors once FileMediaItem2 dup's.
+ FileDescriptor fileDescriptor =
+ ((FileMediaItem2) mediaItem).getFileDescriptor();
+ mFileDescriptorRegistry.unregisterMediaItem(fileDescriptor);
+ } else if (mediaItem instanceof CallbackMediaItem2) {
+ ((CallbackMediaItem2) mediaItemInfo.mMediaItem)
+ .getDataSourceCallback2().close();
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Error releasing media item " + mediaItem, e);
+ }
+ }
+
}
}
diff --git a/media2/src/main/java/androidx/media2/exoplayer/FileDescriptorDataSource.java b/media2/src/main/java/androidx/media2/exoplayer/FileDescriptorDataSource.java
index f73b92e..aab490d 100644
--- a/media2/src/main/java/androidx/media2/exoplayer/FileDescriptorDataSource.java
+++ b/media2/src/main/java/androidx/media2/exoplayer/FileDescriptorDataSource.java
@@ -53,43 +53,48 @@
* @param fileDescriptor The file descriptor to read from.
* @param offset The start offset in the file descriptor.
* @param length The length of the range to read from the file descriptor.
+ * @param lock An object to synchronize on when using the file descriptor.
* @return A factory for data sources that read from the file descriptor.
*/
static DataSource.Factory getFactory(
- final FileDescriptor fileDescriptor, final long offset, final long length) {
+ final FileDescriptor fileDescriptor,
+ final long offset,
+ final long length,
+ final Object lock) {
return new DataSource.Factory() {
@Override
public DataSource createDataSource() {
- return new FileDescriptorDataSource(fileDescriptor, offset, length);
+ return new FileDescriptorDataSource(fileDescriptor, offset, length, lock);
}
};
}
// TODO(b/80232248): Move into core ExoPlayer library and delete this class.
- private final FileDescriptor mFactoryFileDescriptor;
+ private final FileDescriptor mFileDescriptor;
private final long mOffset;
private final long mLength;
+ private final Object mLock;
- private @Nullable FileDescriptor mFileDescriptor;
private @Nullable Uri mUri;
private @Nullable InputStream mInputStream;
private long mBytesRemaining;
private boolean mOpened;
+ private long mPosition;
- public FileDescriptorDataSource(FileDescriptor fileDescriptor, long offset, long length) {
+ FileDescriptorDataSource(
+ FileDescriptor fileDescriptor, long offset, long length, Object lock) {
super(/* isNetwork= */ false);
- mFactoryFileDescriptor = fileDescriptor;
+ mFileDescriptor = fileDescriptor;
mOffset = offset;
mLength = length;
+ mLock = lock;
}
@Override
- public long open(DataSpec dataSpec) throws IOException {
+ public long open(DataSpec dataSpec) {
mUri = dataSpec.uri;
transferInitializing(dataSpec);
- mFileDescriptor = FileDescriptorUtil.dup(mFactoryFileDescriptor);
- FileDescriptorUtil.seek(mFileDescriptor, mOffset + dataSpec.position);
mInputStream = new FileInputStream(mFileDescriptor);
if (dataSpec.length != C.LENGTH_UNSET) {
mBytesRemaining = dataSpec.length;
@@ -98,14 +103,14 @@
} else {
mBytesRemaining = C.LENGTH_UNSET;
}
+ mPosition = mOffset + dataSpec.position;
mOpened = true;
transferStarted(dataSpec);
return mBytesRemaining;
}
@Override
- public int read(byte[] buffer, int offset, int readLength)
- throws IOException {
+ public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (readLength == 0) {
return 0;
} else if (mBytesRemaining == 0) {
@@ -113,12 +118,18 @@
}
int bytesToRead = mBytesRemaining == C.LENGTH_UNSET
? readLength : (int) Math.min(mBytesRemaining, readLength);
- int bytesRead = Preconditions.checkNotNull(mInputStream).read(buffer, offset, bytesToRead);
- if (bytesRead == -1) {
- if (mBytesRemaining != C.LENGTH_UNSET) {
- throw new EOFException();
+ int bytesRead;
+ synchronized (mLock) {
+ // The file descriptor position is shared across all users, so seek before reading.
+ FileDescriptorUtil.seek(mFileDescriptor, mPosition);
+ bytesRead = Preconditions.checkNotNull(mInputStream).read(buffer, offset, bytesToRead);
+ if (bytesRead == -1) {
+ if (mBytesRemaining != C.LENGTH_UNSET) {
+ throw new EOFException();
+ }
+ return C.RESULT_END_OF_INPUT;
}
- return C.RESULT_END_OF_INPUT;
+ mPosition += bytesRead;
}
if (mBytesRemaining != C.LENGTH_UNSET) {
mBytesRemaining -= bytesRead;
@@ -129,7 +140,7 @@
@Override
public Uri getUri() {
- return mUri;
+ return Preconditions.checkNotNull(mUri);
}
@Override
@@ -141,13 +152,9 @@
}
} finally {
mInputStream = null;
- try {
- FileDescriptorUtil.close(mFileDescriptor);
- } finally {
- if (mOpened) {
- mOpened = false;
- transferEnded();
- }
+ if (mOpened) {
+ mOpened = false;
+ transferEnded();
}
}
}
diff --git a/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java b/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java
index f3426b5..9a6ff34 100644
--- a/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java
+++ b/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java
@@ -22,7 +22,9 @@
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.os.Build;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,6 +39,7 @@
/**
* This test checks weather MediaRouter is initialized well if an empty route exists
*/
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
@Test
@SmallTest
public void testEmptyUserRoute() throws Exception {
diff --git a/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 373fefd..ab2a3467 100644
--- a/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -18,7 +18,9 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.app.Activity;
import android.app.ActivityManager;
+import android.app.Application;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -2132,6 +2134,7 @@
}
}
};
+ private ForegroundChecker mForegroundChecker;
GlobalMediaRouter(Context applicationContext) {
mApplicationContext = applicationContext;
@@ -2144,6 +2147,10 @@
// the framework media router. This one is special and receives
// synchronization messages from the media router.
mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
+
+ mForegroundChecker = new ForegroundChecker();
+ Application app = (Application) mApplicationContext;
+ app.registerActivityLifecycleCallbacks(mForegroundChecker);
}
public void start() {
@@ -2341,6 +2348,7 @@
// Combine all of the callback selectors and active scan flags.
boolean discover = false;
boolean activeScan = false;
+
MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
for (int i = mRouters.size(); --i >= 0; ) {
MediaRouter router = mRouters.get(i).get();
@@ -2366,6 +2374,10 @@
}
}
}
+ // When the app is in background, remove discovery request
+ if (!activeScan && !mForegroundChecker.isForeground()) {
+ discover = false;
+ }
MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
// Create a new discovery request.
@@ -3244,5 +3256,63 @@
}
}
}
+
+ private static final class ForegroundChecker
+ implements Application.ActivityLifecycleCallbacks {
+ // Use the same delay in ProcessLifecycleOwner
+ public static final long DELAY_MS = 700;
+ private final Handler mHandler = new Handler();
+ // Assume we are in foreground if we have no further information
+ private boolean mIsForeground = true;
+ private boolean mIsStopped = false;
+ private Runnable mChecker = new Runnable() {
+ @Override
+ public void run() {
+ if (mIsForeground && mIsStopped) {
+ mIsForeground = false;
+ sGlobal.updateDiscoveryRequest();
+ }
+ }
+ };
+
+ public boolean isForeground() {
+ return mIsForeground;
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) { }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ mIsStopped = false;
+ boolean wasBackground = !mIsForeground;
+ mIsForeground = true;
+
+ mHandler.removeCallbacks(mChecker);
+ if (wasBackground) {
+ sGlobal.updateDiscoveryRequest();
+ }
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) { }
+
+ @Override
+ public void onActivityPaused(Activity activity) { }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ mIsStopped = true;
+ // By checking if onActivityStarted is not called after DELAY_MS
+ // we can note that the app goes to background
+ mHandler.postDelayed(mChecker, DELAY_MS);
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) { }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) { }
+ }
}
}
diff --git a/paging/common/api/2.0.0.txt b/paging/common/api/2.0.0.txt
index 7428bf6..ecb3bc1 100644
--- a/paging/common/api/2.0.0.txt
+++ b/paging/common/api/2.0.0.txt
@@ -124,10 +124,8 @@
}
public static class PagedList.Config {
- field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
field public final boolean enablePlaceholders;
field public final int initialLoadSizeHint;
- field public final int maxSize;
field public final int pageSize;
field public final int prefetchDistance;
}
@@ -137,7 +135,6 @@
method public androidx.paging.PagedList.Config build();
method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean);
method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(int);
- method public androidx.paging.PagedList.Config.Builder setMaxSize(int);
method public androidx.paging.PagedList.Config.Builder setPageSize(int);
method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(int);
}
diff --git a/paging/common/api/2.1.0-beta01.txt b/paging/common/api/2.1.0-beta01.txt
new file mode 100644
index 0000000..c8e0be1
--- /dev/null
+++ b/paging/common/api/2.1.0-beta01.txt
@@ -0,0 +1,182 @@
+// Signature format: 2.0
+package androidx.paging {
+
+ public abstract class DataSource<Key, Value> {
+ method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method @AnyThread public void invalidate();
+ method @WorkerThread public boolean isInvalid();
+ method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+ method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+ method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ }
+
+ public abstract static class DataSource.Factory<Key, Value> {
+ ctor public DataSource.Factory();
+ method public abstract androidx.paging.DataSource<Key,Value> create();
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+ }
+
+ public static interface DataSource.InvalidatedCallback {
+ method @AnyThread public void onInvalidated();
+ }
+
+ public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method public abstract Key getKey(Value);
+ method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+ method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+ method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value>);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+ }
+
+ public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+ ctor public ItemKeyedDataSource.LoadCallback();
+ method public abstract void onResult(java.util.List<Value>);
+ }
+
+ public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+ ctor public ItemKeyedDataSource.LoadInitialCallback();
+ method public abstract void onResult(java.util.List<Value>, int, int);
+ }
+
+ public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+ ctor public ItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ field public final boolean placeholdersEnabled;
+ field public final Key? requestedInitialKey;
+ field public final int requestedLoadSize;
+ }
+
+ public static class ItemKeyedDataSource.LoadParams<Key> {
+ ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ field public final Key key;
+ field public final int requestedLoadSize;
+ }
+
+ public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key>, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value>);
+ method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key>, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value>);
+ method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key>, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value>);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+ }
+
+ public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+ ctor public PageKeyedDataSource.LoadCallback();
+ method public abstract void onResult(java.util.List<Value>, Key?);
+ }
+
+ public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+ ctor public PageKeyedDataSource.LoadInitialCallback();
+ method public abstract void onResult(java.util.List<Value>, int, int, Key?, Key?);
+ method public abstract void onResult(java.util.List<Value>, Key?, Key?);
+ }
+
+ public static class PageKeyedDataSource.LoadInitialParams<Key> {
+ ctor public PageKeyedDataSource.LoadInitialParams(int, boolean);
+ field public final boolean placeholdersEnabled;
+ field public final int requestedLoadSize;
+ }
+
+ public static class PageKeyedDataSource.LoadParams<Key> {
+ ctor public PageKeyedDataSource.LoadParams(Key, int);
+ field public final Key key;
+ field public final int requestedLoadSize;
+ }
+
+ public abstract class PagedList<T> extends java.util.AbstractList<T> {
+ method public void addWeakCallback(java.util.List<T>?, androidx.paging.PagedList.Callback);
+ method public void detach();
+ method public T? get(int);
+ method public androidx.paging.PagedList.Config getConfig();
+ method public abstract androidx.paging.DataSource<?,T> getDataSource();
+ method public abstract Object? getLastKey();
+ method public int getLoadedCount();
+ method public int getPositionOffset();
+ method public boolean isDetached();
+ method public boolean isImmutable();
+ method public void loadAround(int);
+ method public void removeWeakCallback(androidx.paging.PagedList.Callback);
+ method public int size();
+ method public java.util.List<T> snapshot();
+ }
+
+ @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+ ctor public PagedList.BoundaryCallback();
+ method public void onItemAtEndLoaded(T);
+ method public void onItemAtFrontLoaded(T);
+ method public void onZeroItemsLoaded();
+ }
+
+ public static final class PagedList.Builder<Key, Value> {
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value>, androidx.paging.PagedList.Config);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value>, int);
+ method @WorkerThread public androidx.paging.PagedList<Value> build();
+ method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback?);
+ method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor);
+ method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key?);
+ method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor);
+ }
+
+ public abstract static class PagedList.Callback {
+ ctor public PagedList.Callback();
+ method public abstract void onChanged(int, int);
+ method public abstract void onInserted(int, int);
+ method public abstract void onRemoved(int, int);
+ }
+
+ public static class PagedList.Config {
+ field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+ field public final boolean enablePlaceholders;
+ field public final int initialLoadSizeHint;
+ field public final int maxSize;
+ field public final int pageSize;
+ field public final int prefetchDistance;
+ }
+
+ public static final class PagedList.Config.Builder {
+ ctor public PagedList.Config.Builder();
+ method public androidx.paging.PagedList.Config build();
+ method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean);
+ method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int);
+ method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int);
+ method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int);
+ method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int);
+ }
+
+ public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+ method public static int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int);
+ method public static int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int);
+ method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T>);
+ method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T>);
+ method public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V>);
+ method public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>>);
+ }
+
+ public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+ ctor public PositionalDataSource.LoadInitialCallback();
+ method public abstract void onResult(java.util.List<T>, int, int);
+ method public abstract void onResult(java.util.List<T>, int);
+ }
+
+ public static class PositionalDataSource.LoadInitialParams {
+ ctor public PositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ field public final int pageSize;
+ field public final boolean placeholdersEnabled;
+ field public final int requestedLoadSize;
+ field public final int requestedStartPosition;
+ }
+
+ public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+ ctor public PositionalDataSource.LoadRangeCallback();
+ method public abstract void onResult(java.util.List<T>);
+ }
+
+ public static class PositionalDataSource.LoadRangeParams {
+ ctor public PositionalDataSource.LoadRangeParams(int, int);
+ field public final int loadSize;
+ field public final int startPosition;
+ }
+
+}
+
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index 3d74916..8e166b3 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -30,7 +30,7 @@
dependencies {
compile(SUPPORT_ANNOTATIONS)
- compile(project(":arch:core-common"))
+ compile("androidx.arch.core:core-common:2.0.0")
testCompile(JUNIT)
testCompile(MOCKITO_CORE)
diff --git a/paging/common/ktx/api/2.1.0-beta01.txt b/paging/common/ktx/api/2.1.0-beta01.txt
new file mode 100644
index 0000000..0f070c2
--- /dev/null
+++ b/paging/common/ktx/api/2.1.0-beta01.txt
@@ -0,0 +1,15 @@
+// Signature format: 2.0
+package androidx.paging {
+
+ public final class PagedListConfigKt {
+ ctor public PagedListConfigKt();
+ method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
+ }
+
+ public final class PagedListKt {
+ ctor public PagedListKt();
+ method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
+ }
+
+}
+
diff --git a/paging/runtime/api/2.1.0-beta01.txt b/paging/runtime/api/2.1.0-beta01.txt
new file mode 100644
index 0000000..0f658dfd
--- /dev/null
+++ b/paging/runtime/api/2.1.0-beta01.txt
@@ -0,0 +1,42 @@
+// Signature format: 2.0
+package androidx.paging {
+
+ public class AsyncPagedListDiffer<T> {
+ ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T>);
+ ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T>);
+ method public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T>);
+ method public androidx.paging.PagedList<T>? getCurrentList();
+ method public T? getItem(int);
+ method public int getItemCount();
+ method public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T>);
+ method public void submitList(androidx.paging.PagedList<T>?);
+ method public void submitList(androidx.paging.PagedList<T>?, Runnable?);
+ }
+
+ public static interface AsyncPagedListDiffer.PagedListListener<T> {
+ method public void onCurrentListChanged(androidx.paging.PagedList<T>?, androidx.paging.PagedList<T>?);
+ }
+
+ public final class LivePagedListBuilder<Key, Value> {
+ ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config);
+ ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value>, int);
+ method public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+ method public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>?);
+ method public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor);
+ method public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key?);
+ }
+
+ public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+ ctor protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>);
+ ctor protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T>);
+ method public androidx.paging.PagedList<T>? getCurrentList();
+ method protected T? getItem(int);
+ method public int getItemCount();
+ method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>?);
+ method public void onCurrentListChanged(androidx.paging.PagedList<T>?, androidx.paging.PagedList<T>?);
+ method public void submitList(androidx.paging.PagedList<T>?);
+ method public void submitList(androidx.paging.PagedList<T>?, Runnable?);
+ }
+
+}
+
diff --git a/paging/runtime/build.gradle b/paging/runtime/build.gradle
index 1d5d447..2b0ec98 100644
--- a/paging/runtime/build.gradle
+++ b/paging/runtime/build.gradle
@@ -36,8 +36,7 @@
api("androidx.arch.core:core-runtime:2.0.0")
api("androidx.lifecycle:lifecycle-runtime:2.0.0")
api("androidx.lifecycle:lifecycle-livedata:2.0.0")
-
- api(SUPPORT_RECYCLERVIEW, libs.support_exclude_config)
+ api("androidx.recyclerview:recyclerview:1.0.0", libs.support_exclude_config)
androidTestImplementation(JUNIT)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
diff --git a/paging/runtime/ktx/api/2.1.0-beta01.txt b/paging/runtime/ktx/api/2.1.0-beta01.txt
new file mode 100644
index 0000000..b7750be
--- /dev/null
+++ b/paging/runtime/ktx/api/2.1.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 2.0
+package androidx.paging {
+
+ public final class LivePagedListKt {
+ ctor public LivePagedListKt();
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ }
+
+}
+
diff --git a/paging/rxjava2/api/2.1.0-beta01.txt b/paging/rxjava2/api/2.1.0-beta01.txt
new file mode 100644
index 0000000..067f422
--- /dev/null
+++ b/paging/rxjava2/api/2.1.0-beta01.txt
@@ -0,0 +1,16 @@
+// Signature format: 2.0
+package androidx.paging {
+
+ public final class RxPagedListBuilder<Key, Value> {
+ ctor public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config);
+ ctor public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value>, int);
+ method public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy);
+ method public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+ method public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>?);
+ method public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler);
+ method public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key?);
+ method public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler);
+ }
+
+}
+
diff --git a/paging/rxjava2/build.gradle b/paging/rxjava2/build.gradle
index bf0ed5b..b011d9c 100644
--- a/paging/rxjava2/build.gradle
+++ b/paging/rxjava2/build.gradle
@@ -31,9 +31,10 @@
}
dependencies {
- api(project(":arch:core-runtime"))
api(project(":paging:paging-common"))
+ api("androidx.arch.core:core-runtime:2.0.0")
+
api(RX_JAVA)
androidTestImplementation(JUNIT)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
diff --git a/paging/rxjava2/ktx/api/2.1.0-beta01.txt b/paging/rxjava2/ktx/api/2.1.0-beta01.txt
new file mode 100644
index 0000000..6b573b4
--- /dev/null
+++ b/paging/rxjava2/ktx/api/2.1.0-beta01.txt
@@ -0,0 +1,13 @@
+// Signature format: 2.0
+package androidx.paging {
+
+ public final class RxPagedListKt {
+ ctor public RxPagedListKt();
+ method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, io.reactivex.Scheduler? fetchScheduler = null, io.reactivex.Scheduler? notifyScheduler = null, io.reactivex.BackpressureStrategy backpressureStrategy = io.reactivex.BackpressureStrategy.LATEST);
+ method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, io.reactivex.Scheduler? fetchScheduler = null, io.reactivex.Scheduler? notifyScheduler = null, io.reactivex.BackpressureStrategy backpressureStrategy = io.reactivex.BackpressureStrategy.LATEST);
+ method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, io.reactivex.Scheduler? fetchScheduler = null, io.reactivex.Scheduler? notifyScheduler = null);
+ method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, io.reactivex.Scheduler? fetchScheduler = null, io.reactivex.Scheduler? notifyScheduler = null);
+ }
+
+}
+
diff --git a/palette/ktx/build.gradle b/palette/ktx/build.gradle
index c4a9ed4..d999771 100644
--- a/palette/ktx/build.gradle
+++ b/palette/ktx/build.gradle
@@ -41,7 +41,7 @@
supportLibrary {
name = "Palette Kotlin Extensions"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.PALETTE_KTX
mavenGroup = LibraryGroups.PALETTE
inceptionYear = "2018"
description = "Kotlin extensions for 'palette' artifact"
diff --git a/percent/build.gradle b/percent/build.gradle
index 7faabd5..35f4af5 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -23,7 +23,7 @@
supportLibrary {
name = "Android Percent Support Library"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.PERCENTLAYOUT
mavenGroup = LibraryGroups.PERCENTLAYOUT
inceptionYear = "2015"
description = "Android Percent Support Library"
diff --git a/preference/build.gradle b/preference/build.gradle
index fc69785..350fb4f 100644
--- a/preference/build.gradle
+++ b/preference/build.gradle
@@ -23,11 +23,11 @@
}
dependencies {
- api(project(":core"))
- api(project(":collection"))
- api(project(":fragment"))
- api(project(":appcompat"))
- api(project(":recyclerview"))
+ api("androidx.core:core:1.1.0-alpha01")
+ api("androidx.collection:collection:1.0.0")
+ api("androidx.fragment:fragment:1.1.0-alpha01")
+ api("androidx.appcompat:appcompat:1.0.0")
+ api("androidx.recyclerview:recyclerview:1.0.0")
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(TEST_RULES)
diff --git a/preference/ktx/build.gradle b/preference/ktx/build.gradle
index 2a43336..d344a29 100644
--- a/preference/ktx/build.gradle
+++ b/preference/ktx/build.gradle
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.*
+
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
-import androidx.build.SupportLibraryExtension
+
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
@@ -41,12 +42,13 @@
androidTestImplementation(TEST_RULES)
androidTestImplementation(TRUTH)
androidTestImplementation(project(":internal-testutils-ktx"))
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
supportLibrary {
name = "Android Preferences KTX"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.PREFERENCE_KTX
mavenGroup = LibraryGroups.PREFERENCE
inceptionYear = "2018"
description = "Kotlin extensions for preferences"
diff --git a/print/build.gradle b/print/build.gradle
index 7901514..98a973c 100644
--- a/print/build.gradle
+++ b/print/build.gradle
@@ -12,7 +12,7 @@
supportLibrary {
name = "Android Support Library Print"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.PRINT
mavenGroup = LibraryGroups.PRINT
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/recommendation/build.gradle b/recommendation/build.gradle
index 3b4ea70..37ffe4b 100644
--- a/recommendation/build.gradle
+++ b/recommendation/build.gradle
@@ -18,7 +18,7 @@
supportLibrary {
name = "Android Support Recommendation"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.RECOMMENDATION
mavenGroup = LibraryGroups.RECOMMENDATION
inceptionYear = "2015"
description = "Android Support Recommendation"
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java
index 36a34f7..9a02110 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java
@@ -788,6 +788,10 @@
this.mFocusable = mFocusable;
}
+ public String getDisplayText() {
+ return mText + "(" + mId + ")";
+ }
+
@Override
public String toString() {
return "Item{" +
@@ -897,7 +901,7 @@
assertNotNull(holder.mOwnerRecyclerView);
assertEquals(position, holder.getAdapterPosition());
final Item item = mItems.get(position);
- ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mId + ")");
+ ((TextView) (holder.itemView)).setText(item.getDisplayText());
holder.itemView.setBackgroundColor(position % 2 == 0 ? 0xFFFF0000 : 0xFF0000FF);
holder.mBoundItem = item;
if (mLayoutParams != null) {
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/PagerSnapHelperTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/PagerSnapHelperTest.java
index 8bef928..c65c9eb 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/PagerSnapHelperTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/PagerSnapHelperTest.java
@@ -20,7 +20,12 @@
import static androidx.recyclerview.widget.RecyclerView.HORIZONTAL;
import static androidx.recyclerview.widget.RecyclerView.VERTICAL;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.CoreMatchers.allOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
@@ -29,8 +34,10 @@
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.LargeTest;
+import androidx.testutils.SwipeToLocation;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,6 +45,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@LargeTest
@@ -178,6 +187,14 @@
runSnapOnMaxFlingNextView(mRecyclerView.getMaxFlingVelocity());
}
+ @Test
+ public void snapWhenFlingToSnapPosition() throws Throwable {
+ final Config config = (Config) mConfig.clone();
+ setupByConfig(config, true, getChildLayoutParams(), getParentLayoutParams());
+ setupSnapHelper();
+ runSnapOnFlingExactlyToNextView();
+ }
+
private RecyclerView.LayoutParams getParentLayoutParams() {
return new RecyclerView.LayoutParams(RECYCLERVIEW_SIZE, RECYCLERVIEW_SIZE);
}
@@ -207,7 +224,6 @@
waitForIdleScroll(mRecyclerView);
assertTrue(fling(velocityDir, velocityDir));
mLayoutManager.waitForSnap(100);
- getInstrumentation().waitForIdleSync();
View viewAfterFling = findCenterView(mLayoutManager);
@@ -219,6 +235,43 @@
assertCenterAligned(viewAfterFling);
}
+ private void runSnapOnFlingExactlyToNextView() throws Throwable {
+ // Record the current center view.
+ View view = findCenterView(mLayoutManager);
+ assertCenterAligned(view);
+
+ // Determine the target item to scroll to
+ final int expectedPosition = mConfig.mItemCount / 2 + (mConfig.mReverseLayout
+ ? (mReverseScroll ? 1 : -1)
+ : (mReverseScroll ? -1 : 1));
+
+ // Smooth scroll in the correct direction to allow fling snapping to the next view.
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mRecyclerView.smoothScrollToPosition(expectedPosition);
+ }
+ });
+ waitForDistanceToTarget(expectedPosition, .5f);
+
+ // Interrupt scroll and fling to target view, ending exactly when the view is snapped
+ mLayoutManager.expectIdleState(1);
+ onView(allOf(
+ isDescendantOfA(isAssignableFrom(RecyclerView.class)),
+ withText(mTestAdapter.getItemAt(expectedPosition).getDisplayText())
+ )).perform(SwipeToLocation.flingToCenter());
+ waitForIdleScroll(mRecyclerView);
+
+ // Wait until the RecyclerView comes to a rest
+ mLayoutManager.waitForSnap(100);
+
+ // Check the result
+ View viewAfterFling = findCenterView(mLayoutManager);
+ assertNotSame("The view should have scrolled", view, viewAfterFling);
+ assertEquals(expectedPosition, mLayoutManager.getPosition(viewAfterFling));
+ assertCenterAligned(viewAfterFling);
+ }
+
private void setupSnapHelper() throws Throwable {
SnapHelper snapHelper = new PagerSnapHelper();
@@ -314,4 +367,35 @@
waitForIdleScroll(mRecyclerView);
return true;
}
+
+ /**
+ * Waits until the RecyclerView has smooth scrolled till within the given margin from the target
+ * item. The percentage is relative to the size of the target view.
+ *
+ * @param targetPosition The adapter position of the view we want to scroll to
+ * @param distancePercent The distance from the view when we stop waiting, relative to the
+ * target view
+ */
+ private void waitForDistanceToTarget(final int targetPosition, final float distancePercent)
+ throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ View target = mLayoutManager.findViewByPosition(targetPosition);
+ if (target == null) {
+ return;
+ }
+ int distancePx = distFromCenter(target);
+ int size = mConfig.mOrientation == HORIZONTAL
+ ? target.getWidth()
+ : target.getHeight();
+ if ((float) distancePx / size <= distancePercent) {
+ latch.countDown();
+ }
+ }
+ });
+ assertTrue("should be close enough to the target view within 10 seconds",
+ latch.await(10, TimeUnit.SECONDS));
+ }
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewFocusRecoveryTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewFocusRecoveryTest.java
index 8434c3ac9..415f45c 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewFocusRecoveryTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewFocusRecoveryTest.java
@@ -916,7 +916,7 @@
}
protected String getText(Item item) {
- return item.mText + "(" + item.mId + ")";
+ return item.getDisplayText();
}
abstract void setFocusable(boolean focusable);
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
index caf4a54..5978b64 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
@@ -357,7 +357,7 @@
Item item = mItems.get(position);
holder.mBoundItem = item;
((EditText) ((FrameLayout) holder.itemView).getChildAt(0)).setText(
- item.mText + " (" + item.mId + ")");
+ item.getDisplayText());
// Good to have colors for debugging
StateListDrawable stl = new StateListDrawable();
stl.addState(new int[]{android.R.attr.state_focused},
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java
index 1dbc495..3592602 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java
@@ -177,9 +177,8 @@
final int dx = snapDistances[0];
final int dy = snapDistances[1];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
- if (time > 0) {
- action.update(dx, dy, time, mDecelerateInterpolator);
- }
+ // TODO: revert change in next line when b/118663993 is fixed
+ action.update(dx, dy, Math.max(1, time), mDecelerateInterpolator);
}
@Override
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
index 1bb351b..ba49625 100644
--- a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
@@ -82,6 +82,8 @@
ClassName.get("androidx.room.paging", "LimitOffsetDataSource")
val DB_UTIL: ClassName =
ClassName.get("androidx.room.util", "DBUtil")
+ val CURSOR_UTIL: ClassName =
+ ClassName.get("androidx.room.util", "CursorUtil")
}
object PagingTypeNames {
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
index 03e7c20..c1e97ca 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
@@ -17,6 +17,7 @@
package androidx.room.solver.query.result
import androidx.room.ext.L
+import androidx.room.ext.RoomTypeNames
import androidx.room.ext.S
import androidx.room.ext.T
import androidx.room.processor.Context
@@ -118,8 +119,9 @@
} else {
"getColumnIndexOrThrow"
}
- scope.builder().addStatement("final $T $L = $L.$L($S)",
- TypeName.INT, indexVar, cursorVarName, indexMethod, it.columnName)
+ scope.builder().addStatement("final $T $L = $T.$L($L, $S)",
+ TypeName.INT, indexVar, RoomTypeNames.CURSOR_UTIL, indexMethod, cursorVarName,
+ it.columnName)
FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = info != null)
}
if (relationCollectors.isNotEmpty()) {
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index 5cd3758..ae6aad7 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -503,11 +503,16 @@
elm: ExecutableElement,
owner: DeclaredType
): MethodSpec.Builder {
- val baseSpec = MethodSpec.overriding(elm, owner, processingEnv.typeUtils).build()
+ val baseSpec = MethodSpec.overriding(elm, owner, processingEnv.typeUtils)
+ .build()
+
+ // make all the params final
+ val params = baseSpec.parameters.map { it.toBuilder().addModifiers(FINAL).build() }
+
return MethodSpec.methodBuilder(baseSpec.name).apply {
addAnnotation(Override::class.java)
addModifiers(baseSpec.modifiers)
- addParameters(baseSpec.parameters)
+ addParameters(params)
varargs(baseSpec.varargs)
returns(baseSpec.returnType)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
index 4345ea1..391f386 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
@@ -129,8 +129,9 @@
if (shouldCopyCursor) "true" else "false")
beginControlFlow("try").apply {
- addStatement("final $T $L = $L.getColumnIndex($S)",
- TypeName.INT, itemKeyIndexVar, cursorVar, relation.entityField.columnName)
+ addStatement("final $T $L = $T.getColumnIndex($L, $S)",
+ TypeName.INT, itemKeyIndexVar, RoomTypeNames.CURSOR_UTIL, cursorVar,
+ relation.entityField.columnName)
beginControlFlow("if ($L == -1)", itemKeyIndexVar).apply {
addStatement("return")
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index 8efa8d3..52b387e 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -7,6 +7,7 @@
import androidx.room.InvalidationTracker.Observer;
import androidx.room.RoomDatabase;
import androidx.room.RoomSQLiteQuery;
+import androidx.room.util.CursorUtil;
import androidx.room.util.DBUtil;
import androidx.room.util.StringUtil;
import java.lang.Integer;
@@ -30,7 +31,7 @@
}
@Override
- public boolean transactionMethod(int i, String s, long l) {
+ public boolean transactionMethod(final int i, final String s, final long l) {
__db.beginTransaction();
try {
boolean _result = super.transactionMethod(i, s, l);
@@ -42,15 +43,15 @@
}
@Override
- public List<ComplexDao.FullName> fullNames(int id) {
+ public List<ComplexDao.FullName> fullNames(final int id) {
final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, id);
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
- final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName");
- final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
+ final int _cursorIndexOfFullName = CursorUtil.getColumnIndexOrThrow(_cursor, "fullName");
+ final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
while(_cursor.moveToNext()) {
final ComplexDao.FullName _item;
@@ -67,17 +68,17 @@
}
@Override
- public User getById(int id) {
+ public User getById(final int id) {
final String _sql = "SELECT * FROM user where uid = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, id);
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
- final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
- final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
- final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
- final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+ final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+ final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+ final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
final User _result;
if(_cursor.moveToFirst()) {
_result = new User();
@@ -98,7 +99,7 @@
}
@Override
- public User findByName(String name, String lastName) {
+ public User findByName(final String name, final String lastName) {
final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
int _argIndex = 1;
@@ -115,10 +116,10 @@
}
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
- final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
- final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
- final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
- final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+ final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+ final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+ final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
final User _result;
if(_cursor.moveToFirst()) {
_result = new User();
@@ -139,7 +140,7 @@
}
@Override
- public List<User> loadAllByIds(int... ids) {
+ public List<User> loadAllByIds(final int... ids) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT * FROM user where uid IN (");
final int _inputSize = ids.length;
@@ -155,10 +156,10 @@
}
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
- final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
- final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
- final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
- final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+ final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+ final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+ final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item_1;
@@ -179,7 +180,7 @@
}
@Override
- int getAge(int id) {
+ int getAge(final int id) {
final String _sql = "SELECT ageColumn FROM user where uid = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
@@ -200,7 +201,7 @@
}
@Override
- public int[] getAllAges(int... ids) {
+ public int[] getAllAges(final int... ids) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids.length;
@@ -232,7 +233,7 @@
}
@Override
- public List<Integer> getAllAgesAsList(List<Integer> ids) {
+ public List<Integer> getAllAgesAsList(final List<Integer> ids) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids.size();
@@ -270,7 +271,7 @@
}
@Override
- public LiveData<User> getByIdLive(int id) {
+ public LiveData<User> getByIdLive(final int id) {
final String _sql = "SELECT * FROM user where uid = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
@@ -291,10 +292,10 @@
}
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
- final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
- final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
- final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
- final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+ final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+ final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+ final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
final User _result;
if(_cursor.moveToFirst()) {
_result = new User();
@@ -321,7 +322,7 @@
}
@Override
- public LiveData<List<User>> loadUsersByIdsLive(int... ids) {
+ public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT * FROM user where uid IN (");
final int _inputSize = ids.length;
@@ -351,10 +352,10 @@
}
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
- final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
- final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
- final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
- final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+ final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+ final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+ final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item_1;
@@ -381,7 +382,7 @@
}
@Override
- public List<Integer> getAllAgesAsList(List<Integer> ids1, int[] ids2, int... ids3) {
+ public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2, final int... ids3) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids1.size();
diff --git a/room/compiler/src/test/data/daoWriter/output/DeletionDao.java b/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
index d34345b..59fae03 100644
--- a/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
@@ -95,7 +95,7 @@
}
@Override
- public void deleteUser(User user) {
+ public void deleteUser(final User user) {
__db.beginTransaction();
try {
__deletionAdapterOfUser.handle(user);
@@ -106,7 +106,7 @@
}
@Override
- public void deleteUsers(User user1, List<User> others) {
+ public void deleteUsers(final User user1, final List<User> others) {
__db.beginTransaction();
try {
__deletionAdapterOfUser.handle(user1);
@@ -118,7 +118,7 @@
}
@Override
- public void deleteArrayOfUsers(User[] users) {
+ public void deleteArrayOfUsers(final User[] users) {
__db.beginTransaction();
try {
__deletionAdapterOfUser.handleMultiple(users);
@@ -129,7 +129,7 @@
}
@Override
- public Integer deleteUserAndReturnCountObject(User user) {
+ public Integer deleteUserAndReturnCountObject(final User user) {
int _total = 0;
__db.beginTransaction();
try {
@@ -142,7 +142,7 @@
}
@Override
- public int deleteUserAndReturnCount(User user) {
+ public int deleteUserAndReturnCount(final User user) {
int _total = 0;
__db.beginTransaction();
try {
@@ -155,7 +155,7 @@
}
@Override
- public int deleteUserAndReturnCount(User user1, List<User> others) {
+ public int deleteUserAndReturnCount(final User user1, final List<User> others) {
int _total = 0;
__db.beginTransaction();
try {
@@ -169,7 +169,7 @@
}
@Override
- public int deleteUserAndReturnCount(User[] users) {
+ public int deleteUserAndReturnCount(final User[] users) {
int _total = 0;
__db.beginTransaction();
try {
@@ -182,7 +182,7 @@
}
@Override
- public Completable deleteUserCompletable(User user) {
+ public Completable deleteUserCompletable(final User user) {
return Completable.fromCallable(new Callable() {
@Override
public Void call() throws Exception {
@@ -199,7 +199,7 @@
}
@Override
- public Single<Integer> deleteUserSingle(User user) {
+ public Single<Integer> deleteUserSingle(final User user) {
return Single.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
@@ -217,7 +217,7 @@
}
@Override
- public Maybe<Integer> deleteUserMaybe(User user) {
+ public Maybe<Integer> deleteUserMaybe(final User user) {
return Maybe.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
@@ -235,7 +235,7 @@
}
@Override
- public int multiPKey(MultiPKeyEntity entity) {
+ public int multiPKey(final MultiPKeyEntity entity) {
int _total = 0;
__db.beginTransaction();
try {
@@ -248,7 +248,7 @@
}
@Override
- public void deleteUserAndBook(User user, Book book) {
+ public void deleteUserAndBook(final User user, final Book book) {
__db.beginTransaction();
try {
__deletionAdapterOfUser.handle(user);
@@ -260,7 +260,7 @@
}
@Override
- public int deleteByUid(int uid) {
+ public int deleteByUid(final int uid) {
final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
__db.beginTransaction();
try {
@@ -290,7 +290,7 @@
}
@Override
- public int deleteByUidList(int... uid) {
+ public int deleteByUidList(final int... uid) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("DELETE FROM user where uid IN(");
final int _inputSize = uid.length;
diff --git a/room/compiler/src/test/data/daoWriter/output/UpdateDao.java b/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
index f9ba14a..8eb9945 100644
--- a/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
@@ -117,7 +117,7 @@
}
@Override
- public void updateUser(User user) {
+ public void updateUser(final User user) {
__db.beginTransaction();
try {
__updateAdapterOfUser.handle(user);
@@ -128,7 +128,7 @@
}
@Override
- public void updateUsers(User user1, List<User> others) {
+ public void updateUsers(final User user1, final List<User> others) {
__db.beginTransaction();
try {
__updateAdapterOfUser.handle(user1);
@@ -140,7 +140,7 @@
}
@Override
- public void updateArrayOfUsers(User[] users) {
+ public void updateArrayOfUsers(final User[] users) {
__db.beginTransaction();
try {
__updateAdapterOfUser.handleMultiple(users);
@@ -151,7 +151,7 @@
}
@Override
- public int updateUserAndReturnCount(User user) {
+ public int updateUserAndReturnCount(final User user) {
int _total = 0;
__db.beginTransaction();
try {
@@ -164,7 +164,7 @@
}
@Override
- public int updateUserAndReturnCount(User user1, List<User> others) {
+ public int updateUserAndReturnCount(final User user1, final List<User> others) {
int _total = 0;
__db.beginTransaction();
try {
@@ -178,7 +178,7 @@
}
@Override
- public int updateUserAndReturnCount(User[] users) {
+ public int updateUserAndReturnCount(final User[] users) {
int _total = 0;
__db.beginTransaction();
try {
@@ -191,7 +191,7 @@
}
@Override
- public Integer updateUserAndReturnCountObject(User user) {
+ public Integer updateUserAndReturnCountObject(final User user) {
int _total = 0;
__db.beginTransaction();
try {
@@ -204,7 +204,7 @@
}
@Override
- public Completable updateUserAndReturnCountCompletable(User user) {
+ public Completable updateUserAndReturnCountCompletable(final User user) {
return Completable.fromCallable(new Callable() {
@Override
public Void call() throws Exception {
@@ -221,7 +221,7 @@
}
@Override
- public Single<Integer> updateUserAndReturnCountSingle(User user) {
+ public Single<Integer> updateUserAndReturnCountSingle(final User user) {
return Single.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
@@ -239,7 +239,7 @@
}
@Override
- public Maybe<Integer> updateUserAndReturnCountMaybe(User user) {
+ public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
return Maybe.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
@@ -257,7 +257,7 @@
}
@Override
- public int multiPKey(MultiPKeyEntity entity) {
+ public int multiPKey(final MultiPKeyEntity entity) {
int _total = 0;
__db.beginTransaction();
try {
@@ -270,7 +270,7 @@
}
@Override
- public void updateUserAndBook(User user, Book book) {
+ public void updateUserAndBook(final User user, final Book book) {
__db.beginTransaction();
try {
__updateAdapterOfUser.handle(user);
@@ -282,7 +282,7 @@
}
@Override
- public void updateAndAge(User user) {
+ public void updateAndAge(final User user) {
__db.beginTransaction();
try {
UpdateDao.super.updateAndAge(user);
@@ -293,7 +293,7 @@
}
@Override
- public void ageUserByUid(String uid) {
+ public void ageUserByUid(final String uid) {
final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire();
__db.beginTransaction();
try {
diff --git a/room/compiler/src/test/data/daoWriter/output/WriterDao.java b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
index d2a1576..0e4a986 100644
--- a/room/compiler/src/test/data/daoWriter/output/WriterDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
@@ -100,7 +100,7 @@
}
@Override
- public void insertUser(User user) {
+ public void insertUser(final User user) {
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(user);
@@ -111,7 +111,7 @@
}
@Override
- public void insertUsers(User user1, List<User> others) {
+ public void insertUsers(final User user1, final List<User> others) {
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(user1);
@@ -123,7 +123,7 @@
}
@Override
- public void insertUsers(User[] users) {
+ public void insertUsers(final User[] users) {
__db.beginTransaction();
try {
__insertionAdapterOfUser_1.insert(users);
@@ -134,7 +134,7 @@
}
@Override
- public void insertUserAndBook(User user, Book book) {
+ public void insertUserAndBook(final User user, final Book book) {
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(user);
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
index 98b18be..9311bff 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
@@ -55,6 +55,15 @@
@Insert
fun addPublishersMaybe(vararg publishers: Publisher): Maybe<List<Long>>
+ @Insert
+ fun addPublisherSingle(publisher: Publisher): Single<Long>
+
+ @Insert
+ fun addPublisherCompletable(publisher: Publisher): Completable
+
+ @Insert
+ fun addPublisherMaybe(publisher: Publisher): Maybe<Long>
+
@Delete
fun deletePublishers(vararg publishers: Publisher)
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
index aa463f8..c7aa469 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
@@ -33,6 +33,7 @@
import androidx.room.integration.testapp.vo.Day;
import androidx.room.integration.testapp.vo.NameAndLastName;
import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserSummary;
import androidx.sqlite.db.SupportSQLiteQuery;
import org.reactivestreams.Publisher;
@@ -42,6 +43,7 @@
import java.util.Set;
import java.util.concurrent.Callable;
+import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
@@ -88,6 +90,15 @@
public abstract int update(User user);
@Update
+ public abstract Completable updateCompletable(User user);
+
+ @Update
+ public abstract Single<Integer> updateSingle(User user);
+
+ @Update
+ public abstract Single<Integer> updateSingleUsers(User user1, User user2);
+
+ @Update
public abstract int updateAll(List<User> users);
@Insert
@@ -248,4 +259,8 @@
+ "OR mName LIKE '%' || 'video' || '%' "
+ "OR mName LIKE '%' || 'games' || '%' ")
public abstract List<User> getUserWithCoolNames();
+
+ // The subquery is intentional (b/118398616)
+ @Query("SELECT `mId`, `mName` FROM (SELECT * FROM User)")
+ public abstract List<UserSummary> getNames();
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index cbe76b0..2f05f72 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -46,6 +46,7 @@
import androidx.room.integration.testapp.vo.Product;
import androidx.room.integration.testapp.vo.User;
import androidx.room.integration.testapp.vo.UserAndAllPets;
+import androidx.room.integration.testapp.vo.UserSummary;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -628,6 +629,16 @@
}
+ @Test
+ public void subquery() {
+ User user = TestUtil.createUser(3);
+ user.setName("john");
+ mUserDao.insert(user);
+ List<UserSummary> users = mUserDao.getNames();
+ assertThat(users, hasSize(1));
+ assertThat(users.get(0).getName(), is(equalTo("john")));
+ }
+
private Set<Day> toSet(Day... days) {
return new HashSet<>(Arrays.asList(days));
}
diff --git a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistryOwner.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserSummary.java
similarity index 63%
copy from lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistryOwner.java
copy to room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserSummary.java
index 9ac507b..c76e0b1 100644
--- a/lifecycle/savedstate-core/src/main/java/androidx/lifecycle/SavedStateRegistryOwner.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserSummary.java
@@ -14,20 +14,27 @@
* limitations under the License.
*/
-package androidx.lifecycle;
+package androidx.room.integration.testapp.vo;
+public class UserSummary {
-import androidx.annotation.NonNull;
+ private int mId;
-/**
- * A scope that owns {@link SavedStateRegistry}
- */
-public interface SavedStateRegistryOwner {
- /**
- * Returns owned {@link SavedStateRegistry}
- *
- * @return a {@link SavedStateRegistry}
- */
- @NonNull
- SavedStateRegistry getSavedState();
+ private String mName;
+
+ public int getId() {
+ return mId;
+ }
+
+ public void setId(int id) {
+ mId = id;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
}
diff --git a/room/runtime/src/main/java/androidx/room/util/CursorUtil.java b/room/runtime/src/main/java/androidx/room/util/CursorUtil.java
index 317029d..53ef36c 100644
--- a/room/runtime/src/main/java/androidx/room/util/CursorUtil.java
+++ b/room/runtime/src/main/java/androidx/room/util/CursorUtil.java
@@ -75,6 +75,39 @@
return matrixCursor;
}
+ /**
+ * Patches {@link Cursor#getColumnIndex(String)} to work around issues on older devices.
+ * If the column is not found, it retries with the specified name surrounded by backticks.
+ *
+ * @param c The cursor.
+ * @param name The name of the target column.
+ * @return The index of the column, or -1 if not found.
+ */
+ public static int getColumnIndex(@NonNull Cursor c, @NonNull String name) {
+ final int index = c.getColumnIndex(name);
+ if (index >= 0) {
+ return index;
+ }
+ return c.getColumnIndex("`" + name + "`");
+ }
+
+ /**
+ * Patches {@link Cursor#getColumnIndexOrThrow(String)} to work around issues on older devices.
+ * If the column is not found, it retries with the specified name surrounded by backticks.
+ *
+ * @param c The cursor.
+ * @param name The name of the target column.
+ * @return The index of the column.
+ * @throws IllegalArgumentException if the column does not exist.
+ */
+ public static int getColumnIndexOrThrow(@NonNull Cursor c, @NonNull String name) {
+ final int index = c.getColumnIndex(name);
+ if (index >= 0) {
+ return index;
+ }
+ return c.getColumnIndexOrThrow("`" + name + "`");
+ }
+
private CursorUtil() {
}
}
diff --git a/samples/SupportCarDemos/src/main/AndroidManifest.xml b/samples/SupportCarDemos/src/main/AndroidManifest.xml
index a10987d..9e7b234 100644
--- a/samples/SupportCarDemos/src/main/AndroidManifest.xml
+++ b/samples/SupportCarDemos/src/main/AndroidManifest.xml
@@ -32,7 +32,7 @@
<activity android:name=".PagedListViewActivity"
android:label="@string/paged_list_view_title"
- android:parentActivityName=".SupportCarDemoActivity">
+ android:parentActivityName=".SupportCarDemoActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
@@ -213,5 +213,18 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".SupportCarDemoActivity"/>
</activity>
+
+ <activity
+ android:name=".DefaultActionBarDemoActivity"
+ android:label="Default Action Bar"
+ android:parentActivityName=".SupportCarDemoActivity"
+ android:theme="@style/Theme.Car">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SAMPLE_CODE" />
+ </intent-filter>
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value=".SupportCarDemoActivity" />
+ </activity>
</application>
</manifest>
\ No newline at end of file
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/DefaultActionBarDemoActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/DefaultActionBarDemoActivity.java
new file mode 100644
index 0000000..77462be
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/DefaultActionBarDemoActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 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.example.androidx.car;
+
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ * A demo activity that demonstrates the styling of the default
+ * {@link androidx.appcompat.app.ActionBar}.
+ */
+public class DefaultActionBarDemoActivity extends AppCompatActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Set an empty view since we're only interested in the action bar styling.
+ setContentView(new FrameLayout(/* context= */ this));
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index beea444..c056267 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -177,7 +177,6 @@
includeProject(":webkit", "webkit")
includeProject(":webkit:integration-tests:testapp", "webkit/integration-tests/testapp")
includeProject(":work:work-runtime", "work/workmanager")
-includeProject(":work:work-runtime-sync", "work/workmanager-sync")
includeProject(":work:work-runtime-ktx", "work/workmanager-ktx")
includeProject(":work:work-firebase", "work/workmanager-firebase")
includeProject(":work:work-testing", "work/workmanager-testing")
diff --git a/slices/benchmark/build.gradle b/slices/benchmark/build.gradle
index ee39ae2..e57e33d 100644
--- a/slices/benchmark/build.gradle
+++ b/slices/benchmark/build.gradle
@@ -38,7 +38,7 @@
supportLibrary {
name = "Slices Benchmarks"
publish = false
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.SLICE_BENCHMARK
mavenGroup = LibraryGroups.SLICE
inceptionYear = "2018"
description = "RecyclerView Benchmarks"
diff --git a/slices/builders/api/1.1.0-alpha01.txt b/slices/builders/api/1.1.0-alpha01.txt
index 820e4ce..291321c 100644
--- a/slices/builders/api/1.1.0-alpha01.txt
+++ b/slices/builders/api/1.1.0-alpha01.txt
@@ -31,6 +31,7 @@
method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
method public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
+ method public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
method public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
method public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
method public androidx.slice.builders.ListBuilder setIsError(boolean);
diff --git a/slices/builders/api/current.txt b/slices/builders/api/current.txt
index 820e4ce..291321c 100644
--- a/slices/builders/api/current.txt
+++ b/slices/builders/api/current.txt
@@ -31,6 +31,7 @@
method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
method public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
+ method public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
method public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
method public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
method public androidx.slice.builders.ListBuilder setIsError(boolean);
diff --git a/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
index 083f0c7..1145d2a 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
@@ -457,6 +457,15 @@
}
/**
+ * Add a selection row to the list builder.
+ */
+ @NonNull
+ public ListBuilder addSelection(@NonNull SelectionBuilder selectionBuilder) {
+ mImpl.addSelection(selectionBuilder);
+ return this;
+ }
+
+ /**
* Builder to construct a range row which can be added to a {@link ListBuilder}.
* <p>
* A range row supports displaying a horizontal progress indicator.
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
index 93e1b69..62fcc98 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
@@ -29,6 +29,7 @@
import androidx.slice.builders.ListBuilder.InputRangeBuilder;
import androidx.slice.builders.ListBuilder.RangeBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;
+import androidx.slice.builders.SelectionBuilder;
import androidx.slice.builders.SliceAction;
import java.time.Duration;
@@ -75,6 +76,11 @@
void addRange(RangeBuilder builder);
/**
+ * Add a selection row to the list builder.
+ */
+ void addSelection(SelectionBuilder builder);
+
+ /**
* If all content in a slice cannot be shown, the row added here will be displayed where the
* content is cut off. This row should have an affordance to take the user to an activity to
* see all of the content.
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
index 6abdbda..4fad6d9 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
@@ -44,6 +44,7 @@
import androidx.slice.builders.ListBuilder.InputRangeBuilder;
import androidx.slice.builders.ListBuilder.RangeBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;
+import androidx.slice.builders.SelectionBuilder;
import androidx.slice.builders.SliceAction;
import java.time.Duration;
@@ -171,6 +172,19 @@
}
}
+ @Override
+ public void addSelection(SelectionBuilder builder) {
+ if (mTitle == null && builder.getTitle() != null) {
+ mTitle = builder.getTitle();
+ }
+ if (mSubtitle == null && builder.getSubtitle() != null) {
+ mSubtitle = builder.getSubtitle();
+ }
+ if (mSliceAction == null && builder.getPrimaryAction() != null) {
+ mSliceAction = builder.getPrimaryAction();
+ }
+ }
+
/**
*/
@Override
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
index f64b9a1..c7e51a7 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
@@ -66,6 +66,7 @@
import androidx.slice.builders.ListBuilder.InputRangeBuilder;
import androidx.slice.builders.ListBuilder.RangeBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;
+import androidx.slice.builders.SelectionBuilder;
import androidx.slice.builders.SliceAction;
import androidx.slice.core.SliceQuery;
@@ -222,6 +223,12 @@
getBuilder().addSubSlice(impl.build(), SUBTYPE_RANGE);
}
+ @Override
+ public void addSelection(SelectionBuilder builder) {
+ SelectionBuilderImpl impl = new SelectionBuilderV1Impl(createChildBuilder(), builder);
+ getBuilder().addSubSlice(impl.build());
+ }
+
/**
*/
@Override
diff --git a/slices/view/src/androidTest/java/androidx/slice/SliceBuilderTest.java b/slices/view/src/androidTest/java/androidx/slice/SliceBuilderTest.java
index 9fa3938..3f836a6 100644
--- a/slices/view/src/androidTest/java/androidx/slice/SliceBuilderTest.java
+++ b/slices/view/src/androidTest/java/androidx/slice/SliceBuilderTest.java
@@ -34,6 +34,7 @@
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.builders.GridRowBuilder;
import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.SelectionBuilder;
import androidx.slice.builders.SliceAction;
import androidx.slice.render.SliceRenderActivity;
import androidx.slice.widget.SliceLiveData;
@@ -193,6 +194,37 @@
lb.build();
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testThrowSelectionNoPrimaryAction() {
+ ListBuilder lb = new ListBuilder(mContext, mUri, INFINITY);
+ lb.addSelection(new SelectionBuilder()
+ .setTitle("Title")
+ .setSubtitle("Subtitle")
+ .setInputAction(getIntent("")));
+ lb.build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testThrowSelectionNoInputAction() {
+ ListBuilder lb = new ListBuilder(mContext, mUri, INFINITY);
+ lb.addSelection(new SelectionBuilder()
+ .setTitle("Title")
+ .setSubtitle("Subtitle")
+ .setPrimaryAction(getAction("action")));
+ lb.build();
+ }
+
+ @Test
+ public void testNoThrowSelection() {
+ ListBuilder lb = new ListBuilder(mContext, mUri, INFINITY);
+ lb.addSelection(new SelectionBuilder()
+ .setTitle("Title")
+ .setSubtitle("Subtitle")
+ .setPrimaryAction(getAction("action"))
+ .setInputAction(getIntent("")));
+ lb.build();
+ }
+
@Test
public void testNoThrowNoPrimaryActionWhileLoading() {
ListBuilder lb = new ListBuilder(mContext, mUri, INFINITY);
diff --git a/slices/view/src/main/java/androidx/slice/widget/LargeSliceAdapter.java b/slices/view/src/main/java/androidx/slice/widget/LargeSliceAdapter.java
index 58a31e6..548074d 100644
--- a/slices/view/src/main/java/androidx/slice/widget/LargeSliceAdapter.java
+++ b/slices/view/src/main/java/androidx/slice/widget/LargeSliceAdapter.java
@@ -265,20 +265,17 @@
}
private View inflateForType(int viewType) {
- View v = new RowView(mContext);
switch (viewType) {
case TYPE_GRID:
- v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null);
- break;
+ return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null);
case TYPE_MESSAGE:
- v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null);
- break;
+ return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null);
case TYPE_MESSAGE_LOCAL:
- v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message_local,
+ return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message_local,
null);
- break;
+ default:
+ return new RowView(mContext);
}
- return v;
}
protected static class SliceWrapper {
diff --git a/slidingpanelayout/build.gradle b/slidingpanelayout/build.gradle
index 81730bd..44b08bc 100644
--- a/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/build.gradle
@@ -14,7 +14,7 @@
supportLibrary {
name = "Android Support Library Sliding Pane Layout"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.SLIDINGPANELAYOUT
mavenGroup = LibraryGroups.SLIDINGPANELAYOUT
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/studiow b/studiow
new file mode 100755
index 0000000..5da0362
--- /dev/null
+++ b/studiow
@@ -0,0 +1,96 @@
+#!/bin/bash
+set -e
+
+# This is a wrapper script that runs the specific version of Android Studio that is recommended for developing in this repository.
+# (This serves a similar purpose to gradlew)
+
+
+function getStudioUrl() {
+ osName="$1"
+ buildNumber="5056338"
+ studioUrl="https://dl.google.com/dl/android/studio/ide-zips/3.2.1.0/android-studio-ide-181.${buildNumber}-${osName}.zip"
+ echo "${studioUrl}"
+}
+
+acceptsLicenseAgreement="$1"
+scriptDir="$(cd $(dirname $0) && pwd)"
+tempDir="${scriptDir}/studio"
+function getOsName() {
+ unameOutput="$(uname)"
+ osName=""
+ if [ "${unameOutput}" == "Linux" ]; then
+ osName="linux"
+ else
+ osName="mac"
+ fi
+ echo "${osName}"
+}
+osName="$(getOsName)"
+studioUrl="$(getStudioUrl $osName)"
+studioDestName="$(basename ${studioUrl})"
+studioZipPath="${tempDir}/${studioDestName}"
+studioUnzippedPath="$(echo ${studioZipPath} | sed 's/\.zip$//')"
+
+function downloadFile() {
+ fromUrl="$1"
+ destPath="$2"
+ tempPath="${destPath}.tmp"
+ echo "Downloading ${fromUrl} to ${destPath}"
+ curl "${fromUrl}" > "${tempPath}"
+ mv "${tempPath}" "${destPath}"
+}
+
+function checkLicenseAgreement() {
+ # TODO: Is there a more official way to check that the user accepts the license?
+ if [ "${acceptsLicenseAgreement}" != "-y" ]; then
+ echo "Do you accept the license agreement at ${studioUnzippedPath}/android-studio/LICENSE.txt ?"
+ echo "If you do, then rerun this script with a '-y' argument"
+ exit 1
+ fi
+}
+
+function updateStudio() {
+ # skip if already up-to-date
+ if stat "${studioZipPath}" >/dev/null 2>/dev/null; then
+ # already up-to-date
+ return
+ fi
+
+ mkdir -p "${tempDir}"
+ downloadFile "${studioUrl}" "${studioZipPath}"
+ echo
+
+ echo "Removing previous installations"
+ ls "${tempDir}" | grep -v "^${studioDestName}\$" | sed "s|^|${tempDir}/|" | xargs rm -rf
+
+ echo "Unzipping"
+ unzip "${studioZipPath}" -d "${studioUnzippedPath}"
+}
+
+function runStudioLinux() {
+ studioPath="${studioUnzippedPath}/android-studio/bin/studio.sh"
+ echo "$studioPath &"
+ "${studioPath}" &
+}
+
+function runStudioMac() {
+ studioPath="${studioUnzippedPath}/Android Studio.app"
+ echo "open ${studioPath}"
+ open "${studioPath}"
+}
+
+function runStudio() {
+ if [ "${osName}" == "mac" ]; then
+ runStudioMac
+ else
+ runStudioLinux
+ fi
+}
+
+function main() {
+ updateStudio
+ checkLicenseAgreement
+ runStudio
+}
+
+main
diff --git a/testutils/src/main/java/androidx/testutils/SwipeToLocation.java b/testutils/src/main/java/androidx/testutils/SwipeToLocation.java
new file mode 100644
index 0000000..68a30dd
--- /dev/null
+++ b/testutils/src/main/java/androidx/testutils/SwipeToLocation.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2018 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 androidx.testutils;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.test.espresso.InjectEventSecurityException;
+import androidx.test.espresso.PerformException;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.action.CoordinatesProvider;
+
+import org.hamcrest.Matcher;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Swipes the view on which the action is performed to the given top-left coordinates. It is
+ * required that the view moves along with the swipe, as it would list views (e.g., a RecyclerView).
+ *
+ * Provides two different ways to provide the target coordinates: either the center of the view's
+ * parent ({@link #swipeToCenter()} and {@link #flingToCenter()}), or fixed coordinates ({@link
+ * #swipeTo(float[])} and {@link #flingTo(float[])})
+ */
+public class SwipeToLocation implements ViewAction {
+
+ private static CoordinatesProvider sCenterInParent = new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ View parent = (View) view.getParent();
+
+ int horizontalPadding = parent.getPaddingLeft() + parent.getPaddingRight();
+ int verticalPadding = parent.getPaddingTop() + parent.getPaddingBottom();
+ int widthParent = parent.getWidth() - horizontalPadding;
+ int heightParent = parent.getHeight() - verticalPadding;
+ int widthView = view.getWidth();
+ int heightView = view.getHeight();
+
+ float[] coords = new float[2];
+ coords[X] = (widthParent - widthView) / 2;
+ coords[Y] = (heightParent - heightView) / 2;
+ return coords;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "center in parent";
+ }
+ };
+
+ private static class FixedCoordinates implements CoordinatesProvider {
+ private final float[] mCoordinates;
+
+ FixedCoordinates(float[] coordinates) {
+ mCoordinates = coordinates;
+ }
+
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return mCoordinates;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return String.format("fixed coordinates (%f, %f)", mCoordinates[X], mCoordinates[Y]);
+ }
+ }
+
+ private static final int X = 0;
+ private static final int Y = 1;
+
+ private final CoordinatesProvider mCoordinatesProvider;
+ private final int mDuration;
+ private final int mSteps;
+
+ private SwipeToLocation(CoordinatesProvider coordinatesProvider, int duration, int steps) {
+ mCoordinatesProvider = coordinatesProvider;
+ mDuration = duration;
+ mSteps = steps;
+ }
+
+ /**
+ * Swipe the view to the given target location. Swiping takes 1 second to complete.
+ *
+ * @param targetLocation The top-left target coordinates of the view
+ * @return The ViewAction to use in {@link
+ * androidx.test.espresso.ViewInteraction#perform(ViewAction...)}
+ */
+ public static SwipeToLocation swipeTo(float[] targetLocation) {
+ return new SwipeToLocation(new FixedCoordinates(targetLocation), 1000, 10);
+ }
+
+ /**
+ * Fling the view to the given target location. Flinging takes 0.1 seconds to complete.
+ *
+ * @param targetLocation The top-left target coordinates of the view
+ * @return The ViewAction to use in {@link
+ * androidx.test.espresso.ViewInteraction#perform(ViewAction...)}
+ */
+ public static SwipeToLocation flingTo(float[] targetLocation) {
+ return new SwipeToLocation(new FixedCoordinates(targetLocation), 100, 10);
+ }
+
+ /**
+ * Swipe the view to the center of its parent. Swiping takes 1 second to complete.
+ *
+ * @return The ViewAction to use in {@link
+ * androidx.test.espresso.ViewInteraction#perform(ViewAction...)}
+ */
+ public static SwipeToLocation swipeToCenter() {
+ return new SwipeToLocation(sCenterInParent, 1000, 10);
+ }
+
+ /**
+ * Fling the view to the center of its parent. Flinging takes 0.1 seconds to complete.
+ *
+ * @return The ViewAction to use in {@link
+ * androidx.test.espresso.ViewInteraction#perform(ViewAction...)}
+ */
+ public static SwipeToLocation flingToCenter() {
+ return new SwipeToLocation(sCenterInParent, 100, 10);
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(10);
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format(Locale.US, "Swiping view to location %s", mCoordinatesProvider);
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ float[] swipeStart = getCenterOfView(view);
+ float[] targetCoordinates = mCoordinatesProvider.calculateCoordinates(view);
+ sendOnlineSwipe(uiController, view, targetCoordinates, swipeStart, mDuration, mSteps);
+ }
+
+ /**
+ * Inject motion events to emulate a swipe to the target location. Instead of calculating all
+ * events up front and then injecting them one by one, perform the required number of steps and
+ * determine the distance to cover in the current step based on the current distance of the view
+ * to the target. This makes it robust against movements of the view during the event sequence.
+ * This is for example likely to happen between the down event and the first move event if we're
+ * interrupting a smooth scroll.
+ *
+ * @param uiController The controller to inject the motion events with
+ * @param view The view to swipe on
+ * @param targetViewLocation The view location where we want the view to end
+ * @param swipeStart The pointer location where we start the swipe, must be on the view
+ * @param duration The duration in milliseconds of the swipe gesture
+ * @param steps The number of move motion events that will be sent for the gesture
+ */
+ private void sendOnlineSwipe(UiController uiController, View view, float[] targetViewLocation,
+ float[] swipeStart, int duration, int steps) {
+ final long startTime = SystemClock.uptimeMillis();
+ long eventTime = startTime;
+ float[] pointerLocation = new float[]{swipeStart[X], swipeStart[Y]};
+ float[] viewLocation = new float[2];
+ float[] nextViewLocation = new float[2];
+ List<MotionEvent> events = new ArrayList<>();
+ try {
+ // Down event
+ MotionEvent downEvent = obtainDownEvent(startTime, pointerLocation);
+ events.add(downEvent);
+ injectMotionEvent(uiController, downEvent);
+
+ // Move events
+ for (int i = 1; i <= steps; i++) {
+ eventTime = startTime + duration * i / duration;
+ getCurrentCoords(view, viewLocation);
+ lerp(viewLocation, targetViewLocation, 1f / (steps - i + 1), nextViewLocation);
+ updatePointerLocation(pointerLocation, viewLocation, nextViewLocation);
+
+ MotionEvent moveEvent = obtainMoveEvent(startTime, eventTime, pointerLocation);
+ events.add(moveEvent);
+ injectMotionEvent(uiController, moveEvent);
+ }
+
+ // Up event
+ MotionEvent upEvent = obtainUpEvent(startTime, eventTime, pointerLocation);
+ events.add(upEvent);
+ injectMotionEvent(uiController, upEvent);
+ } catch (Exception e) {
+ throw new PerformException.Builder().withActionDescription("Perform swipe")
+ .withViewDescription("unknown").withCause(e).build();
+ } finally {
+ for (MotionEvent event : events) {
+ event.recycle();
+ }
+ }
+ }
+
+ private static MotionEvent obtainDownEvent(long time, float[] coord) {
+ return MotionEvent.obtain(time, time,
+ MotionEvent.ACTION_DOWN, coord[X], coord[Y], 0);
+ }
+
+ private static MotionEvent obtainMoveEvent(long startTime, long elapsedTime, float[] coord) {
+ return MotionEvent.obtain(startTime, elapsedTime,
+ MotionEvent.ACTION_MOVE, coord[X], coord[Y], 0);
+ }
+
+ private static MotionEvent obtainUpEvent(long startTime, long elapsedTime, float[] coord) {
+ return MotionEvent.obtain(startTime, elapsedTime,
+ MotionEvent.ACTION_UP, coord[X], coord[Y], 0);
+ }
+
+ private static void injectMotionEvent(UiController uiController, MotionEvent event)
+ throws InjectEventSecurityException {
+ while (event.getEventTime() - SystemClock.uptimeMillis() > 10) {
+ // Because the loopMainThreadForAtLeast is overkill for waiting, intentionally only
+ // call it with a smaller amount of milliseconds as best effort
+ uiController.loopMainThreadForAtLeast(10);
+ }
+ uiController.injectMotionEvent(event);
+ }
+
+ private void updatePointerLocation(float[] pointerLocation, float[] viewLocation,
+ float[] nextViewLocation) {
+ pointerLocation[X] += nextViewLocation[X] - viewLocation[X];
+ pointerLocation[Y] += nextViewLocation[Y] - viewLocation[Y];
+ }
+
+ private static float[] getCenterOfView(View view) {
+ Rect r = new Rect();
+ view.getGlobalVisibleRect(r);
+ return new float[]{r.centerX(), r.centerY()};
+ }
+
+ private static void getCurrentCoords(View view, float[] out) {
+ out[X] = view.getLeft();
+ out[Y] = view.getTop();
+ }
+
+ private static void lerp(float[] from, float[] to, float f, float[] out) {
+ out[X] = (int) (from[X] + (to[X] - from[X]) * f);
+ out[Y] = (int) (from[Y] + (to[Y] - from[Y]) * f);
+ }
+}
diff --git a/textclassifier/build.gradle b/textclassifier/build.gradle
index 3ce017b..0a81aa3 100644
--- a/textclassifier/build.gradle
+++ b/textclassifier/build.gradle
@@ -18,6 +18,7 @@
androidTestImplementation(ESPRESSO_CORE, libs.exclude_for_espresso)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
+ androidTestImplementation(GUAVA_LISTENABLE_FUTURE_AVOID_CONFLICT)
}
android {
diff --git a/transition/api/1.1.0-alpha01.txt b/transition/api/1.1.0-alpha01.txt
index e1151ab..6f25f20 100644
--- a/transition/api/1.1.0-alpha01.txt
+++ b/transition/api/1.1.0-alpha01.txt
@@ -98,6 +98,7 @@
ctor public Scene(android.view.ViewGroup, android.view.View);
method public void enter();
method public void exit();
+ method public static androidx.transition.Scene? getCurrentScene(android.view.View);
method public static androidx.transition.Scene getSceneForLayout(android.view.ViewGroup, @LayoutRes int, android.content.Context);
method public android.view.ViewGroup getSceneRoot();
method public void setEnterAction(Runnable?);
@@ -230,7 +231,7 @@
method public void captureEndValues(androidx.transition.TransitionValues);
method public void captureStartValues(androidx.transition.TransitionValues);
method public int getOrdering();
- method public androidx.transition.Transition! getTransitionAt(int);
+ method public androidx.transition.Transition? getTransitionAt(int);
method public int getTransitionCount();
method public androidx.transition.TransitionSet removeListener(androidx.transition.Transition.TransitionListener);
method public androidx.transition.TransitionSet removeTarget(@IdRes int);
diff --git a/transition/api/current.txt b/transition/api/current.txt
index e1151ab..6f25f20 100644
--- a/transition/api/current.txt
+++ b/transition/api/current.txt
@@ -98,6 +98,7 @@
ctor public Scene(android.view.ViewGroup, android.view.View);
method public void enter();
method public void exit();
+ method public static androidx.transition.Scene? getCurrentScene(android.view.View);
method public static androidx.transition.Scene getSceneForLayout(android.view.ViewGroup, @LayoutRes int, android.content.Context);
method public android.view.ViewGroup getSceneRoot();
method public void setEnterAction(Runnable?);
@@ -230,7 +231,7 @@
method public void captureEndValues(androidx.transition.TransitionValues);
method public void captureStartValues(androidx.transition.TransitionValues);
method public int getOrdering();
- method public androidx.transition.Transition! getTransitionAt(int);
+ method public androidx.transition.Transition? getTransitionAt(int);
method public int getTransitionCount();
method public androidx.transition.TransitionSet removeListener(androidx.transition.Transition.TransitionListener);
method public androidx.transition.TransitionSet removeTarget(@IdRes int);
diff --git a/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java b/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java
index bc19a6a..c438bc2 100644
--- a/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java
+++ b/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java
@@ -29,6 +29,7 @@
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.MediumTest;
import androidx.transition.test.R;
@@ -68,6 +69,19 @@
assertThat(endHolder.red, is(below(endHolder.green)));
}
+ @UiThreadTest
+ @Test
+ public void testApplyingBounds() {
+ View view = new View(rule.getActivity());
+
+ ViewUtils.setLeftTopRightBottom(view, 10, 20, 30, 40);
+
+ assertThat(view.getLeft(), is(10));
+ assertThat(view.getTop(), is(20));
+ assertThat(view.getRight(), is(30));
+ assertThat(view.getBottom(), is(40));
+ }
+
@Test
public void testSuppressLayoutWhileAnimating() throws Throwable {
if (Build.VERSION.SDK_INT < 18) {
diff --git a/transition/src/androidTest/java/androidx/transition/SceneTest.java b/transition/src/androidTest/java/androidx/transition/SceneTest.java
index c5a2d18..fa02615 100644
--- a/transition/src/androidTest/java/androidx/transition/SceneTest.java
+++ b/transition/src/androidTest/java/androidx/transition/SceneTest.java
@@ -31,7 +31,7 @@
import org.junit.Test;
@MediumTest
-public class SceneTest extends BaseTest {
+public class SceneTest extends BaseTransitionTest {
@Test
public void testGetSceneRoot() {
@@ -125,4 +125,11 @@
is(sameInstance(scene)));
}
+ @Test
+ public void testGetCurrentScene() throws Throwable {
+ Scene scene = Scene.getSceneForLayout(mRoot, R.layout.support_scene0, rule.getActivity());
+ enterScene(scene);
+ assertThat(Scene.getCurrentScene(mRoot), is(scene));
+ }
+
}
diff --git a/transition/src/main/java/androidx/transition/Scene.java b/transition/src/main/java/androidx/transition/Scene.java
index 72846a3..5657a35 100644
--- a/transition/src/main/java/androidx/transition/Scene.java
+++ b/transition/src/main/java/androidx/transition/Scene.java
@@ -190,21 +190,23 @@
* information is used by Scene to determine whether there is a previous
* scene which should be exited before the new scene is entered.
*
- * @param view The view on which the current scene is being set
+ * @param sceneRoot The view on which the current scene is being set
*/
- static void setCurrentScene(View view, Scene scene) {
- view.setTag(R.id.transition_current_scene, scene);
+ static void setCurrentScene(@NonNull View sceneRoot, @Nullable Scene scene) {
+ sceneRoot.setTag(R.id.transition_current_scene, scene);
}
/**
* Gets the current {@link Scene} set on the given view. A scene is set on a view
* only if that view is the scene root.
*
+ * @param sceneRoot The view on which the current scene will be returned
* @return The current Scene set on this view. A value of null indicates that
* no Scene is currently set.
*/
- static Scene getCurrentScene(View view) {
- return (Scene) view.getTag(R.id.transition_current_scene);
+ @Nullable
+ public static Scene getCurrentScene(@NonNull View sceneRoot) {
+ return (Scene) sceneRoot.getTag(R.id.transition_current_scene);
}
/**
diff --git a/transition/src/main/java/androidx/transition/ViewUtilsBase.java b/transition/src/main/java/androidx/transition/ViewUtilsBase.java
index c3dad8f..a383d23 100644
--- a/transition/src/main/java/androidx/transition/ViewUtilsBase.java
+++ b/transition/src/main/java/androidx/transition/ViewUtilsBase.java
@@ -16,14 +16,24 @@
package androidx.transition;
+import android.annotation.SuppressLint;
import android.graphics.Matrix;
+import android.util.Log;
import android.view.View;
import android.view.ViewParent;
import androidx.annotation.NonNull;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
class ViewUtilsBase {
+ private static final String TAG = "ViewUtilsBase";
+
+ private static Method sSetFrameMethod;
+ private static boolean sSetFrameFetched;
+
private float[] mMatrixValues;
public void setTransitionAlpha(@NonNull View view, float alpha) {
@@ -123,10 +133,30 @@
}
public void setLeftTopRightBottom(View v, int left, int top, int right, int bottom) {
- v.setLeft(left);
- v.setTop(top);
- v.setRight(right);
- v.setBottom(bottom);
+ fetchSetFrame();
+ if (sSetFrameMethod != null) {
+ try {
+ sSetFrameMethod.invoke(v, left, top, right, bottom);
+ } catch (IllegalAccessException e) {
+ // Do nothing
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ }
+
+ @SuppressLint("PrivateApi")
+ private void fetchSetFrame() {
+ if (!sSetFrameFetched) {
+ try {
+ sSetFrameMethod = View.class.getDeclaredMethod("setFrame",
+ int.class, int.class, int.class, int.class);
+ sSetFrameMethod.setAccessible(true);
+ } catch (NoSuchMethodException e) {
+ Log.i(TAG, "Failed to retrieve setFrame method", e);
+ }
+ sSetFrameFetched = true;
+ }
}
}
diff --git a/tv-provider/build.gradle b/tv-provider/build.gradle
index 7bb5478..4eccff9 100644
--- a/tv-provider/build.gradle
+++ b/tv-provider/build.gradle
@@ -24,7 +24,7 @@
supportLibrary {
name = "Android Support TV Provider"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.TVPROVIDER
mavenGroup = LibraryGroups.TVPROVIDER
inceptionYear = "2017"
description = "Android Support Library for TV Provider"
diff --git a/versionedparcelable/build.gradle b/versionedparcelable/build.gradle
index 268b72a..899bc2c 100644
--- a/versionedparcelable/build.gradle
+++ b/versionedparcelable/build.gradle
@@ -24,8 +24,8 @@
}
dependencies {
- implementation project(":annotation")
- implementation project(":collection")
+ implementation("androidx.annotation:annotation:1.0.0")
+ implementation("androidx.collection:collection:1.0.0")
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy)
diff --git a/viewpager/api/1.1.0-alpha01.txt b/viewpager/api/1.1.0-alpha01.txt
new file mode 100644
index 0000000..4343e5d
--- /dev/null
+++ b/viewpager/api/1.1.0-alpha01.txt
@@ -0,0 +1,128 @@
+// Signature format: 2.0
+package androidx.viewpager.widget {
+
+ public abstract class PagerAdapter {
+ ctor public PagerAdapter();
+ method public void destroyItem(android.view.ViewGroup, int, Object);
+ method @Deprecated public void destroyItem(android.view.View, int, Object);
+ method public void finishUpdate(android.view.ViewGroup);
+ method @Deprecated public void finishUpdate(android.view.View);
+ method public abstract int getCount();
+ method public int getItemPosition(Object);
+ method public CharSequence? getPageTitle(int);
+ method public float getPageWidth(int);
+ method public Object instantiateItem(android.view.ViewGroup, int);
+ method @Deprecated public Object instantiateItem(android.view.View, int);
+ method public abstract boolean isViewFromObject(android.view.View, Object);
+ method public void notifyDataSetChanged();
+ method public void registerDataSetObserver(android.database.DataSetObserver);
+ method public void restoreState(android.os.Parcelable?, ClassLoader?);
+ method public android.os.Parcelable? saveState();
+ method public void setPrimaryItem(android.view.ViewGroup, int, Object);
+ method @Deprecated public void setPrimaryItem(android.view.View, int, Object);
+ method public void startUpdate(android.view.ViewGroup);
+ method @Deprecated public void startUpdate(android.view.View);
+ method public void unregisterDataSetObserver(android.database.DataSetObserver);
+ field public static final int POSITION_NONE = -2; // 0xfffffffe
+ field public static final int POSITION_UNCHANGED = -1; // 0xffffffff
+ }
+
+ public class PagerTabStrip extends androidx.viewpager.widget.PagerTitleStrip {
+ ctor public PagerTabStrip(android.content.Context);
+ ctor public PagerTabStrip(android.content.Context, android.util.AttributeSet?);
+ method public boolean getDrawFullUnderline();
+ method @ColorInt public int getTabIndicatorColor();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method public void setDrawFullUnderline(boolean);
+ method public void setTabIndicatorColor(@ColorInt int);
+ method public void setTabIndicatorColorResource(@ColorRes int);
+ }
+
+ @androidx.viewpager.widget.ViewPager.DecorView public class PagerTitleStrip extends android.view.ViewGroup {
+ ctor public PagerTitleStrip(android.content.Context);
+ ctor public PagerTitleStrip(android.content.Context, android.util.AttributeSet?);
+ method public int getTextSpacing();
+ method public void setGravity(int);
+ method public void setNonPrimaryAlpha(@FloatRange(from=0.0, to=1.0) float);
+ method public void setTextColor(@ColorInt int);
+ method public void setTextSize(int, float);
+ method public void setTextSpacing(int);
+ }
+
+ public class ViewPager extends android.view.ViewGroup {
+ ctor public ViewPager(android.content.Context);
+ ctor public ViewPager(android.content.Context, android.util.AttributeSet?);
+ method public void addOnAdapterChangeListener(androidx.viewpager.widget.ViewPager.OnAdapterChangeListener);
+ method public void addOnPageChangeListener(androidx.viewpager.widget.ViewPager.OnPageChangeListener);
+ method public boolean arrowScroll(int);
+ method public boolean beginFakeDrag();
+ method protected boolean canScroll(android.view.View!, boolean, int, int, int);
+ method public void clearOnPageChangeListeners();
+ method public void endFakeDrag();
+ method public boolean executeKeyEvent(android.view.KeyEvent);
+ method public void fakeDragBy(float);
+ method public androidx.viewpager.widget.PagerAdapter? getAdapter();
+ method public int getCurrentItem();
+ method public int getOffscreenPageLimit();
+ method public int getPageMargin();
+ method public boolean isDragInGutterEnabled();
+ method public boolean isFakeDragging();
+ method @CallSuper protected void onPageScrolled(int, float, int);
+ method public void onRestoreInstanceState(android.os.Parcelable!);
+ method public android.os.Parcelable! onSaveInstanceState();
+ method public void removeOnAdapterChangeListener(androidx.viewpager.widget.ViewPager.OnAdapterChangeListener);
+ method public void removeOnPageChangeListener(androidx.viewpager.widget.ViewPager.OnPageChangeListener);
+ method public void setAdapter(androidx.viewpager.widget.PagerAdapter?);
+ method public void setCurrentItem(int);
+ method public void setCurrentItem(int, boolean);
+ method public void setDragInGutterEnabled(boolean);
+ method public void setOffscreenPageLimit(int);
+ method @Deprecated public void setOnPageChangeListener(androidx.viewpager.widget.ViewPager.OnPageChangeListener!);
+ method public void setPageMargin(int);
+ method public void setPageMarginDrawable(android.graphics.drawable.Drawable?);
+ method public void setPageMarginDrawable(@DrawableRes int);
+ method public void setPageTransformer(boolean, androidx.viewpager.widget.ViewPager.PageTransformer?);
+ method public void setPageTransformer(boolean, androidx.viewpager.widget.ViewPager.PageTransformer?, int);
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Inherited public static @interface ViewPager.DecorView {
+ }
+
+ public static class ViewPager.LayoutParams extends android.view.ViewGroup.LayoutParams {
+ ctor public ViewPager.LayoutParams();
+ ctor public ViewPager.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ field public int gravity;
+ field public boolean isDecor;
+ }
+
+ public static interface ViewPager.OnAdapterChangeListener {
+ method public void onAdapterChanged(androidx.viewpager.widget.ViewPager, androidx.viewpager.widget.PagerAdapter?, androidx.viewpager.widget.PagerAdapter?);
+ }
+
+ public static interface ViewPager.OnPageChangeListener {
+ method public void onPageScrollStateChanged(int);
+ method public void onPageScrolled(int, float, @Px int);
+ method public void onPageSelected(int);
+ }
+
+ public static interface ViewPager.PageTransformer {
+ method public void transformPage(android.view.View, float);
+ }
+
+ public static class ViewPager.SavedState extends androidx.customview.view.AbsSavedState {
+ ctor public ViewPager.SavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<androidx.viewpager.widget.ViewPager.SavedState>! CREATOR;
+ }
+
+ public static class ViewPager.SimpleOnPageChangeListener implements androidx.viewpager.widget.ViewPager.OnPageChangeListener {
+ ctor public ViewPager.SimpleOnPageChangeListener();
+ method public void onPageScrollStateChanged(int);
+ method public void onPageScrolled(int, float, int);
+ method public void onPageSelected(int);
+ }
+
+}
+
diff --git a/viewpager/api/current.txt b/viewpager/api/current.txt
index de25917..4343e5d 100644
--- a/viewpager/api/current.txt
+++ b/viewpager/api/current.txt
@@ -65,6 +65,7 @@
method public int getCurrentItem();
method public int getOffscreenPageLimit();
method public int getPageMargin();
+ method public boolean isDragInGutterEnabled();
method public boolean isFakeDragging();
method @CallSuper protected void onPageScrolled(int, float, int);
method public void onRestoreInstanceState(android.os.Parcelable!);
@@ -74,6 +75,7 @@
method public void setAdapter(androidx.viewpager.widget.PagerAdapter?);
method public void setCurrentItem(int);
method public void setCurrentItem(int, boolean);
+ method public void setDragInGutterEnabled(boolean);
method public void setOffscreenPageLimit(int);
method @Deprecated public void setOnPageChangeListener(androidx.viewpager.widget.ViewPager.OnPageChangeListener!);
method public void setPageMargin(int);
diff --git a/viewpager/build.gradle b/viewpager/build.gradle
index 1626d52..aa855af 100644
--- a/viewpager/build.gradle
+++ b/viewpager/build.gradle
@@ -21,7 +21,7 @@
supportLibrary {
name = "Android Support Library View Pager"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.VIEWPAGER
mavenGroup = LibraryGroups.VIEWPAGER
inceptionYear = "2018"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
diff --git a/viewpager/src/main/java/androidx/viewpager/widget/ViewPager.java b/viewpager/src/main/java/androidx/viewpager/widget/ViewPager.java
index 08098ed..c067c75 100644
--- a/viewpager/src/main/java/androidx/viewpager/widget/ViewPager.java
+++ b/viewpager/src/main/java/androidx/viewpager/widget/ViewPager.java
@@ -191,6 +191,9 @@
private int mDefaultGutterSize;
private int mGutterSize;
private int mTouchSlop;
+
+ private boolean mDragInGutterEnabled = true;
+
/**
* Position of the last motion event.
*/
@@ -1994,7 +1997,26 @@
}
}
+ /**
+ * @return Whether dragging in the gutter (left and right edges) of the ViewPager is enabled.
+ */
+ public boolean isDragInGutterEnabled() {
+ return mDragInGutterEnabled;
+ }
+
+ /**
+ * Set whether ViewPager should consume drag events if they are within the gutter
+ * (left and right edges) of the ViewPager. The default value {@code false}.
+ * @param enabled true if ViewPager should allow drag in gutter, false otherwise
+ */
+ public void setDragInGutterEnabled(boolean enabled) {
+ mDragInGutterEnabled = enabled;
+ }
+
private boolean isGutterDrag(float x, float dx) {
+ if (mDragInGutterEnabled) {
+ return false;
+ }
return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
}
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
index 5598c79..789032b 100644
--- a/viewpager2/build.gradle
+++ b/viewpager2/build.gradle
@@ -47,7 +47,7 @@
supportLibrary {
name = "AndroidX Widget ViewPager2"
publish = false
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.VIEWPAGER2
mavenGroup = LibraryGroups.VIEWPAGER2
inceptionYear = "2017"
description = "AndroidX Widget ViewPager2"
diff --git a/wear/build.gradle b/wear/build.gradle
index 278ce0c..c932d291 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -38,7 +38,7 @@
supportLibrary {
name = "Android Wear Support UI"
publish = true
- mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenVersion = LibraryVersions.WEAR
mavenGroup = LibraryGroups.WEAR
inceptionYear = "2016"
description = "Android Wear Support UI"
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index 6bbb8031..a00f27f 100644
--- a/work/integration-tests/testapp/build.gradle
+++ b/work/integration-tests/testapp/build.gradle
@@ -19,6 +19,7 @@
plugins {
id("AndroidXPlugin")
id("com.android.application")
+ id("kotlin-android")
}
project.ext.noDocs = true
@@ -34,7 +35,7 @@
dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
- implementation project(':work:work-runtime')
+ implementation project(':work:work-runtime-ktx')
implementation project(':work:work-firebase')
implementation "android.arch.lifecycle:extensions:1.1.0"
implementation "android.arch.persistence.room:runtime:1.1.1-rc1"
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/CoroutineSleepWorker.kt b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/CoroutineSleepWorker.kt
new file mode 100644
index 0000000..733bb866
--- /dev/null
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/CoroutineSleepWorker.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 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 androidx.work.integration.testapp
+
+import android.content.Context
+import android.util.Log
+import androidx.work.WorkerParameters
+import androidx.work.CoroutineWorker
+import kotlinx.coroutines.delay
+
+class CoroutineSleepWorker(context: Context, params: WorkerParameters)
+ : CoroutineWorker(context, params) {
+
+ companion object {
+ val sleepTimeKey = "sleep_time"
+ val tag = "CoroutineWorker"
+ }
+
+ override suspend fun doWork(): Payload {
+ val sleepTime = inputData.getLong(sleepTimeKey, 0L)
+ Log.e(tag, "sleeping for $sleepTime")
+ delay(sleepTime)
+ Log.e(tag, "finished sleep; stopped=$isStopped")
+ return Payload(Result.SUCCESS)
+ }
+}
\ No newline at end of file
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
index 011c2b0..05f7332 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
@@ -38,8 +38,8 @@
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkContinuation;
+import androidx.work.WorkInfo;
import androidx.work.WorkManager;
-import androidx.work.WorkStatus;
import androidx.work.integration.testapp.imageprocessing.ImageProcessingActivity;
import androidx.work.integration.testapp.sherlockholmes.AnalyzeSherlockHolmesActivity;
@@ -148,6 +148,31 @@
}
});
+ findViewById(R.id.coroutine_sleep).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String delayString = delayInMs.getText().toString();
+ long delay = Long.parseLong(delayString);
+ Log.d(TAG, "Enqueuing job with delay of " + delay + " ms");
+
+ Data inputData = new Data.Builder()
+ .put("sleep_time", delay)
+ .build();
+ WorkManager.getInstance().enqueue(
+ new OneTimeWorkRequest.Builder(CoroutineSleepWorker.class)
+ .setInputData(inputData)
+ .addTag("coroutine_sleep")
+ .build());
+ }
+ });
+
+ findViewById(R.id.coroutine_cancel).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ WorkManager.getInstance().cancelAllWorkByTag("coroutine_sleep");
+ }
+ });
+
findViewById(R.id.enqueue_periodic_work).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -220,17 +245,17 @@
@Override
public void onClick(View view) {
WorkManager workManager = WorkManager.getInstance();
- workManager.getStatusesForUniqueWorkLiveData(REPLACE_COMPLETED_WORK)
- .observe(MainActivity.this, new Observer<List<WorkStatus>>() {
+ workManager.getWorkInfosForUniqueWorkLiveData(REPLACE_COMPLETED_WORK)
+ .observe(MainActivity.this, new Observer<List<WorkInfo>>() {
private int mCount;
@Override
- public void onChanged(@Nullable List<WorkStatus> workStatuses) {
- if (workStatuses == null) {
+ public void onChanged(@Nullable List<WorkInfo> workInfos) {
+ if (workInfos == null) {
return;
}
- if (!workStatuses.isEmpty()) {
- WorkStatus status = workStatuses.get(0);
+ if (!workInfos.isEmpty()) {
+ WorkInfo status = workInfos.get(0);
if (status.getState().isFinished()) {
if (mCount < NUM_WORKERS) {
// Enqueue another worker.
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java
index 4a5543b..08bdb15 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java
@@ -33,8 +33,8 @@
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkInfo;
import androidx.work.WorkManager;
-import androidx.work.WorkStatus;
import androidx.work.WorkerParameters;
import java.util.List;
@@ -65,14 +65,14 @@
}
});
- WorkManager.getInstance().getStatusesByTagLiveData("test")
- .observe(this, new Observer<List<WorkStatus>>() {
+ WorkManager.getInstance().getWorkInfosByTagLiveData("test")
+ .observe(this, new Observer<List<WorkInfo>>() {
@Override
- public void onChanged(@Nullable List<WorkStatus> workStatuses) {
+ public void onChanged(@Nullable List<WorkInfo> workInfos) {
String text = "";
- for (WorkStatus workStatus : workStatuses) {
- text = text + "id: " + workStatus.getId().toString().substring(0, 4)
- + " (" + workStatus.getState() + ")\n";
+ for (WorkInfo workInfo : workInfos) {
+ text = text + "id: " + workInfo.getId().toString().substring(0, 4)
+ + " (" + workInfo.getState() + ")\n";
}
if (text.equals("")) {
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java
index 15448c9..45819b70 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java
@@ -16,8 +16,8 @@
package androidx.work.integration.testapp.sherlockholmes;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
@@ -94,7 +94,7 @@
.then(textReducingWork)
.enqueue();
- workManager.getStatusByIdLiveData(textReducingWork.getId()).observe(
+ workManager.getWorkInfoByIdLiveData(textReducingWork.getId()).observe(
this,
status -> {
boolean loading = (status != null
diff --git a/work/integration-tests/testapp/src/main/res/layout/activity_main.xml b/work/integration-tests/testapp/src/main/res/layout/activity_main.xml
index c487485..432c11e 100644
--- a/work/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/work/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -75,31 +75,46 @@
android:layout_marginTop="12dp"
android:text="Observe Image URI"/>
+ <EditText android:id="@+id/delay_in_ms"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="12dp"
+ android:ems="10"
+ android:hint="Delay in ms"
+ android:inputType="number"
+ android:singleLine="true"
+ android:text="0"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
- <EditText android:id="@+id/delay_in_ms"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="16dp"
- android:layout_marginStart="16dp"
- android:layout_marginTop="12dp"
- android:ems="10"
- android:hint="Delay in ms"
- android:inputType="number"
- android:singleLine="true"
- android:text="0"/>
-
<Button android:id="@+id/schedule_delay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
- android:text="Schedule with Delay"/>
+ android:text="Schedule w/ Delay"/>
+
+ <Button android:id="@+id/coroutine_sleep"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="12dp"
+ android:text="Coroutine Sleep"/>
+
+ <Button android:id="@+id/coroutine_cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="12dp"
+ android:text="Coroutine Cancel"/>
</LinearLayout>
diff --git a/work/workmanager-firebase/src/androidTest/java/androidx/work/impl/background/firebase/FirebaseJobServiceTest.java b/work/workmanager-firebase/src/androidTest/java/androidx/work/impl/background/firebase/FirebaseJobServiceTest.java
index 8f2946b..f590619 100644
--- a/work/workmanager-firebase/src/androidTest/java/androidx/work/impl/background/firebase/FirebaseJobServiceTest.java
+++ b/work/workmanager-firebase/src/androidTest/java/androidx/work/impl/background/firebase/FirebaseJobServiceTest.java
@@ -34,7 +34,7 @@
import androidx.test.runner.AndroidJUnit4;
import androidx.work.Configuration;
import androidx.work.OneTimeWorkRequest;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.WorkRequest;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
@@ -112,10 +112,10 @@
Thread.sleep(5000L);
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
- assertThat(workSpecDao.getState(work.getStringId()), is(State.RUNNING));
+ assertThat(workSpecDao.getState(work.getStringId()), is(WorkInfo.State.RUNNING));
mFirebaseJobService.onStopJob(mockParams);
- assertThat(workSpecDao.getState(work.getStringId()), is(State.ENQUEUED));
+ assertThat(workSpecDao.getState(work.getStringId()), is(WorkInfo.State.ENQUEUED));
}
@Test
@@ -141,7 +141,7 @@
JobParameters mockParams = createMockJobParameters(work.getStringId());
assertThat(mFirebaseJobService.onStartJob(mockParams), is(true));
- WorkManagerImpl.getInstance().cancelWorkByIdInternal(work.getId()).get();
+ WorkManagerImpl.getInstance().cancelWorkById(work.getId()).getResult().get();
assertThat(mFirebaseJobService.onStopJob(mockParams), is(false));
}
diff --git a/work/workmanager-ktx/api/1.0.0-alpha11.txt b/work/workmanager-ktx/api/1.0.0-alpha11.txt
index 0947101..0640e2d 100644
--- a/work/workmanager-ktx/api/1.0.0-alpha11.txt
+++ b/work/workmanager-ktx/api/1.0.0-alpha11.txt
@@ -1,6 +1,15 @@
// Signature format: 2.0
package androidx.work {
+ public abstract class CoroutineWorker extends androidx.work.ListenableWorker {
+ ctor public CoroutineWorker(android.content.Context appContext, androidx.work.WorkerParameters params);
+ method public abstract Object? doWork(kotlin.coroutines.experimental.Continuation<? super androidx.work.ListenableWorker.Payload> p);
+ method public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
+ method public final void onStopped();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> startWork();
+ property public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
+ }
+
public final class DataKt {
ctor public DataKt();
method public static androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
diff --git a/work/workmanager-ktx/api/current.txt b/work/workmanager-ktx/api/current.txt
index 0947101..0640e2d 100644
--- a/work/workmanager-ktx/api/current.txt
+++ b/work/workmanager-ktx/api/current.txt
@@ -1,6 +1,15 @@
// Signature format: 2.0
package androidx.work {
+ public abstract class CoroutineWorker extends androidx.work.ListenableWorker {
+ ctor public CoroutineWorker(android.content.Context appContext, androidx.work.WorkerParameters params);
+ method public abstract Object? doWork(kotlin.coroutines.experimental.Continuation<? super androidx.work.ListenableWorker.Payload> p);
+ method public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
+ method public final void onStopped();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> startWork();
+ property public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
+ }
+
public final class DataKt {
ctor public DataKt();
method public static androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
diff --git a/work/workmanager-ktx/build.gradle b/work/workmanager-ktx/build.gradle
index 56b0a35..355e09b 100644
--- a/work/workmanager-ktx/build.gradle
+++ b/work/workmanager-ktx/build.gradle
@@ -38,6 +38,7 @@
dependencies {
api project(':work:work-runtime')
api(KOTLIN_STDLIB)
+ api(KOTLIN_COROUTINES)
androidTestImplementation project(':work:work-testing')
androidTestImplementation(TEST_RUNNER)
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
new file mode 100644
index 0000000..888f6cc
--- /dev/null
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2018 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 androidx.work
+
+import android.arch.core.executor.ArchTaskExecutor
+import android.content.Context
+import android.util.Log
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import androidx.work.impl.WorkDatabase
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.taskexecutor.TaskExecutor
+import kotlinx.coroutines.asCoroutineDispatcher
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.UUID
+import java.util.concurrent.Executor
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CoroutineWorkerTest {
+
+ private lateinit var context: Context
+ private lateinit var configuration: Configuration
+ private lateinit var database: WorkDatabase
+ private lateinit var workManagerImpl: WorkManagerImpl
+
+ @Before
+ fun setUp() {
+ ArchTaskExecutor.getInstance()
+ .setDelegate(object : android.arch.core.executor.TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun isMainThread(): Boolean {
+ return true
+ }
+ })
+
+ context = InstrumentationRegistry.getTargetContext()
+ configuration = Configuration.Builder()
+ .setExecutor(SynchronousExecutor())
+ .build()
+ workManagerImpl = WorkManagerImpl(context, configuration,
+ InstantWorkTaskExecutor()
+ )
+ WorkManagerImpl.setDelegate(workManagerImpl)
+ database = workManagerImpl.getWorkDatabase()
+ Logger.setMinimumLoggingLevel(Log.DEBUG)
+ }
+
+ @After
+ fun tearDown() {
+ WorkManagerImpl.setDelegate(null)
+ ArchTaskExecutor.getInstance().setDelegate(null)
+ }
+
+ @Test
+ fun testCoroutineWorker_basicUsage() {
+ val workerFactory = WorkerFactory.getDefaultWorkerFactory()
+ val worker = workerFactory.createWorkerWithDefaultFallback(
+ context,
+ SynchronousCoroutineWorker::class.java.name,
+ WorkerParameters(
+ UUID.randomUUID(),
+ Data.EMPTY,
+ emptyList(),
+ WorkerParameters.RuntimeExtras(),
+ 1,
+ configuration.executor,
+ workManagerImpl.workTaskExecutor,
+ workerFactory)) as SynchronousCoroutineWorker
+
+ assertThat(worker.job.isCompleted, `is`(false))
+
+ val future = worker.startWork()
+ val payload = future.get()
+
+ assertThat(future.isDone, `is`(true))
+ assertThat(future.isCancelled, `is`(false))
+ assertThat(payload.result, `is`(ListenableWorker.Result.SUCCESS))
+ assertThat(payload.outputData.getLong("output", 0L), `is`(999L))
+ }
+
+ @Test
+ fun testCoroutineWorker_cancellingFutureCancelsJob() {
+ val workerFactory = WorkerFactory.getDefaultWorkerFactory()
+ val worker = workerFactory.createWorkerWithDefaultFallback(
+ context,
+ SynchronousCoroutineWorker::class.java.name,
+ WorkerParameters(
+ UUID.randomUUID(),
+ Data.EMPTY,
+ emptyList(),
+ WorkerParameters.RuntimeExtras(),
+ 1,
+ configuration.executor,
+ workManagerImpl.workTaskExecutor,
+ workerFactory)) as SynchronousCoroutineWorker
+
+ assertThat(worker.job.isCancelled, `is`(false))
+ worker.future.cancel(true)
+ assertThat(worker.job.isCancelled, `is`(true))
+ }
+
+ class SynchronousExecutor : Executor {
+
+ override fun execute(command: Runnable) {
+ command.run()
+ }
+ }
+
+ class InstantWorkTaskExecutor : TaskExecutor {
+
+ private val mSynchronousExecutor = SynchronousExecutor()
+
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun getMainThreadExecutor(): Executor {
+ return mSynchronousExecutor
+ }
+
+ override fun executeOnBackgroundThread(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun getBackgroundExecutor(): Executor {
+ return mSynchronousExecutor
+ }
+
+ override fun getBackgroundExecutorThread(): Thread {
+ return Thread.currentThread()
+ }
+ }
+
+ class SynchronousCoroutineWorker(context: Context, params: WorkerParameters) :
+ CoroutineWorker(context, params) {
+
+ override suspend fun doWork(): Payload {
+ return Payload(Result.SUCCESS, workDataOf("output" to 999L))
+ }
+
+ override val coroutineContext = SynchronousExecutor().asCoroutineDispatcher()
+ }
+}
\ No newline at end of file
diff --git a/work/workmanager-sync/src/main/AndroidManifest.xml b/work/workmanager-ktx/src/androidTest/res/values/values.xml
similarity index 76%
rename from work/workmanager-sync/src/main/AndroidManifest.xml
rename to work/workmanager-ktx/src/androidTest/res/values/values.xml
index 1eec653..bb6fa33 100644
--- a/work/workmanager-sync/src/main/AndroidManifest.xml
+++ b/work/workmanager-ktx/src/androidTest/res/values/values.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2016 The Android Open Source Project
+ ~ Copyright 2018 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.
@@ -14,7 +15,6 @@
~ limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.work">
-
-</manifest>
+<resources>
+ <bool name="workmanager_test_configuration">true</bool>
+</resources>
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
new file mode 100644
index 0000000..4e7ad61
--- /dev/null
+++ b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018 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 androidx.work
+
+import android.content.Context
+import androidx.work.impl.utils.futures.SettableFuture
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * A {@link ListenableWorker} implementation that provides interop with Kotlin Coroutines. By
+ * default, CoroutineWorker runs on {@link Dispatchers#Default}, which can be modified by overriding
+ * {@link #coroutineContext}. Override the {@link #doWork()} function to do your suspending
+ * work.
+ */
+abstract class CoroutineWorker(
+ appContext: Context,
+ params: WorkerParameters
+) : ListenableWorker(appContext, params) {
+
+ internal val job = Job()
+ internal val future: SettableFuture<Payload> = SettableFuture.create()
+
+ init {
+ future.addListener(
+ Runnable {
+ if (future.isCancelled) {
+ job.cancel()
+ }
+ },
+ taskExecutor.backgroundExecutor)
+ }
+
+ /**
+ * The coroutine context on which {@link #doWork()} will run. By default, this is
+ * {@link Dispatchers#Default}.
+ */
+ open val coroutineContext = Dispatchers.Default
+
+ final override fun startWork(): ListenableFuture<Payload> {
+
+ val coroutineScope = CoroutineScope(coroutineContext + job)
+ coroutineScope.launch {
+ try {
+ val payload = doWork()
+ future.set(payload)
+ } catch (t: Throwable) {
+ future.setException(t)
+ }
+ }
+
+ return future
+ }
+
+ /**
+ * A suspending method to do your work. This function runs on the coroutine context specified
+ * by {@link #coroutineContext}.
+ *
+ * @return The {@link ListenableWorker.Payload} of the result of the background work
+ */
+ abstract suspend fun doWork(): ListenableWorker.Payload
+
+ final override fun onStopped() {
+ super.onStopped()
+ future.cancel(false)
+ }
+}
\ No newline at end of file
diff --git a/work/workmanager-sync/build.gradle b/work/workmanager-sync/build.gradle
deleted file mode 100644
index 8e61f74..0000000
--- a/work/workmanager-sync/build.gradle
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-
-import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
-import androidx.build.SupportLibraryExtension
-
-import static androidx.build.dependencies.DependenciesKt.*
-
-plugins {
- id("SupportAndroidLibraryPlugin")
-}
-
-dependencies {
- api project(':work:work-runtime')
-
- androidTestImplementation(project(":work:work-runtime"))
- androidTestImplementation(TEST_RUNNER)
- androidTestImplementation(ESPRESSO_CORE)
- androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
- androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
-}
-
-supportLibrary {
- name = "Android WorkManager Synchronous APIs"
- publish = true
- mavenVersion = LibraryVersions.WORKMANAGER
- mavenGroup = LibraryGroups.WORKMANAGER
- inceptionYear = "2018"
- description = "Android WorkManager Synchronous APIs"
- url = SupportLibraryExtension.ARCHITECTURE_URL
-}
diff --git a/work/workmanager-sync/src/main/java/androidx/work/WorkManagerSync.java b/work/workmanager-sync/src/main/java/androidx/work/WorkManagerSync.java
deleted file mode 100644
index 96362c3..0000000
--- a/work/workmanager-sync/src/main/java/androidx/work/WorkManagerSync.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright 2018 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 androidx.work;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.WorkerThread;
-
-import androidx.work.impl.WorkManagerImpl;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Blocking methods for {@link androidx.work.WorkManager} operations. These methods are expected
- * to be called from a background thread.
- */
-public final class WorkManagerSync {
-
- private final WorkManagerImpl mWorkManagerImpl;
-
- private static WorkManagerSync sInstance = null;
- private static final Object sLock = new Object();
-
- private WorkManagerSync(@NonNull WorkManagerImpl workManagerImpl) {
- this.mWorkManagerImpl = workManagerImpl;
- }
-
- /**
- * Retrieves the {@code default} singleton instance of {@link WorkManagerSync}.
- *
- * @return The singleton instance of {@link WorkManagerSync}; this may be {@code null} in
- * unusual circumstances where you have disabled automatic initialization and have
- * failed to manually call {@link WorkManager#initialize(Context, Configuration)}.
- * @throws IllegalStateException Thrown when WorkManager is not initialized properly. This is
- * most likely because you disabled the automatic initialization but forgot to manually
- * call {@link WorkManager#initialize(Context, Configuration)}.
- */
- public static @NonNull WorkManagerSync getInstance() {
- synchronized (sLock) {
- if (sInstance == null) {
- WorkManagerImpl workManager = WorkManagerImpl.getInstance();
- if (workManager == null) {
- throw new IllegalStateException(
- "WorkManager is not initialized properly. The most "
- + "likely cause is that you disabled WorkManagerInitializer "
- + "in your manifest but forgot to call WorkManager#initialize "
- + "in your Application#onCreate or a ContentProvider.");
- }
- sInstance = new WorkManagerSync(workManager);
- }
- return sInstance;
- }
- }
-
- /**
- * Enqueues one or more items for background processing.
- *
- * @param workRequests One or more {@link WorkRequest}s to enqueue
- * @throws InterruptedException Thrown when the thread enqueueing the {@link WorkRequest}s is
- * interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to enqueue
- * {@link WorkRequest}s.
- */
- @WorkerThread
- public void enqueue(@NonNull WorkRequest... workRequests)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.enqueueInternal(Arrays.asList(workRequests)).get();
- }
-
- /**
- * Enqueues one or more items for background processing.
- *
- * @param requests One or more {@link WorkRequest}s to enqueue
- * @throws InterruptedException Thrown when the thread enqueueing the {@link WorkRequest}s is
- * interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to enqueue
- * {@link WorkRequest}s.
- */
- @WorkerThread
- public void enqueue(@NonNull List<? extends WorkRequest> requests)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.enqueueInternal(requests).get();
- }
-
- /**
- * This method allows you to enqueue {@code work} requests to a uniquely-named
- * {@link WorkContinuation}, where only one continuation of a particular name can be active at
- * a time. For example, you may only want one sync operation to be active. If there is one
- * pending, you can choose to let it run or replace it with your new work.
- *
- * <p>
- * The {@code uniqueWorkName} uniquely identifies this {@link WorkContinuation}.
- * </p>
- *
- * @param uniqueWorkName A unique name which for this operation
- * @param existingWorkPolicy An {@link ExistingWorkPolicy}; see below for more information
- * @param work {@link OneTimeWorkRequest}s to enqueue. {@code REPLACE} ensures
- * that if there is pending work labelled with {@code uniqueWorkName},
- * it will be cancelled and the new work will run. {@code KEEP} will
- * run the new OneTimeWorkRequests only if there is no pending work
- * labelled with {@code uniqueWorkName}. {@code APPEND} will append
- * the OneTimeWorkRequests as leaf nodes labelled with
- * {@code uniqueWorkName}.
- * @throws InterruptedException Thrown when the thread enqueueing the
- * {@link OneTimeWorkRequest}s is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to enqueue
- * {@link OneTimeWorkRequest}s.
- */
- @WorkerThread
- public void enqueueUniqueWork(
- @NonNull String uniqueWorkName,
- @NonNull ExistingWorkPolicy existingWorkPolicy,
- @NonNull OneTimeWorkRequest...work)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.enqueueUniqueWorkInternal(
- uniqueWorkName, existingWorkPolicy, Arrays.asList(work)).get();
- }
-
- /**
- * This method allows you to enqueue {@code work} requests to a uniquely-named
- * {@link WorkContinuation}, where only one continuation of a particular name can be active at
- * a time. For example, you may only want one sync operation to be active. If there is one
- * pending, you can choose to let it run or replace it with your new work.
- *
- * <p>
- * The {@code uniqueWorkName} uniquely identifies this {@link WorkContinuation}.
- * </p>
- *
- * @param uniqueWorkName A unique name which for this operation
- * @param existingWorkPolicy An {@link ExistingWorkPolicy}; see below for more information
- * @param work {@link OneTimeWorkRequest}s to enqueue. {@code REPLACE} ensures
- * that if there is pending work labelled with {@code uniqueWorkName},
- * it will be cancelled and the new work will run. {@code KEEP} will
- * run the new OneTimeWorkRequests only if there is no pending work
- * labelled with {@code uniqueWorkName}. {@code APPEND} will append
- * the OneTimeWorkRequests as leaf nodes labelled with
- * {@code uniqueWorkName}.
- * @throws InterruptedException Thrown when the thread enqueueing the
- * {@link OneTimeWorkRequest}s is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to enqueue
- * {@link OneTimeWorkRequest}s.
- */
- @WorkerThread
- public void enqueueUniqueWork(
- @NonNull String uniqueWorkName,
- @NonNull ExistingWorkPolicy existingWorkPolicy,
- @NonNull List<OneTimeWorkRequest> work)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.enqueueUniqueWorkInternal(uniqueWorkName, existingWorkPolicy, work).get();
- }
-
- /**
- * This method allows you to enqueue a uniquely-named {@link PeriodicWorkRequest}, where only
- * one PeriodicWorkRequest of a particular name can be active at a time. For example, you may
- * only want one sync operation to be active. If there is one pending, you can choose to let it
- * run or replace it with your new work.
- *
- * <p>
- * The {@code uniqueWorkName} uniquely identifies this PeriodicWorkRequest.
- * </p>
- *
- * @param uniqueWorkName A unique name which for this operation
- * @param existingPeriodicWorkPolicy An {@link ExistingPeriodicWorkPolicy}
- * @param periodicWork A {@link PeriodicWorkRequest} to enqueue. {@code REPLACE}
- * ensures that if there is pending work labelled with
- * {@code uniqueWorkName}, it will be cancelled and the new
- * work will run. {@code KEEP} will run the new
- * PeriodicWorkRequest only if there is no pending work
- * labelled with {@code uniqueWorkName}.
- * @throws InterruptedException Thrown when the thread enqueueing the
- * {@link PeriodicWorkRequest} is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to enqueue
- * {@link PeriodicWorkRequest}.
- */
- @WorkerThread
- public void enqueueUniquePeriodicWork(
- @NonNull String uniqueWorkName,
- @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
- @NonNull PeriodicWorkRequest periodicWork)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.enqueueUniquePeriodicWorkInternal(
- uniqueWorkName, existingPeriodicWorkPolicy, periodicWork).get();
- }
-
- /**
- * Cancels work with the given id if it isn't finished. Note that cancellation is a best-effort
- * policy and work that is already executing may continue to run.
- *
- * @param id The id of the work completed
- * @throws InterruptedException Thrown when the thread cancelling work by id is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to cancel
- * work by id.
- */
- @WorkerThread
- public void cancelWorkById(@NonNull UUID id)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.cancelWorkByIdInternal(id).get();
- }
-
- /**
- * Cancels all unfinished work with the given tag. Note that cancellation is a best-effort
- * policy and work that is already executing may continue to run.
- *
- * @param tag The tag used to identify the work
- * @throws InterruptedException Thrown when the thread cancelling work by tag is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to cancel
- * work by tag.
- */
- @WorkerThread
- public void cancelAllWorkByTag(@NonNull String tag)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.cancelAllWorkByTagInternal(tag).get();
- }
-
- /**
- * Cancels all unfinished work in the work chain with the given name. Note that cancellation is
- * a best-effort policy and work that is already executing may continue to run.
- *
- * @param uniqueWorkName The unique name used to identify the chain of work
- * @throws InterruptedException Thrown when the thread cancelling unique work is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to cancel
- * unique work.
- */
- @WorkerThread
- public void cancelUniqueWork(@NonNull String uniqueWorkName)
- throws InterruptedException, ExecutionException {
- mWorkManagerImpl.cancelUniqueWorkInternal(uniqueWorkName).get();
- }
-
- /**
- * Cancels all unfinished work. <b>Use this method with extreme caution!</b> By invoking it,
- * you will potentially affect other modules or libraries in your codebase. It is strongly
- * recommended that you use one of the other cancellation methods at your disposal.
- * @throws InterruptedException Thrown when the thread cancelling all work is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to cancel
- * all work.
- */
- @WorkerThread
- public void cancelAllWork() throws InterruptedException, ExecutionException {
- mWorkManagerImpl.cancelAllWorkInternal().get();
- }
-
- /**
- * Prunes all eligible finished work from the internal database. Eligible work must be finished
- * ({@link State#SUCCEEDED}, {@link State#FAILED}, or {@link State#CANCELLED}), with zero
- * unfinished dependents.
- * <p>
- * <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in
- * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work.
- * You do not normally need to call this method - WorkManager takes care to auto-prune its work
- * after a sane period of time. This method also ignores the
- * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy.
- *
- * @throws InterruptedException Thrown when the thread pruning all work is interrupted.
- * @throws ExecutionException Thrown when there is an error executing the request to prune all
- * work.
- */
- @WorkerThread
- public void pruneWork() throws InterruptedException, ExecutionException {
- mWorkManagerImpl.pruneWorkInternal().get();
- }
-}
diff --git a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
index c2ff348..5527f7a 100644
--- a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
+++ b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
@@ -29,9 +29,9 @@
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkContinuation;
+import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
-import androidx.work.WorkStatus;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.testing.workers.CountingTestWorker;
import androidx.work.testing.workers.TestWorker;
@@ -65,8 +65,8 @@
WorkRequest request = createWorkRequest();
// TestWorkManagerImpl is a subtype of WorkManagerImpl.
WorkManagerImpl workManagerImpl = WorkManagerImpl.getInstance();
- workManagerImpl.enqueueInternal(Collections.singletonList(request)).get();
- WorkStatus status = workManagerImpl.getStatusById(request.getId()).get();
+ workManagerImpl.enqueue(Collections.singletonList(request)).getResult().get();
+ WorkInfo status = workManagerImpl.getWorkInfoById(request.getId()).get();
assertThat(status.getState().isFinished(), is(true));
}
@@ -79,10 +79,10 @@
WorkManager workManager = WorkManager.getInstance();
WorkContinuation continuation = workManager.beginWith(request)
.then(dependentRequest);
- continuation.enqueue().get();
- WorkStatus requestStatus = workManager.getStatusById(request.getId()).get();
- WorkStatus dependentStatus = workManager
- .getStatusById(dependentRequest.getId()).get();
+ continuation.enqueue().getResult().get();
+ WorkInfo requestStatus = workManager.getWorkInfoById(request.getId()).get();
+ WorkInfo dependentStatus = workManager
+ .getWorkInfoById(dependentRequest.getId()).get();
assertThat(requestStatus.getState().isFinished(), is(true));
assertThat(dependentStatus.getState().isFinished(), is(true));
@@ -95,7 +95,7 @@
OneTimeWorkRequest request = createWorkRequestWithNetworkConstraints();
WorkManager workManager = WorkManager.getInstance();
workManager.enqueue(request);
- WorkStatus requestStatus = workManager.getStatusById(request.getId()).get();
+ WorkInfo requestStatus = workManager.getWorkInfoById(request.getId()).get();
assertThat(requestStatus.getState().isFinished(), is(false));
}
@@ -106,10 +106,10 @@
OneTimeWorkRequest request = createWorkRequestWithNetworkConstraints();
WorkManager workManager = WorkManager.getInstance();
workManager.enqueue(request);
- WorkStatus requestStatus = workManager.getStatusById(request.getId()).get();
+ WorkInfo requestStatus = workManager.getWorkInfoById(request.getId()).get();
assertThat(requestStatus.getState().isFinished(), is(false));
mTestDriver.setAllConstraintsMet(request.getId());
- requestStatus = workManager.getStatusById(request.getId()).get();
+ requestStatus = workManager.getWorkInfoById(request.getId()).get();
assertThat(requestStatus.getState().isFinished(), is(true));
}
@@ -120,7 +120,7 @@
OneTimeWorkRequest request = createWorkRequestWithInitialDelay();
WorkManager workManager = WorkManager.getInstance();
workManager.enqueue(request);
- WorkStatus requestStatus = workManager.getStatusById(request.getId()).get();
+ WorkInfo requestStatus = workManager.getWorkInfoById(request.getId()).get();
assertThat(requestStatus.getState().isFinished(), is(false));
}
@@ -132,7 +132,7 @@
WorkManager workManager = WorkManager.getInstance();
workManager.enqueue(request);
mTestDriver.setInitialDelayMet(request.getId());
- WorkStatus requestStatus = workManager.getStatusById(request.getId()).get();
+ WorkInfo requestStatus = workManager.getWorkInfoById(request.getId()).get();
assertThat(requestStatus.getState().isFinished(), is(true));
}
@@ -155,7 +155,7 @@
for (int i = 0; i < 5; ++i) {
mTestDriver.setPeriodDelayMet(request.getId());
assertThat(CountingTestWorker.COUNT.get(), is(i + 2));
- WorkStatus requestStatus = workManager.getStatusById(request.getId()).get();
+ WorkInfo requestStatus = workManager.getWorkInfoById(request.getId()).get();
assertThat(requestStatus.getState().isFinished(), is(false));
}
}
diff --git a/work/workmanager/api/1.0.0-alpha11.txt b/work/workmanager/api/1.0.0-alpha11.txt
index ced045a..c0e4fac 100644
--- a/work/workmanager/api/1.0.0-alpha11.txt
+++ b/work/workmanager/api/1.0.0-alpha11.txt
@@ -78,7 +78,6 @@
method public long[]? getLongArray(String);
method public String? getString(String);
method public String[]? getStringArray(String);
- method @VisibleForTesting public int size();
field public static final androidx.work.Data! EMPTY;
field public static final int MAX_DATA_BYTES = 10240; // 0x2800
}
@@ -89,17 +88,17 @@
method public androidx.work.Data.Builder putAll(androidx.work.Data);
method public androidx.work.Data.Builder putAll(java.util.Map<java.lang.String,java.lang.Object>);
method public androidx.work.Data.Builder putBoolean(String, boolean);
- method public androidx.work.Data.Builder putBooleanArray(String, boolean[]!);
+ method public androidx.work.Data.Builder putBooleanArray(String, boolean[]);
method public androidx.work.Data.Builder putDouble(String, double);
- method public androidx.work.Data.Builder putDoubleArray(String, double[]!);
+ method public androidx.work.Data.Builder putDoubleArray(String, double[]);
method public androidx.work.Data.Builder putFloat(String, float);
- method public androidx.work.Data.Builder putFloatArray(String!, float[]!);
+ method public androidx.work.Data.Builder putFloatArray(String, float[]);
method public androidx.work.Data.Builder putInt(String, int);
- method public androidx.work.Data.Builder putIntArray(String, int[]!);
+ method public androidx.work.Data.Builder putIntArray(String, int[]);
method public androidx.work.Data.Builder putLong(String, long);
- method public androidx.work.Data.Builder putLongArray(String, long[]!);
- method public androidx.work.Data.Builder putString(String, String!);
- method public androidx.work.Data.Builder putStringArray(String, String[]!);
+ method public androidx.work.Data.Builder putLongArray(String, long[]);
+ method public androidx.work.Data.Builder putString(String, String?);
+ method public androidx.work.Data.Builder putStringArray(String, String[]);
}
public enum ExistingPeriodicWorkPolicy {
@@ -128,9 +127,8 @@
method public final java.util.Set<java.lang.String> getTags();
method @RequiresApi(24) public final java.util.List<java.lang.String>? getTriggeredContentAuthorities();
method @RequiresApi(24) public final java.util.List<android.net.Uri>? getTriggeredContentUris();
- method public final boolean isCancelled();
method public final boolean isStopped();
- method public void onStopped(boolean);
+ method public void onStopped();
method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> startWork();
}
@@ -167,6 +165,25 @@
method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger>);
}
+ public interface Operation {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.work.Operation.State.SUCCESS> getResult();
+ method public android.arch.lifecycle.LiveData<androidx.work.Operation.State> getState();
+ }
+
+ public abstract static class Operation.State {
+ }
+
+ public static final class Operation.State.FAILURE extends androidx.work.Operation.State {
+ ctor public Operation.State.FAILURE(Throwable);
+ method public Throwable getException();
+ }
+
+ public static final class Operation.State.IN_PROGRESS extends androidx.work.Operation.State {
+ }
+
+ public static final class Operation.State.SUCCESS extends androidx.work.Operation.State {
+ }
+
public final class OverwritingInputMerger extends androidx.work.InputMerger {
ctor public OverwritingInputMerger();
method public androidx.work.Data merge(java.util.List<androidx.work.Data>);
@@ -184,54 +201,61 @@
ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration, java.time.Duration);
}
- public enum State {
- method public boolean isFinished();
- enum_constant public static final androidx.work.State BLOCKED;
- enum_constant public static final androidx.work.State CANCELLED;
- enum_constant public static final androidx.work.State ENQUEUED;
- enum_constant public static final androidx.work.State FAILED;
- enum_constant public static final androidx.work.State RUNNING;
- enum_constant public static final androidx.work.State SUCCEEDED;
- }
-
public abstract class WorkContinuation {
ctor public WorkContinuation();
method public static androidx.work.WorkContinuation combine(androidx.work.WorkContinuation...);
method public static androidx.work.WorkContinuation combine(java.util.List<androidx.work.WorkContinuation>);
method public static androidx.work.WorkContinuation combine(androidx.work.OneTimeWorkRequest, androidx.work.WorkContinuation...);
method public static androidx.work.WorkContinuation combine(androidx.work.OneTimeWorkRequest, java.util.List<androidx.work.WorkContinuation>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Void> enqueue();
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkStatus>> getStatuses();
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkStatus>> getStatusesLiveData();
+ method public abstract androidx.work.Operation enqueue();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfos();
+ method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData();
method public final androidx.work.WorkContinuation then(androidx.work.OneTimeWorkRequest);
method public abstract androidx.work.WorkContinuation then(java.util.List<androidx.work.OneTimeWorkRequest>);
}
+ public final class WorkInfo {
+ method public java.util.UUID getId();
+ method public androidx.work.Data getOutputData();
+ method public androidx.work.WorkInfo.State getState();
+ method public java.util.Set<java.lang.String> getTags();
+ }
+
+ public static enum WorkInfo.State {
+ method public boolean isFinished();
+ enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
+ enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
+ enum_constant public static final androidx.work.WorkInfo.State ENQUEUED;
+ enum_constant public static final androidx.work.WorkInfo.State FAILED;
+ enum_constant public static final androidx.work.WorkInfo.State RUNNING;
+ enum_constant public static final androidx.work.WorkInfo.State SUCCEEDED;
+ }
+
public abstract class WorkManager {
method public final androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest...);
method public abstract androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest);
method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest>);
- method public void cancelAllWork();
- method public void cancelAllWorkByTag(String);
- method public void cancelUniqueWork(String);
- method public void cancelWorkById(java.util.UUID);
- method public final void enqueue(androidx.work.WorkRequest);
- method public void enqueue(java.util.List<? extends androidx.work.WorkRequest>);
- method public void enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
- method public void enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest...);
- method public void enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public abstract androidx.work.Operation cancelAllWork();
+ method public abstract androidx.work.Operation cancelAllWorkByTag(String);
+ method public abstract androidx.work.Operation cancelUniqueWork(String);
+ method public abstract androidx.work.Operation cancelWorkById(java.util.UUID);
+ method public final androidx.work.Operation enqueue(androidx.work.WorkRequest);
+ method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest>);
+ method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
+ method public androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest...);
+ method public abstract androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
method public static androidx.work.WorkManager getInstance();
method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long> getLastCancelAllTimeMillis();
method public abstract android.arch.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkStatus> getStatusById(java.util.UUID);
- method public abstract android.arch.lifecycle.LiveData<androidx.work.WorkStatus> getStatusByIdLiveData(java.util.UUID);
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkStatus>> getStatusesByTag(String);
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkStatus>> getStatusesByTagLiveData(String);
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkStatus>> getStatusesForUniqueWork(String);
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkStatus>> getStatusesForUniqueWorkLiveData(String);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo> getWorkInfoById(java.util.UUID);
+ method public abstract android.arch.lifecycle.LiveData<androidx.work.WorkInfo> getWorkInfoByIdLiveData(java.util.UUID);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTag(String);
+ method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWork(String);
+ method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String);
method public static void initialize(android.content.Context, androidx.work.Configuration);
- method public void pruneWork();
+ method public abstract androidx.work.Operation pruneWork();
}
public abstract class WorkRequest {
@@ -251,13 +275,6 @@
method public final B setInputData(androidx.work.Data);
}
- public final class WorkStatus {
- method public java.util.UUID getId();
- method public androidx.work.Data getOutputData();
- method public androidx.work.State getState();
- method public java.util.Set<java.lang.String> getTags();
- }
-
public abstract class Worker extends androidx.work.ListenableWorker {
ctor @Keep public Worker(android.content.Context, androidx.work.WorkerParameters);
method @WorkerThread public abstract androidx.work.ListenableWorker.Result doWork();
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index ced045a..c0e4fac 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -78,7 +78,6 @@
method public long[]? getLongArray(String);
method public String? getString(String);
method public String[]? getStringArray(String);
- method @VisibleForTesting public int size();
field public static final androidx.work.Data! EMPTY;
field public static final int MAX_DATA_BYTES = 10240; // 0x2800
}
@@ -89,17 +88,17 @@
method public androidx.work.Data.Builder putAll(androidx.work.Data);
method public androidx.work.Data.Builder putAll(java.util.Map<java.lang.String,java.lang.Object>);
method public androidx.work.Data.Builder putBoolean(String, boolean);
- method public androidx.work.Data.Builder putBooleanArray(String, boolean[]!);
+ method public androidx.work.Data.Builder putBooleanArray(String, boolean[]);
method public androidx.work.Data.Builder putDouble(String, double);
- method public androidx.work.Data.Builder putDoubleArray(String, double[]!);
+ method public androidx.work.Data.Builder putDoubleArray(String, double[]);
method public androidx.work.Data.Builder putFloat(String, float);
- method public androidx.work.Data.Builder putFloatArray(String!, float[]!);
+ method public androidx.work.Data.Builder putFloatArray(String, float[]);
method public androidx.work.Data.Builder putInt(String, int);
- method public androidx.work.Data.Builder putIntArray(String, int[]!);
+ method public androidx.work.Data.Builder putIntArray(String, int[]);
method public androidx.work.Data.Builder putLong(String, long);
- method public androidx.work.Data.Builder putLongArray(String, long[]!);
- method public androidx.work.Data.Builder putString(String, String!);
- method public androidx.work.Data.Builder putStringArray(String, String[]!);
+ method public androidx.work.Data.Builder putLongArray(String, long[]);
+ method public androidx.work.Data.Builder putString(String, String?);
+ method public androidx.work.Data.Builder putStringArray(String, String[]);
}
public enum ExistingPeriodicWorkPolicy {
@@ -128,9 +127,8 @@
method public final java.util.Set<java.lang.String> getTags();
method @RequiresApi(24) public final java.util.List<java.lang.String>? getTriggeredContentAuthorities();
method @RequiresApi(24) public final java.util.List<android.net.Uri>? getTriggeredContentUris();
- method public final boolean isCancelled();
method public final boolean isStopped();
- method public void onStopped(boolean);
+ method public void onStopped();
method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> startWork();
}
@@ -167,6 +165,25 @@
method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger>);
}
+ public interface Operation {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.work.Operation.State.SUCCESS> getResult();
+ method public android.arch.lifecycle.LiveData<androidx.work.Operation.State> getState();
+ }
+
+ public abstract static class Operation.State {
+ }
+
+ public static final class Operation.State.FAILURE extends androidx.work.Operation.State {
+ ctor public Operation.State.FAILURE(Throwable);
+ method public Throwable getException();
+ }
+
+ public static final class Operation.State.IN_PROGRESS extends androidx.work.Operation.State {
+ }
+
+ public static final class Operation.State.SUCCESS extends androidx.work.Operation.State {
+ }
+
public final class OverwritingInputMerger extends androidx.work.InputMerger {
ctor public OverwritingInputMerger();
method public androidx.work.Data merge(java.util.List<androidx.work.Data>);
@@ -184,54 +201,61 @@
ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration, java.time.Duration);
}
- public enum State {
- method public boolean isFinished();
- enum_constant public static final androidx.work.State BLOCKED;
- enum_constant public static final androidx.work.State CANCELLED;
- enum_constant public static final androidx.work.State ENQUEUED;
- enum_constant public static final androidx.work.State FAILED;
- enum_constant public static final androidx.work.State RUNNING;
- enum_constant public static final androidx.work.State SUCCEEDED;
- }
-
public abstract class WorkContinuation {
ctor public WorkContinuation();
method public static androidx.work.WorkContinuation combine(androidx.work.WorkContinuation...);
method public static androidx.work.WorkContinuation combine(java.util.List<androidx.work.WorkContinuation>);
method public static androidx.work.WorkContinuation combine(androidx.work.OneTimeWorkRequest, androidx.work.WorkContinuation...);
method public static androidx.work.WorkContinuation combine(androidx.work.OneTimeWorkRequest, java.util.List<androidx.work.WorkContinuation>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Void> enqueue();
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkStatus>> getStatuses();
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkStatus>> getStatusesLiveData();
+ method public abstract androidx.work.Operation enqueue();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfos();
+ method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData();
method public final androidx.work.WorkContinuation then(androidx.work.OneTimeWorkRequest);
method public abstract androidx.work.WorkContinuation then(java.util.List<androidx.work.OneTimeWorkRequest>);
}
+ public final class WorkInfo {
+ method public java.util.UUID getId();
+ method public androidx.work.Data getOutputData();
+ method public androidx.work.WorkInfo.State getState();
+ method public java.util.Set<java.lang.String> getTags();
+ }
+
+ public static enum WorkInfo.State {
+ method public boolean isFinished();
+ enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
+ enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
+ enum_constant public static final androidx.work.WorkInfo.State ENQUEUED;
+ enum_constant public static final androidx.work.WorkInfo.State FAILED;
+ enum_constant public static final androidx.work.WorkInfo.State RUNNING;
+ enum_constant public static final androidx.work.WorkInfo.State SUCCEEDED;
+ }
+
public abstract class WorkManager {
method public final androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest...);
method public abstract androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest);
method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest>);
- method public void cancelAllWork();
- method public void cancelAllWorkByTag(String);
- method public void cancelUniqueWork(String);
- method public void cancelWorkById(java.util.UUID);
- method public final void enqueue(androidx.work.WorkRequest);
- method public void enqueue(java.util.List<? extends androidx.work.WorkRequest>);
- method public void enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
- method public void enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest...);
- method public void enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public abstract androidx.work.Operation cancelAllWork();
+ method public abstract androidx.work.Operation cancelAllWorkByTag(String);
+ method public abstract androidx.work.Operation cancelUniqueWork(String);
+ method public abstract androidx.work.Operation cancelWorkById(java.util.UUID);
+ method public final androidx.work.Operation enqueue(androidx.work.WorkRequest);
+ method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest>);
+ method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
+ method public androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest...);
+ method public abstract androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
method public static androidx.work.WorkManager getInstance();
method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long> getLastCancelAllTimeMillis();
method public abstract android.arch.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkStatus> getStatusById(java.util.UUID);
- method public abstract android.arch.lifecycle.LiveData<androidx.work.WorkStatus> getStatusByIdLiveData(java.util.UUID);
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkStatus>> getStatusesByTag(String);
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkStatus>> getStatusesByTagLiveData(String);
- method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkStatus>> getStatusesForUniqueWork(String);
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkStatus>> getStatusesForUniqueWorkLiveData(String);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo> getWorkInfoById(java.util.UUID);
+ method public abstract android.arch.lifecycle.LiveData<androidx.work.WorkInfo> getWorkInfoByIdLiveData(java.util.UUID);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTag(String);
+ method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWork(String);
+ method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String);
method public static void initialize(android.content.Context, androidx.work.Configuration);
- method public void pruneWork();
+ method public abstract androidx.work.Operation pruneWork();
}
public abstract class WorkRequest {
@@ -251,13 +275,6 @@
method public final B setInputData(androidx.work.Data);
}
- public final class WorkStatus {
- method public java.util.UUID getId();
- method public androidx.work.Data getOutputData();
- method public androidx.work.State getState();
- method public java.util.Set<java.lang.String> getTags();
- }
-
public abstract class Worker extends androidx.work.ListenableWorker {
ctor @Keep public Worker(android.content.Context, androidx.work.WorkerParameters);
method @WorkerThread public abstract androidx.work.ListenableWorker.Result doWork();
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java
index c2437bd..c874541 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java
@@ -16,9 +16,9 @@
package androidx.work;
-import static androidx.work.State.BLOCKED;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.BLOCKED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import static androidx.work.impl.Scheduler.MAX_SCHEDULER_LIMIT;
import static org.hamcrest.CoreMatchers.equalTo;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index 43fd7ab..dcc8c5b 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -41,11 +41,10 @@
import androidx.work.Configuration;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
-import androidx.work.State;
import androidx.work.TestLifecycleOwner;
import androidx.work.WorkContinuation;
+import androidx.work.WorkInfo;
import androidx.work.WorkManagerTest;
-import androidx.work.WorkStatus;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
import androidx.work.impl.utils.SynchronousExecutor;
@@ -116,7 +115,9 @@
public void tearDown() throws ExecutionException, InterruptedException {
List<String> ids = mDatabase.workSpecDao().getAllWorkSpecIds();
for (String id : ids) {
- mWorkManagerImpl.cancelWorkByIdInternal(UUID.fromString(id)).get();
+ mWorkManagerImpl.cancelWorkById(UUID.fromString(id))
+ .getResult()
+ .get();
}
WorkManagerImpl.setDelegate(null);
ArchTaskExecutor.getInstance().setDelegate(null);
@@ -157,7 +158,7 @@
WorkContinuationImpl continuation = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
assertThat(continuation.isEnqueued(), is(false));
- continuation.enqueue().get();
+ continuation.enqueue().getResult().get();
verifyEnqueued(continuation);
verifyScheduled(mScheduler, continuation);
}
@@ -169,7 +170,7 @@
WorkContinuationImpl chain = (WorkContinuationImpl) (
continuation.then(createTestWorker())
.then(Arrays.asList(createTestWorker(), createTestWorker())));
- chain.enqueue().get();
+ chain.enqueue().getResult().get();
verifyEnqueued(continuation);
verifyScheduled(mScheduler, continuation);
}
@@ -183,11 +184,11 @@
WorkContinuationImpl chain = (WorkContinuationImpl) (
continuation.then(createTestWorker())
.then(Arrays.asList(createTestWorker(), createTestWorker())));
- chain.enqueue().get();
+ chain.enqueue().getResult().get();
verifyEnqueued(continuation);
verifyScheduled(mScheduler, continuation);
WorkContinuationImpl spy = spy(chain);
- spy.enqueue().get();
+ spy.enqueue().getResult().get();
// Verify no more calls to markEnqueued().
verify(spy, times(0)).markEnqueued();
}
@@ -239,7 +240,7 @@
third, fourth);
WorkContinuationImpl dependent = (WorkContinuationImpl) WorkContinuation.combine(
firstDependent, secondDependent);
- dependent.enqueue().get();
+ dependent.enqueue().getResult().get();
verifyEnqueued(dependent);
verifyScheduled(mScheduler, dependent);
}
@@ -260,7 +261,7 @@
first, third);
WorkContinuationImpl dependent = (WorkContinuationImpl) WorkContinuation.combine(
firstDependent, secondDependent);
- dependent.enqueue().get();
+ dependent.enqueue().getResult().get();
verifyEnqueued(dependent);
verifyScheduled(mScheduler, dependent);
}
@@ -275,10 +276,10 @@
final String stringTag = "mystring";
OneTimeWorkRequest firstWork = new OneTimeWorkRequest.Builder(TestWorker.class)
- .setInitialState(State.SUCCEEDED)
+ .setInitialState(WorkInfo.State.SUCCEEDED)
.build();
OneTimeWorkRequest secondWork = new OneTimeWorkRequest.Builder(TestWorker.class)
- .setInitialState(State.SUCCEEDED)
+ .setInitialState(WorkInfo.State.SUCCEEDED)
.build();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
@@ -299,7 +300,7 @@
WorkContinuationImpl dependentContinuation =
(WorkContinuationImpl) WorkContinuation.combine(
firstContinuation, secondContinuation);
- dependentContinuation.enqueue().get();
+ dependentContinuation.enqueue().getResult().get();
String joinId = null;
for (String id : dependentContinuation.getAllIds()) {
@@ -326,7 +327,7 @@
assertThat(joinId, is(not(nullValue())));
WorkSpec joinWorkSpec = mDatabase.workSpecDao().getWorkSpec(joinId);
assertThat(joinWorkSpec, is(not(nullValue())));
- assertThat(joinWorkSpec.state, is(State.SUCCEEDED));
+ assertThat(joinWorkSpec.state, is(WorkInfo.State.SUCCEEDED));
Data output = joinWorkSpec.output;
int[] intArray = output.getIntArray(intTag);
@@ -495,7 +496,7 @@
@Test
@SmallTest
- public void testGetStatusesSync() throws ExecutionException, InterruptedException {
+ public void testGetWorkInfosSync() throws ExecutionException, InterruptedException {
OneTimeWorkRequest aWork = createTestWorker(); // A
OneTimeWorkRequest bWork = createTestWorker(); // B
OneTimeWorkRequest cWork = createTestWorker(); // C
@@ -505,11 +506,11 @@
WorkContinuation secondChain = mWorkManagerImpl.beginWith(cWork);
WorkContinuation combined = WorkContinuation.combine(dWork, firstChain, secondChain);
- combined.enqueue().get();
- List<WorkStatus> statuses = combined.getStatuses().get();
+ combined.enqueue().getResult().get();
+ List<WorkInfo> statuses = combined.getWorkInfos().get();
assertThat(statuses, is(notNullValue()));
List<UUID> ids = new ArrayList<>(statuses.size());
- for (WorkStatus status : statuses) {
+ for (WorkInfo status : statuses) {
ids.add(status.getId());
}
assertThat(ids, containsInAnyOrder(
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
index 7ac2545..fa72954 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
@@ -40,7 +40,7 @@
import androidx.work.OneTimeWorkRequest;
import androidx.work.TestLifecycleOwner;
import androidx.work.WorkContinuation;
-import androidx.work.WorkStatus;
+import androidx.work.WorkInfo;
import androidx.work.impl.background.greedy.GreedyScheduler;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
@@ -150,18 +150,18 @@
final CountDownLatch latch = new CountDownLatch(NUM_WORKERS);
WorkContinuation continuation = mWorkManagerImplSpy.beginWith(workRequests);
- continuation.getStatusesLiveData()
- .observe(mLifecycleOwner, new Observer<List<WorkStatus>>() {
+ continuation.getWorkInfosLiveData()
+ .observe(mLifecycleOwner, new Observer<List<WorkInfo>>() {
@Override
- public void onChanged(@Nullable List<WorkStatus> workStatuses) {
- if (workStatuses == null || workStatuses.isEmpty()) {
+ public void onChanged(@Nullable List<WorkInfo> workInfos) {
+ if (workInfos == null || workInfos.isEmpty()) {
return;
}
- for (WorkStatus workStatus: workStatuses) {
- if (workStatus.getState().isFinished()) {
- if (!completed.contains(workStatus.getId())) {
- completed.add(workStatus.getId());
+ for (WorkInfo workInfo : workInfos) {
+ if (workInfo.getState().isFinished()) {
+ if (!completed.contains(workInfo.getId())) {
+ completed.add(workInfo.getId());
latch.countDown();
}
}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index 2a6d062..32db5e4 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -21,12 +21,12 @@
import static androidx.work.ExistingWorkPolicy.REPLACE;
import static androidx.work.NetworkType.METERED;
import static androidx.work.NetworkType.NOT_REQUIRED;
-import static androidx.work.State.BLOCKED;
-import static androidx.work.State.CANCELLED;
-import static androidx.work.State.ENQUEUED;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.RUNNING;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.BLOCKED;
+import static androidx.work.WorkInfo.State.CANCELLED;
+import static androidx.work.WorkInfo.State.ENQUEUED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.RUNNING;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
import static org.hamcrest.CoreMatchers.is;
@@ -82,8 +82,8 @@
import androidx.work.PeriodicWorkRequest;
import androidx.work.TestLifecycleOwner;
import androidx.work.WorkContinuation;
+import androidx.work.WorkInfo;
import androidx.work.WorkRequest;
-import androidx.work.WorkStatus;
import androidx.work.impl.background.systemalarm.RescheduleReceiver;
import androidx.work.impl.model.Dependency;
import androidx.work.impl.model.DependencyDao;
@@ -177,7 +177,8 @@
mWorkManagerImpl.beginWith(workArray[0]).then(workArray[1])
.then(workArray[2])
- .enqueue().get();
+ .enqueue().getResult()
+ .get();
for (int i = 0; i < workCount; ++i) {
String id = workArray[i].getStringId();
@@ -193,7 +194,7 @@
@SmallTest
public void testEnqueue_AddsImplicitTags() throws ExecutionException, InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- mWorkManagerImpl.enqueueInternal(Collections.singletonList(work)).get();
+ mWorkManagerImpl.enqueue(Collections.singletonList(work)).getResult().get();
WorkTagDao workTagDao = mDatabase.workTagDao();
List<String> tags = workTagDao.getTagsForWorkSpecId(work.getStringId());
@@ -208,7 +209,7 @@
OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
OneTimeWorkRequest work3 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- mWorkManagerImpl.enqueueInternal(Arrays.asList(work1, work2, work3)).get();
+ mWorkManagerImpl.enqueue(Arrays.asList(work1, work2, work3)).getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getWorkSpec(work1.getStringId()), is(notNullValue()));
@@ -227,6 +228,7 @@
mWorkManagerImpl.beginWith(Arrays.asList(work1, work2, work3))
.enqueue()
+ .getResult()
.get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
@@ -249,6 +251,7 @@
mWorkManagerImpl.beginWith(Arrays.asList(work1a, work1b)).then(work2)
.then(Arrays.asList(work3a, work3b))
.enqueue()
+ .getResult()
.get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
@@ -284,12 +287,12 @@
.build();
WorkContinuation workContinuation = mWorkManagerImpl.beginWith(work1);
- workContinuation.enqueue().get();
+ workContinuation.enqueue().getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getState(work1.getStringId()), is(SUCCEEDED));
OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class).build();
- workContinuation.then(work2).enqueue().get();
+ workContinuation.then(work2).enqueue().getResult().get();
assertThat(workSpecDao.getState(work2.getStringId()), isOneOf(ENQUEUED, RUNNING));
}
@@ -303,12 +306,12 @@
.build();
WorkContinuation workContinuation = mWorkManagerImpl.beginWith(work1);
- workContinuation.enqueue().get();
+ workContinuation.enqueue().getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getState(work1.getStringId()), is(FAILED));
OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- workContinuation.then(work2).enqueue().get();
+ workContinuation.then(work2).enqueue().getResult().get();
assertThat(workSpecDao.getState(work2.getStringId()), is(FAILED));
}
@@ -322,12 +325,12 @@
.build();
WorkContinuation workContinuation = mWorkManagerImpl.beginWith(work1);
- workContinuation.enqueue().get();
+ workContinuation.enqueue().getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getState(work1.getStringId()), is(CANCELLED));
OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- workContinuation.then(work2).enqueue().get();
+ workContinuation.then(work2).enqueue().getResult().get();
assertThat(workSpecDao.getState(work2.getStringId()), is(CANCELLED));
}
@@ -353,7 +356,7 @@
.build())
.build();
OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- mWorkManagerImpl.beginWith(work0).then(work1).enqueue().get();
+ mWorkManagerImpl.beginWith(work0).then(work1).enqueue().getResult().get();
WorkSpec workSpec0 = mDatabase.workSpecDao().getWorkSpec(work0.getStringId());
WorkSpec workSpec1 = mDatabase.workSpecDao().getWorkSpec(work1.getStringId());
@@ -395,7 +398,7 @@
.setInitialDelay(expectedInitialDelay, TimeUnit.MILLISECONDS)
.build();
OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- mWorkManagerImpl.beginWith(work0).then(work1).enqueue().get();
+ mWorkManagerImpl.beginWith(work0).then(work1).enqueue().getResult().get();
WorkSpec workSpec0 = mDatabase.workSpecDao().getWorkSpec(work0.getStringId());
WorkSpec workSpec1 = mDatabase.workSpecDao().getWorkSpec(work1.getStringId());
@@ -413,7 +416,7 @@
.setBackoffCriteria(BackoffPolicy.LINEAR, 50000, TimeUnit.MILLISECONDS)
.build();
OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- mWorkManagerImpl.beginWith(work0).then(work1).enqueue().get();
+ mWorkManagerImpl.beginWith(work0).then(work1).enqueue().getResult().get();
WorkSpec workSpec0 = mDatabase.workSpecDao().getWorkSpec(work0.getStringId());
WorkSpec workSpec1 = mDatabase.workSpecDao().getWorkSpec(work1.getStringId());
@@ -441,7 +444,7 @@
.addTag(firstTag)
.build();
OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- mWorkManagerImpl.beginWith(work0).then(work1).then(work2).enqueue().get();
+ mWorkManagerImpl.beginWith(work0).then(work1).then(work2).enqueue().getResult().get();
WorkTagDao workTagDao = mDatabase.workTagDao();
assertThat(workTagDao.getWorkSpecIdsWithTag(firstTag),
@@ -460,7 +463,9 @@
TimeUnit.MILLISECONDS)
.build();
- mWorkManagerImpl.enqueueInternal(Collections.singletonList(periodicWork)).get();
+ mWorkManagerImpl.enqueue(Collections.singletonList(periodicWork))
+ .getResult()
+ .get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(periodicWork.getStringId());
assertThat(workSpec.isPeriodic(), is(true));
@@ -478,7 +483,7 @@
long beforeEnqueueTime = System.currentTimeMillis();
- mWorkManagerImpl.beginWith(work).enqueue().get();
+ mWorkManagerImpl.beginWith(work).enqueue().getResult().get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.periodStartTime, is(greaterThanOrEqualTo(beforeEnqueueTime)));
}
@@ -497,7 +502,9 @@
long beforeEnqueueTime = System.currentTimeMillis();
- mWorkManagerImpl.enqueueInternal(Collections.singletonList(periodicWork)).get();
+ mWorkManagerImpl.enqueue(Collections.singletonList(periodicWork))
+ .getResult()
+ .get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(periodicWork.getStringId());
assertThat(workSpec.periodStartTime, is(greaterThanOrEqualTo(beforeEnqueueTime)));
@@ -515,7 +522,8 @@
mWorkManagerImpl.beginUniqueWork(uniqueName, REPLACE, work)
.then(next)
- .enqueue().get();
+ .enqueue().getResult()
+ .get();
List<String> workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(work.getStringId(), isIn(workSpecIds));
@@ -533,10 +541,10 @@
15L,
TimeUnit.MINUTES)
.build();
- mWorkManagerImpl.enqueueUniquePeriodicWorkInternal(
+ mWorkManagerImpl.enqueueUniquePeriodicWork(
uniqueName,
ExistingPeriodicWorkPolicy.REPLACE,
- periodicWork).get();
+ periodicWork).getResult().get();
List<String> workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(periodicWork.getStringId(), isIn(workSpecIds));
@@ -563,7 +571,7 @@
mWorkManagerImpl
.beginUniqueWork(uniqueName, REPLACE, replacementWork1)
.then(replacementWork2)
- .enqueue().get();
+ .enqueue().getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(
@@ -598,10 +606,10 @@
30L,
TimeUnit.MINUTES)
.build();
- mWorkManagerImpl.enqueueUniquePeriodicWorkInternal(
+ mWorkManagerImpl.enqueueUniquePeriodicWork(
uniqueName,
ExistingPeriodicWorkPolicy.REPLACE,
- replacementWork).get();
+ replacementWork).getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds, contains(replacementWork.getStringId()));
@@ -633,6 +641,7 @@
.beginUniqueWork(uniqueName, KEEP, replacementWork1)
.then(replacementWork2)
.enqueue()
+ .getResult()
.get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
@@ -666,10 +675,10 @@
30L,
TimeUnit.MINUTES)
.build();
- mWorkManagerImpl.enqueueUniquePeriodicWorkInternal(
+ mWorkManagerImpl.enqueueUniquePeriodicWork(
uniqueName,
ExistingPeriodicWorkPolicy.KEEP,
- replacementWork).get();
+ replacementWork).getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds, contains(originalWork.getStringId()));
@@ -701,7 +710,7 @@
mWorkManagerImpl
.beginUniqueWork(uniqueName, KEEP, replacementWork1)
.then(replacementWork2)
- .enqueue().get();
+ .enqueue().getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds,
@@ -736,10 +745,10 @@
30L,
TimeUnit.MINUTES)
.build();
- mWorkManagerImpl.enqueueUniquePeriodicWorkInternal(
+ mWorkManagerImpl.enqueueUniquePeriodicWork(
uniqueName,
ExistingPeriodicWorkPolicy.KEEP,
- replacementWork).get();
+ replacementWork).getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds, contains(replacementWork.getStringId()));
@@ -768,7 +777,7 @@
mWorkManagerImpl
.beginUniqueWork(uniqueName, APPEND, appendWork1)
.then(appendWork2)
- .enqueue().get();
+ .enqueue().getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds,
@@ -806,10 +815,10 @@
OneTimeWorkRequest appendWork1 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
OneTimeWorkRequest appendWork2 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- mWorkManagerImpl.enqueueUniqueWorkInternal(
+ mWorkManagerImpl.enqueueUniqueWork(
uniqueName,
APPEND,
- Arrays.asList(appendWork1, appendWork2)).get();
+ Arrays.asList(appendWork1, appendWork2)).getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds,
containsInAnyOrder(
@@ -860,7 +869,7 @@
mWorkManagerImpl
.beginUniqueWork(uniqueName, APPEND, appendWork1)
.then(appendWork2)
- .enqueue().get();
+ .enqueue().getResult().get();
workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds,
@@ -899,7 +908,7 @@
mWorkManagerImpl
.beginUniqueWork(uniqueName, APPEND, appendWork1)
.then(appendWork2)
- .enqueue().get();
+ .enqueue().getResult().get();
List<String> workSpecIds = mDatabase.workNameDao().getWorkSpecIdsWithName(uniqueName);
assertThat(workSpecIds,
@@ -908,58 +917,58 @@
@Test
@SmallTest
- public void testGetStatusByIdSync() throws ExecutionException, InterruptedException {
+ public void testGetWorkInfoByIdSync() throws ExecutionException, InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(SUCCEEDED)
.build();
insertWorkSpecAndTags(work);
- WorkStatus workStatus = mWorkManagerImpl.getStatusById(work.getId()).get();
- assertThat(workStatus.getId().toString(), is(work.getStringId()));
- assertThat(workStatus.getState(), is(SUCCEEDED));
+ WorkInfo workInfo = mWorkManagerImpl.getWorkInfoById(work.getId()).get();
+ assertThat(workInfo.getId().toString(), is(work.getStringId()));
+ assertThat(workInfo.getState(), is(SUCCEEDED));
}
@Test
@SmallTest
- public void testGetStatusByIdSync_returnsNullIfNotInDatabase()
+ public void testGetWorkInfoByIdSync_returnsNullIfNotInDatabase()
throws ExecutionException, InterruptedException {
- WorkStatus workStatus = mWorkManagerImpl.getStatusById(UUID.randomUUID()).get();
- assertThat(workStatus, is(nullValue()));
+ WorkInfo workInfo = mWorkManagerImpl.getWorkInfoById(UUID.randomUUID()).get();
+ assertThat(workInfo, is(nullValue()));
}
@Test
@SmallTest
@SuppressWarnings("unchecked")
- public void testGetStatusesById() {
+ public void testGetWorkInfoById() {
OneTimeWorkRequest work0 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWorkSpecAndTags(work0);
insertWorkSpecAndTags(work1);
- Observer<List<WorkStatus>> mockObserver = mock(Observer.class);
+ Observer<List<WorkInfo>> mockObserver = mock(Observer.class);
TestLifecycleOwner testLifecycleOwner = new TestLifecycleOwner();
- LiveData<List<WorkStatus>> liveData = mWorkManagerImpl.getStatusesById(
+ LiveData<List<WorkInfo>> liveData = mWorkManagerImpl.getWorkInfosById(
Arrays.asList(work0.getStringId(), work1.getStringId()));
liveData.observe(testLifecycleOwner, mockObserver);
- ArgumentCaptor<List<WorkStatus>> captor = ArgumentCaptor.forClass(List.class);
+ ArgumentCaptor<List<WorkInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(mockObserver).onChanged(captor.capture());
assertThat(captor.getValue(), is(not(nullValue())));
assertThat(captor.getValue().size(), is(2));
- WorkStatus workStatus0 = new WorkStatus(
+ WorkInfo workInfo0 = new WorkInfo(
work0.getId(),
ENQUEUED,
Data.EMPTY,
Collections.singletonList(TestWorker.class.getName()));
- WorkStatus workStatus1 = new WorkStatus(
+ WorkInfo workInfo1 = new WorkInfo(
work1.getId(),
ENQUEUED,
Data.EMPTY,
Collections.singletonList(TestWorker.class.getName()));
- assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
+ assertThat(captor.getValue(), containsInAnyOrder(workInfo0, workInfo1));
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
workSpecDao.setState(RUNNING, work0.getStringId());
@@ -968,12 +977,12 @@
assertThat(captor.getValue(), is(not(nullValue())));
assertThat(captor.getValue().size(), is(2));
- workStatus0 = new WorkStatus(
+ workInfo0 = new WorkInfo(
work0.getId(),
RUNNING,
Data.EMPTY,
Collections.singletonList(TestWorker.class.getName()));
- assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
+ assertThat(captor.getValue(), containsInAnyOrder(workInfo0, workInfo1));
clearInvocations(mockObserver);
workSpecDao.setState(RUNNING, work1.getStringId());
@@ -982,19 +991,19 @@
assertThat(captor.getValue(), is(not(nullValue())));
assertThat(captor.getValue().size(), is(2));
- workStatus1 = new WorkStatus(
+ workInfo1 = new WorkInfo(
work1.getId(),
RUNNING,
Data.EMPTY,
Collections.singletonList(TestWorker.class.getName()));
- assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
+ assertThat(captor.getValue(), containsInAnyOrder(workInfo0, workInfo1));
liveData.removeObservers(testLifecycleOwner);
}
@Test
@SmallTest
- public void testGetStatusesByTagSync() throws ExecutionException, InterruptedException {
+ public void testGetWorkInfosByTagSync() throws ExecutionException, InterruptedException {
final String firstTag = "first_tag";
final String secondTag = "second_tag";
@@ -1015,35 +1024,35 @@
insertWorkSpecAndTags(work1);
insertWorkSpecAndTags(work2);
- WorkStatus workStatus0 = new WorkStatus(
+ WorkInfo workInfo0 = new WorkInfo(
work0.getId(),
RUNNING,
Data.EMPTY,
Arrays.asList(TestWorker.class.getName(), firstTag, secondTag));
- WorkStatus workStatus1 = new WorkStatus(
+ WorkInfo workInfo1 = new WorkInfo(
work1.getId(),
BLOCKED,
Data.EMPTY,
Arrays.asList(TestWorker.class.getName(), firstTag));
- WorkStatus workStatus2 = new WorkStatus(
+ WorkInfo workInfo2 = new WorkInfo(
work2.getId(),
SUCCEEDED,
Data.EMPTY,
Arrays.asList(TestWorker.class.getName(), secondTag));
- List<WorkStatus> workStatuses = mWorkManagerImpl.getStatusesByTag(firstTag).get();
- assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus1));
+ List<WorkInfo> workInfos = mWorkManagerImpl.getWorkInfosByTag(firstTag).get();
+ assertThat(workInfos, containsInAnyOrder(workInfo0, workInfo1));
- workStatuses = mWorkManagerImpl.getStatusesByTag(secondTag).get();
- assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus2));
+ workInfos = mWorkManagerImpl.getWorkInfosByTag(secondTag).get();
+ assertThat(workInfos, containsInAnyOrder(workInfo0, workInfo2));
- workStatuses = mWorkManagerImpl.getStatusesByTag("dummy").get();
- assertThat(workStatuses.size(), is(0));
+ workInfos = mWorkManagerImpl.getWorkInfosByTag("dummy").get();
+ assertThat(workInfos.size(), is(0));
}
@Test
@SmallTest
- public void getStatusByNameSync() throws ExecutionException, InterruptedException {
+ public void getWorkInfosByNameSync() throws ExecutionException, InterruptedException {
final String uniqueName = "myname";
OneTimeWorkRequest work0 = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class)
@@ -1059,33 +1068,33 @@
insertDependency(work1, work0);
insertDependency(work2, work1);
- WorkStatus workStatus0 = new WorkStatus(
+ WorkInfo workInfo0 = new WorkInfo(
work0.getId(),
RUNNING,
Data.EMPTY,
Collections.singletonList(InfiniteTestWorker.class.getName()));
- WorkStatus workStatus1 = new WorkStatus(
+ WorkInfo workInfo1 = new WorkInfo(
work1.getId(),
BLOCKED,
Data.EMPTY,
Collections.singletonList(InfiniteTestWorker.class.getName()));
- WorkStatus workStatus2 = new WorkStatus(
+ WorkInfo workInfo2 = new WorkInfo(
work2.getId(),
BLOCKED,
Data.EMPTY,
Collections.singletonList(InfiniteTestWorker.class.getName()));
- List<WorkStatus> workStatuses = mWorkManagerImpl.getStatusesForUniqueWork(uniqueName).get();
- assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus1, workStatus2));
+ List<WorkInfo> workInfos = mWorkManagerImpl.getWorkInfosForUniqueWork(uniqueName).get();
+ assertThat(workInfos, containsInAnyOrder(workInfo0, workInfo1, workInfo2));
- workStatuses = mWorkManagerImpl.getStatusesForUniqueWork("dummy").get();
- assertThat(workStatuses.size(), is(0));
+ workInfos = mWorkManagerImpl.getWorkInfosForUniqueWork("dummy").get();
+ assertThat(workInfos.size(), is(0));
}
@Test
@SmallTest
@SuppressWarnings("unchecked")
- public void testGetStatusesByName() {
+ public void testGetWorkInfosByName() {
final String uniqueName = "myname";
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
@@ -1102,34 +1111,34 @@
insertDependency(work1, work0);
insertDependency(work2, work1);
- Observer<List<WorkStatus>> mockObserver = mock(Observer.class);
+ Observer<List<WorkInfo>> mockObserver = mock(Observer.class);
TestLifecycleOwner testLifecycleOwner = new TestLifecycleOwner();
- LiveData<List<WorkStatus>> liveData =
- mWorkManagerImpl.getStatusesForUniqueWorkLiveData(uniqueName);
+ LiveData<List<WorkInfo>> liveData =
+ mWorkManagerImpl.getWorkInfosForUniqueWorkLiveData(uniqueName);
liveData.observe(testLifecycleOwner, mockObserver);
- ArgumentCaptor<List<WorkStatus>> captor = ArgumentCaptor.forClass(List.class);
+ ArgumentCaptor<List<WorkInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(mockObserver).onChanged(captor.capture());
assertThat(captor.getValue(), is(not(nullValue())));
assertThat(captor.getValue().size(), is(3));
- WorkStatus workStatus0 = new WorkStatus(
+ WorkInfo workInfo0 = new WorkInfo(
work0.getId(),
RUNNING,
Data.EMPTY,
Collections.singletonList(InfiniteTestWorker.class.getName()));
- WorkStatus workStatus1 = new WorkStatus(
+ WorkInfo workInfo1 = new WorkInfo(
work1.getId(),
BLOCKED,
Data.EMPTY,
Collections.singletonList(InfiniteTestWorker.class.getName()));
- WorkStatus workStatus2 = new WorkStatus(
+ WorkInfo workInfo2 = new WorkInfo(
work2.getId(),
BLOCKED,
Data.EMPTY,
Collections.singletonList(InfiniteTestWorker.class.getName()));
- assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1, workStatus2));
+ assertThat(captor.getValue(), containsInAnyOrder(workInfo0, workInfo1, workInfo2));
workSpecDao.setState(ENQUEUED, work0.getStringId());
@@ -1137,12 +1146,12 @@
assertThat(captor.getValue(), is(not(nullValue())));
assertThat(captor.getValue().size(), is(3));
- workStatus0 = new WorkStatus(
+ workInfo0 = new WorkInfo(
work0.getId(),
ENQUEUED,
Data.EMPTY,
Collections.singletonList(InfiniteTestWorker.class.getName()));
- assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1, workStatus2));
+ assertThat(captor.getValue(), containsInAnyOrder(workInfo0, workInfo1, workInfo2));
liveData.removeObservers(testLifecycleOwner);
}
@@ -1157,7 +1166,7 @@
insertWorkSpecAndTags(work0);
insertWorkSpecAndTags(work1);
- mWorkManagerImpl.cancelWorkByIdInternal(work0.getId()).get();
+ mWorkManagerImpl.cancelWorkById(work0.getId()).getResult().get();
assertThat(workSpecDao.getState(work0.getStringId()), is(CANCELLED));
assertThat(workSpecDao.getState(work1.getStringId()), is(not(CANCELLED)));
}
@@ -1177,7 +1186,7 @@
insertWorkSpecAndTags(work1);
insertDependency(work1, work0);
- mWorkManagerImpl.cancelWorkByIdInternal(work0.getId()).get();
+ mWorkManagerImpl.cancelWorkById(work0.getId()).getResult().get();
assertThat(workSpecDao.getState(work0.getStringId()), is(CANCELLED));
assertThat(workSpecDao.getState(work1.getStringId()), is(CANCELLED));
@@ -1200,7 +1209,7 @@
insertWorkSpecAndTags(work1);
insertDependency(work1, work0);
- mWorkManagerImpl.cancelWorkByIdInternal(work0.getId()).get();
+ mWorkManagerImpl.cancelWorkById(work0.getId()).getResult().get();
assertThat(workSpecDao.getState(work0.getStringId()), is(SUCCEEDED));
assertThat(workSpecDao.getState(work1.getStringId()), is(CANCELLED));
@@ -1231,7 +1240,7 @@
insertWorkSpecAndTags(work2);
insertWorkSpecAndTags(work3);
- mWorkManagerImpl.cancelAllWorkByTagInternal(tagToClear).get();
+ mWorkManagerImpl.cancelAllWorkByTag(tagToClear).getResult().get();
assertThat(workSpecDao.getState(work0.getStringId()), is(CANCELLED));
assertThat(workSpecDao.getState(work1.getStringId()), is(CANCELLED));
@@ -1276,7 +1285,7 @@
insertDependency(work1, work0);
insertDependency(work4, work0);
- mWorkManagerImpl.cancelAllWorkByTagInternal(tag).get();
+ mWorkManagerImpl.cancelAllWorkByTag(tag).getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getState(work0.getStringId()), is(CANCELLED));
@@ -1295,7 +1304,7 @@
OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class).build();
insertNamedWorks(uniqueName, work0, work1);
- mWorkManagerImpl.cancelUniqueWorkInternal(uniqueName).get();
+ mWorkManagerImpl.cancelUniqueWork(uniqueName).getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getState(work0.getStringId()), is(CANCELLED));
@@ -1315,7 +1324,7 @@
OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class).build();
insertNamedWorks(uniqueName, work0, work1);
- mWorkManagerImpl.cancelUniqueWorkInternal(uniqueName).get();
+ mWorkManagerImpl.cancelUniqueWork(uniqueName).getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getState(work0.getStringId()), is(SUCCEEDED));
@@ -1339,7 +1348,7 @@
assertThat(workSpecDao.getState(work1.getStringId()), is(ENQUEUED));
assertThat(workSpecDao.getState(work2.getStringId()), is(SUCCEEDED));
- mWorkManagerImpl.cancelAllWorkInternal().get();
+ mWorkManagerImpl.cancelAllWork().getResult().get();
assertThat(workSpecDao.getState(work0.getStringId()), is(CANCELLED));
assertThat(workSpecDao.getState(work1.getStringId()), is(CANCELLED));
assertThat(workSpecDao.getState(work2.getStringId()), is(SUCCEEDED));
@@ -1409,7 +1418,7 @@
insertDependency(enqueuedWork, finishedWorkWithUnfinishedDependent);
- mWorkManagerImpl.pruneWorkInternal().get();
+ mWorkManagerImpl.pruneWork().getResult().get();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getWorkSpec(enqueuedWork.getStringId()), is(notNullValue()));
@@ -1422,7 +1431,7 @@
@Test
@SmallTest
- public void testSynchronousCancelAndGetStatus()
+ public void testSynchronousCancelAndGetWorkInfo()
throws ExecutionException, InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
@@ -1431,8 +1440,8 @@
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
assertThat(workSpecDao.getState(work.getStringId()), is(ENQUEUED));
- mWorkManagerImpl.cancelWorkByIdInternal(work.getId()).get();
- assertThat(mWorkManagerImpl.getStatusById(work.getId()).get().getState(), is(CANCELLED));
+ mWorkManagerImpl.cancelWorkById(work.getId()).getResult().get();
+ assertThat(mWorkManagerImpl.getWorkInfoById(work.getId()).get().getState(), is(CANCELLED));
}
@Test
@@ -1542,7 +1551,9 @@
new OneTimeWorkRequest.Builder(StopAwareWorker.class)
.build();
- mWorkManagerImpl.enqueueInternal(Collections.singletonList(stopAwareWorkRequest)).get();
+ mWorkManagerImpl.enqueue(Collections.singletonList(stopAwareWorkRequest))
+ .getResult().get();
+
ComponentName componentName = new ComponentName(mContext, RescheduleReceiver.class);
verify(packageManager, times(1))
.setComponentEnabledSetting(eq(componentName),
@@ -1550,7 +1561,9 @@
eq(PackageManager.DONT_KILL_APP));
reset(packageManager);
- mWorkManagerImpl.cancelWorkByIdInternal(stopAwareWorkRequest.getId()).get();
+ mWorkManagerImpl.cancelWorkById(stopAwareWorkRequest.getId())
+ .getResult()
+ .get();
// Sleeping for a little bit, to give the listeners a chance to catch up.
Thread.sleep(SLEEP_DURATION_SMALL_MILLIS);
// There is a small chance that we will call this method twice. Once when the Worker was
@@ -1574,7 +1587,7 @@
.setRequiresBatteryNotLow(true)
.build())
.build();
- mWorkManagerImpl.beginWith(work).enqueue().get();
+ mWorkManagerImpl.beginWith(work).enqueue().getResult().get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(TestWorker.class.getName()));
@@ -1591,7 +1604,7 @@
.setRequiresStorageNotLow(true)
.build())
.build();
- mWorkManagerImpl.beginWith(work).enqueue().get();
+ mWorkManagerImpl.beginWith(work).enqueue().getResult().get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(TestWorker.class.getName()));
@@ -1608,7 +1621,7 @@
.setRequiresBatteryNotLow(true)
.build())
.build();
- mWorkManagerImpl.beginWith(work).enqueue().get();
+ mWorkManagerImpl.beginWith(work).enqueue().getResult().get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(ConstraintTrackingWorker.class.getName()));
@@ -1628,7 +1641,7 @@
.setRequiresStorageNotLow(true)
.build())
.build();
- mWorkManagerImpl.beginWith(work).enqueue().get();
+ mWorkManagerImpl.beginWith(work).enqueue().getResult().get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(ConstraintTrackingWorker.class.getName()));
@@ -1648,7 +1661,7 @@
.setRequiresBatteryNotLow(true)
.build())
.build();
- mWorkManagerImpl.beginWith(work).enqueue().get();
+ mWorkManagerImpl.beginWith(work).enqueue().getResult().get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(TestWorker.class.getName()));
@@ -1665,7 +1678,7 @@
.setRequiresStorageNotLow(true)
.build())
.build();
- mWorkManagerImpl.beginWith(work).enqueue().get();
+ mWorkManagerImpl.beginWith(work).enqueue().getResult().get();
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
assertThat(workSpec.workerClassName, is(TestWorker.class.getName()));
@@ -1693,10 +1706,10 @@
final CountDownLatch latch = new CountDownLatch(3);
TestLifecycleOwner testLifecycleOwner = new TestLifecycleOwner();
- LiveData<WorkStatus> status = mWorkManagerImpl.getStatusByIdLiveData(work.getId());
- status.observe(testLifecycleOwner, new Observer<WorkStatus>() {
+ LiveData<WorkInfo> status = mWorkManagerImpl.getWorkInfoByIdLiveData(work.getId());
+ status.observe(testLifecycleOwner, new Observer<WorkInfo>() {
@Override
- public void onChanged(@Nullable WorkStatus workStatus) {
+ public void onChanged(@Nullable WorkInfo workStatus) {
if (workStatus != null) {
if (workStatus.getState() == RUNNING) {
latch.countDown();
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 9022fab..363710b 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -16,12 +16,12 @@
package androidx.work.impl;
-import static androidx.work.State.BLOCKED;
-import static androidx.work.State.CANCELLED;
-import static androidx.work.State.ENQUEUED;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.RUNNING;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.BLOCKED;
+import static androidx.work.WorkInfo.State.CANCELLED;
+import static androidx.work.WorkInfo.State.ENQUEUED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.RUNNING;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
@@ -879,7 +879,6 @@
Executors.newSingleThreadExecutor().submit(workerWrapper);
workerWrapper.interrupt(false);
assertThat(worker.isStopped(), is(true));
- assertThat(worker.isCancelled(), is(false));
}
@Test
@@ -912,7 +911,6 @@
Executors.newSingleThreadExecutor().submit(workerWrapper);
workerWrapper.interrupt(true);
assertThat(worker.isStopped(), is(true));
- assertThat(worker.isCancelled(), is(true));
}
@Test
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 86ad91b..95f75e6 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -44,7 +44,7 @@
import androidx.work.DatabaseTest;
import androidx.work.Logger;
import androidx.work.OneTimeWorkRequest;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.impl.Processor;
import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkManagerImpl;
@@ -380,7 +380,7 @@
CommandHandler.ACTION_EXECUTION_COMPLETED,
CommandHandler.ACTION_CONSTRAINTS_CHANGED));
- assertThat(workSpec.state, is(State.ENQUEUED));
+ assertThat(workSpec.state, is(WorkInfo.State.ENQUEUED));
}
@Test
@@ -430,7 +430,7 @@
CommandHandler.ACTION_EXECUTION_COMPLETED,
CommandHandler.ACTION_CONSTRAINTS_CHANGED));
- assertThat(workSpec.state, is(State.SUCCEEDED));
+ assertThat(workSpec.state, is(WorkInfo.State.SUCCEEDED));
}
@Test
@@ -443,12 +443,12 @@
OneTimeWorkRequest failed = new OneTimeWorkRequest.Builder(TestWorker.class)
.setPeriodStartTime(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
- .setInitialState(State.FAILED)
+ .setInitialState(WorkInfo.State.FAILED)
.build();
OneTimeWorkRequest succeeded = new OneTimeWorkRequest.Builder(TestWorker.class)
.setPeriodStartTime(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
- .setInitialState(State.SUCCEEDED)
+ .setInitialState(WorkInfo.State.SUCCEEDED)
.build();
OneTimeWorkRequest noConstraints = new OneTimeWorkRequest.Builder(TestWorker.class)
@@ -501,12 +501,12 @@
OneTimeWorkRequest failed = new OneTimeWorkRequest.Builder(TestWorker.class)
.setPeriodStartTime(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
- .setInitialState(State.FAILED)
+ .setInitialState(WorkInfo.State.FAILED)
.build();
OneTimeWorkRequest succeeded = new OneTimeWorkRequest.Builder(TestWorker.class)
.setPeriodStartTime(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
- .setInitialState(State.SUCCEEDED)
+ .setInitialState(WorkInfo.State.SUCCEEDED)
.build();
OneTimeWorkRequest noConstraints = new OneTimeWorkRequest.Builder(TestWorker.class)
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
index 6a6ff9e..05e8448 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
@@ -44,7 +44,7 @@
import androidx.test.runner.AndroidJUnit4;
import androidx.work.Configuration;
import androidx.work.OneTimeWorkRequest;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.WorkManagerTest;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
@@ -184,7 +184,7 @@
@SdkSuppress(minSdkVersion = 23)
public void testSystemJobScheduler_ignoresUnenqueuedWork() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
- .setInitialState(State.CANCELLED)
+ .setInitialState(WorkInfo.State.CANCELLED)
.build();
WorkSpec workSpec = getWorkSpec(work);
// Don't use addToWorkSpecDao and put it in the database mock.
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
index 91b2502..c5f693b 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
@@ -44,7 +44,7 @@
import androidx.test.runner.AndroidJUnit4;
import androidx.work.Configuration;
import androidx.work.OneTimeWorkRequest;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.WorkManagerTest;
import androidx.work.WorkRequest;
import androidx.work.Worker;
@@ -153,12 +153,12 @@
Thread.sleep(5000L);
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
- assertThat(workSpecDao.getState(work.getStringId()), is(State.RUNNING));
+ assertThat(workSpecDao.getState(work.getStringId()), is(WorkInfo.State.RUNNING));
mSystemJobServiceSpy.onStopJob(mockParams);
// TODO(rahulrav): Figure out why this test is flaky.
Thread.sleep(5000L);
- assertThat(workSpecDao.getState(work.getStringId()), is(State.ENQUEUED));
+ assertThat(workSpecDao.getState(work.getStringId()), is(WorkInfo.State.ENQUEUED));
}
@Test
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index 10aa979..04d65e9 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -38,7 +38,7 @@
import androidx.work.DatabaseTest;
import androidx.work.ListenableWorker;
import androidx.work.OneTimeWorkRequest;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.WorkerFactory;
import androidx.work.WorkerParameters;
import androidx.work.impl.Scheduler;
@@ -134,7 +134,7 @@
mWorkTaskExecutor.getBackgroundExecutor().execute(mWorkerWrapper);
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(mWork.getStringId());
- assertThat(workSpec.state, is(State.SUCCEEDED));
+ assertThat(workSpec.state, is(WorkInfo.State.SUCCEEDED));
Data output = workSpec.output;
assertThat(output.getBoolean(TEST_ARGUMENT_NAME, false), is(true));
}
@@ -151,7 +151,7 @@
mWorkTaskExecutor.getBackgroundExecutor().execute(mWorkerWrapper);
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(mWork.getStringId());
- assertThat(workSpec.state, is(State.ENQUEUED));
+ assertThat(workSpec.state, is(WorkInfo.State.ENQUEUED));
}
@Test
@@ -174,7 +174,7 @@
Thread.sleep(TEST_TIMEOUT_IN_MS);
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(mWork.getStringId());
- assertThat(workSpec.state, is(State.ENQUEUED));
+ assertThat(workSpec.state, is(WorkInfo.State.ENQUEUED));
}
@Test
@@ -207,7 +207,7 @@
Thread.sleep(TEST_TIMEOUT_IN_MS);
WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(mWork.getStringId());
- assertThat(workSpec.state, is(State.ENQUEUED));
+ assertThat(workSpec.state, is(WorkInfo.State.ENQUEUED));
}
@Test
@@ -227,11 +227,8 @@
mWorkerWrapper.interrupt(true);
assertThat(mWorker.isStopped(), is(true));
- assertThat(mWorker.isCancelled(), is(true));
assertThat(mWorker.getDelegate(), is(notNullValue()));
assertThat(mWorker.getDelegate().isStopped(), is(true));
- assertThat(mWorker.getDelegate().isCancelled(), is(true));
-
}
private void setupDelegateForExecution(@NonNull String delegateName, Executor executor) {
diff --git a/work/workmanager/src/main/java/androidx/work/Configuration.java b/work/workmanager/src/main/java/androidx/work/Configuration.java
index a31fae9..fb989f0 100644
--- a/work/workmanager/src/main/java/androidx/work/Configuration.java
+++ b/work/workmanager/src/main/java/androidx/work/Configuration.java
@@ -18,6 +18,7 @@
import static androidx.work.impl.Scheduler.MAX_SCHEDULER_LIMIT;
+import android.content.Context;
import android.os.Build;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
@@ -31,7 +32,12 @@
import java.util.concurrent.Executors;
/**
- * Configuration for {@link WorkManager}.
+ * The Configuration object used to initialize {@link WorkManager}. This class contains various
+ * arguments used to setup WorkManager. For example, it is possible to customize the
+ * {@link Executor} used by {@link Worker}s here.
+ * <p>
+ * To set a custom Configuration for WorkManager, see
+ * {@link WorkManager#initialize(Context, Configuration)}.
*/
public final class Configuration {
@@ -176,6 +182,11 @@
/**
* Specifies the range of {@link android.app.job.JobInfo} IDs that can be used by
* {@link WorkManager}. {@link WorkManager} needs a range of at least {@code 1000} IDs.
+ * <p>
+ * JobScheduler uses integers as identifiers for jobs, and WorkManager delegates to
+ * JobScheduler on certain API levels. In order to not clash job codes used in the rest of
+ * your app, you can use this method to tell WorkManager the valid range of job IDs that it
+ * can use.
*
* @param minJobSchedulerId The first valid {@link android.app.job.JobInfo} ID inclusive.
* @param maxJobSchedulerId The last valid {@link android.app.job.JobInfo} ID inclusive.
diff --git a/work/workmanager/src/main/java/androidx/work/Data.java b/work/workmanager/src/main/java/androidx/work/Data.java
index f300833..558d69f 100644
--- a/work/workmanager/src/main/java/androidx/work/Data.java
+++ b/work/workmanager/src/main/java/androidx/work/Data.java
@@ -41,10 +41,16 @@
public final class Data {
+ /**
+ * An empty Data object with no elements.
+ */
public static final Data EMPTY = new Data.Builder().build();
- public static final int MAX_DATA_BYTES = 10 * 1024; // 10KB
- private static final String TAG = "Data";
+ /**
+ * The maximum number of bytes for Data when it is serialized (converted to a byte array).
+ * Please see the class-level Javadoc for more information.
+ */
+ public static final int MAX_DATA_BYTES = 10 * 1024; // 10KB
@SuppressWarnings("WeakerAccess") /* synthetic access */
Map<String, Object> mValues;
@@ -56,7 +62,7 @@
mValues = new HashMap<>(other.mValues);
}
- Data(Map<String, ?> values) {
+ Data(@NonNull Map<String, ?> values) {
mValues = new HashMap<>(values);
}
@@ -282,9 +288,11 @@
}
/**
- * @return The number of arguments
+ * @return The number of elements in this Data object.
+ * @hide
*/
@VisibleForTesting
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public int size() {
return mValues.size();
}
@@ -395,7 +403,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- static Boolean[] convertPrimitiveBooleanArray(boolean[] value) {
+ static @NonNull Boolean[] convertPrimitiveBooleanArray(@NonNull boolean[] value) {
Boolean[] returnValue = new Boolean[value.length];
for (int i = 0; i < value.length; ++i) {
returnValue[i] = value[i];
@@ -404,7 +412,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- static Integer[] convertPrimitiveIntArray(int[] value) {
+ static @NonNull Integer[] convertPrimitiveIntArray(@NonNull int[] value) {
Integer[] returnValue = new Integer[value.length];
for (int i = 0; i < value.length; ++i) {
returnValue[i] = value[i];
@@ -413,7 +421,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- static Long[] convertPrimitiveLongArray(long[] value) {
+ static @NonNull Long[] convertPrimitiveLongArray(@NonNull long[] value) {
Long[] returnValue = new Long[value.length];
for (int i = 0; i < value.length; ++i) {
returnValue[i] = value[i];
@@ -422,7 +430,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- static Float[] convertPrimitiveFloatArray(float[] value) {
+ static @NonNull Float[] convertPrimitiveFloatArray(@NonNull float[] value) {
Float[] returnValue = new Float[value.length];
for (int i = 0; i < value.length; ++i) {
returnValue[i] = value[i];
@@ -431,7 +439,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- static Double[] convertPrimitiveDoubleArray(double[] value) {
+ static @NonNull Double[] convertPrimitiveDoubleArray(@NonNull double[] value) {
Double[] returnValue = new Double[value.length];
for (int i = 0; i < value.length; ++i) {
returnValue[i] = value[i];
@@ -465,7 +473,7 @@
* @param value The value for this argument
* @return The {@link Builder}
*/
- public @NonNull Builder putBooleanArray(@NonNull String key, boolean[] value) {
+ public @NonNull Builder putBooleanArray(@NonNull String key, @NonNull boolean[] value) {
mValues.put(key, convertPrimitiveBooleanArray(value));
return this;
}
@@ -489,7 +497,7 @@
* @param value The value for this argument
* @return The {@link Builder}
*/
- public @NonNull Builder putIntArray(@NonNull String key, int[] value) {
+ public @NonNull Builder putIntArray(@NonNull String key, @NonNull int[] value) {
mValues.put(key, convertPrimitiveIntArray(value));
return this;
}
@@ -513,7 +521,7 @@
* @param value The value for this argument
* @return The {@link Builder}
*/
- public @NonNull Builder putLongArray(@NonNull String key, long[] value) {
+ public @NonNull Builder putLongArray(@NonNull String key, @NonNull long[] value) {
mValues.put(key, convertPrimitiveLongArray(value));
return this;
}
@@ -537,7 +545,7 @@
* @param value The value for this argument
* @return The {@link Builder}
*/
- public @NonNull Builder putFloatArray(String key, float[] value) {
+ public @NonNull Builder putFloatArray(@NonNull String key, @NonNull float[] value) {
mValues.put(key, convertPrimitiveFloatArray(value));
return this;
}
@@ -561,7 +569,7 @@
* @param value The value for this argument
* @return The {@link Builder}
*/
- public @NonNull Builder putDoubleArray(@NonNull String key, double[] value) {
+ public @NonNull Builder putDoubleArray(@NonNull String key, @NonNull double[] value) {
mValues.put(key, convertPrimitiveDoubleArray(value));
return this;
}
@@ -573,7 +581,7 @@
* @param value The value for this argument
* @return The {@link Builder}
*/
- public @NonNull Builder putString(@NonNull String key, String value) {
+ public @NonNull Builder putString(@NonNull String key, @Nullable String value) {
mValues.put(key, value);
return this;
}
@@ -585,7 +593,7 @@
* @param value The value for this argument
* @return The {@link Builder}
*/
- public @NonNull Builder putStringArray(@NonNull String key, String[] value) {
+ public @NonNull Builder putStringArray(@NonNull String key, @NonNull String[] value) {
mValues.put(key, value);
return this;
}
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index 25108466..60d6386 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -26,6 +26,8 @@
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
+import androidx.work.impl.utils.taskexecutor.TaskExecutor;
+
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
@@ -71,7 +73,6 @@
private @NonNull WorkerParameters mWorkerParams;
private volatile boolean mStopped;
- private volatile boolean mCancelled;
private boolean mUsed;
@@ -197,27 +198,12 @@
}
/**
- * Returns {@code true} if this Worker has been told to stop and explicitly informed that it is
- * cancelled and will never execute again. If {@link #isStopped()} returns {@code true} but
- * this method returns {@code false}, that means the system has decided to preempt the task.
- * <p>
- * Note that it is almost never sufficient to check only this method; its value is only
- * meaningful when {@link #isStopped()} returns {@code true}.
- *
- * @return {@code true} if this work operation has been cancelled
- */
- public final boolean isCancelled() {
- return mCancelled;
- }
-
- /**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public final void stop(boolean cancelled) {
+ public final void stop() {
mStopped = true;
- mCancelled = cancelled;
- onStopped(cancelled);
+ onStopped();
}
/**
@@ -227,10 +213,8 @@
* processing in this method should be lightweight - there are no contractual guarantees about
* which thread will invoke this call, so this should not be a long-running or blocking
* operation.
- *
- * @param cancelled If {@code true}, the work has been explicitly cancelled
*/
- public void onStopped(boolean cancelled) {
+ public void onStopped() {
// Do nothing by default.
}
@@ -267,6 +251,14 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public @NonNull TaskExecutor getTaskExecutor() {
+ return mWorkerParams.getTaskExecutor();
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public @NonNull WorkerFactory getWorkerFactory() {
return mWorkerParams.getWorkerFactory();
}
diff --git a/work/workmanager/src/main/java/androidx/work/Operation.java b/work/workmanager/src/main/java/androidx/work/Operation.java
new file mode 100644
index 0000000..5fc9a3a
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/Operation.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2018 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 androidx.work;
+
+import android.arch.lifecycle.LiveData;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Information about an operation being performed by {@link WorkManager}.
+ */
+public interface Operation {
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ State.SUCCESS SUCCESS = new State.SUCCESS();
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ State.IN_PROGRESS IN_PROGRESS = new State.IN_PROGRESS();
+
+ /**
+ * Gets a {@link LiveData} of the Operation {@link State}.
+ *
+ * @return A {@link LiveData} of the Operation {@link State}.
+ */
+ @NonNull
+ LiveData<State> getState();
+
+ /**
+ * Gets a {@link ListenableFuture} which will only resolve with a {@link State.SUCCESS}.
+ * FAILURE {@link Operation}'s will come through as a {@link Throwable} on the
+ * {@link ListenableFuture}.
+ *
+ * <p>
+ * Call {@link ListenableFuture#get()} to block until the {@link Operation} reaches a
+ * terminal state.
+ * </p>
+ *
+ * @return a {@link ListenableFuture} with information about {@link Operation}'s
+ * {@link State.SUCCESS} state.
+ */
+ @NonNull
+ ListenableFuture<State.SUCCESS> getResult();
+
+ /**
+ * The {@link Operation} state.
+ */
+ abstract class State {
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ State() {
+ // Restricting access to the constructor, to give Operation.State a sealed class
+ // like behavior.
+ }
+
+ /**
+ * This represents an {@link Operation} which is successful.
+ */
+ public static final class SUCCESS extends Operation.State {
+ private SUCCESS() {
+ super();
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "SUCCESS";
+ }
+ }
+
+ /**
+ * This represents an {@link Operation} which is in progress.
+ */
+ public static final class IN_PROGRESS extends Operation.State {
+ private IN_PROGRESS() {
+ super();
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "IN_PROGRESS";
+ }
+ }
+
+ /**
+ * This represents an {@link Operation} which has failed.
+ */
+ public static final class FAILURE extends Operation.State {
+
+ private final Throwable mThrowable;
+
+ public FAILURE(@NonNull Throwable exception) {
+ super();
+ mThrowable = exception;
+ }
+
+ /**
+ * @return The {@link Throwable} which caused the {@link Operation} to fail.
+ */
+ @NonNull
+ public Throwable getException() {
+ return mThrowable;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("FAILURE (%s)", mThrowable.getMessage());
+ }
+ }
+ }
+}
diff --git a/work/workmanager/src/main/java/androidx/work/State.java b/work/workmanager/src/main/java/androidx/work/State.java
deleted file mode 100644
index 3541fac..0000000
--- a/work/workmanager/src/main/java/androidx/work/State.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2018 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 androidx.work;
-
-/**
- * The current state of a unit of work.
- */
-public enum State {
-
- /**
- * The state for work that is enqueued (hasn't completed and isn't running)
- */
- ENQUEUED,
-
- /**
- * The state for work that is currently being executed
- */
- RUNNING,
-
- /**
- * The state for work that has completed successfully
- */
- SUCCEEDED,
-
- /**
- * The state for work that has completed in a failure state
- */
- FAILED,
-
- /**
- * The state for work that is currently blocked because its prerequisites haven't finished
- * successfully
- */
- BLOCKED,
-
- /**
- * The state for work that has been cancelled and will not execute
- */
- CANCELLED;
-
- /**
- * Returns {@code true} if this State is considered finished.
- *
- * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and {@link #CANCELLED} states
- */
- public boolean isFinished() {
- return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
- }
-}
diff --git a/work/workmanager/src/main/java/androidx/work/WorkContinuation.java b/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
index d127d0b..052e411 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
@@ -54,29 +54,29 @@
public abstract @NonNull WorkContinuation then(@NonNull List<OneTimeWorkRequest> work);
/**
- * Returns a {@link LiveData} list of {@link WorkStatus} that provides information about work,
+ * Returns a {@link LiveData} list of {@link WorkInfo} that provides information about work,
* their progress, and any resulting output. If state or outputs of any of the jobs in this
* chain changes, any attached {@link android.arch.lifecycle.Observer}s will trigger.
*
- * @return A {@link LiveData} containing a list of {@link WorkStatus}es
+ * @return A {@link LiveData} containing a list of {@link WorkInfo}es
*/
- public abstract @NonNull LiveData<List<WorkStatus>> getStatusesLiveData();
+ public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosLiveData();
/**
- * Returns a {@link ListenableFuture} of a {@link List} of {@link WorkStatus} that provides
+ * Returns a {@link ListenableFuture} of a {@link List} of {@link WorkInfo} that provides
* information about work, their progress, and any resulting output in the
* {@link WorkContinuation}.
*
- * @return A {@link ListenableFuture} of a {@link List} of {@link WorkStatus}es
+ * @return A {@link ListenableFuture} of a {@link List} of {@link WorkInfo}es
*/
- public abstract @NonNull ListenableFuture<List<WorkStatus>> getStatuses();
+ public abstract @NonNull ListenableFuture<List<WorkInfo>> getWorkInfos();
/**
* Enqueues the instance of {@link WorkContinuation} on the background thread.
*
- * @return A {@link ListenableFuture} that completes when the enqueue operation is completed
+ * @return An {@link Operation} that can be used to determine when the enqueue has completed
*/
- public abstract @NonNull ListenableFuture<Void> enqueue();
+ public abstract @NonNull Operation enqueue();
/**
* Combines multiple {@link WorkContinuation}s to allow for complex chaining.
diff --git a/work/workmanager/src/main/java/androidx/work/WorkStatus.java b/work/workmanager/src/main/java/androidx/work/WorkInfo.java
similarity index 69%
rename from work/workmanager/src/main/java/androidx/work/WorkStatus.java
rename to work/workmanager/src/main/java/androidx/work/WorkInfo.java
index 66870e3..43a0570 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkStatus.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkInfo.java
@@ -30,7 +30,7 @@
* {@link State#FAILED}).
*/
-public final class WorkStatus {
+public final class WorkInfo {
private @NonNull UUID mId;
private @NonNull State mState;
@@ -41,7 +41,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public WorkStatus(
+ public WorkInfo(
@NonNull UUID id,
@NonNull State state,
@NonNull Data outputData,
@@ -73,7 +73,7 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- WorkStatus that = (WorkStatus) o;
+ WorkInfo that = (WorkInfo) o;
if (mId != null ? !mId.equals(that.mId) : that.mId != null) return false;
if (mState != that.mState) return false;
@@ -95,11 +95,58 @@
@Override
public String toString() {
- return "WorkStatus{"
+ return "WorkInfo{"
+ "mId='" + mId + '\''
+ ", mState=" + mState
+ ", mOutputData=" + mOutputData
+ ", mTags=" + mTags
+ '}';
}
+
+ /**
+ * The current state of a unit of work.
+ */
+ public enum State {
+
+ /**
+ * The state for work that is enqueued (hasn't completed and isn't running)
+ */
+ ENQUEUED,
+
+ /**
+ * The state for work that is currently being executed
+ */
+ RUNNING,
+
+ /**
+ * The state for work that has completed successfully
+ */
+ SUCCEEDED,
+
+ /**
+ * The state for work that has completed in a failure state
+ */
+ FAILED,
+
+ /**
+ * The state for work that is currently blocked because its prerequisites haven't finished
+ * successfully
+ */
+ BLOCKED,
+
+ /**
+ * The state for work that has been cancelled and will not execute
+ */
+ CANCELLED;
+
+ /**
+ * Returns {@code true} if this State is considered finished.
+ *
+ * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and
+ * {@link #CANCELLED} states
+ */
+ public boolean isFinished() {
+ return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
+ }
+ }
}
diff --git a/work/workmanager/src/main/java/androidx/work/WorkManager.java b/work/workmanager/src/main/java/androidx/work/WorkManager.java
index 6a335a7..a21eae0 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java
@@ -64,7 +64,7 @@
* {@code
* WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
* workManager.enqueue(request);
- * LiveData<WorkStatus> status = workManager.getStatusByIdLiveData(request.getId());
+ * LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(request.getId());
* status.observe(...);}</pre>
*
* You can also use the id for cancellation:
@@ -114,6 +114,12 @@
* (see {@link WorkRequest.Builder#addTag(String)}), and chains of work can be given a
* uniquely-identifiable name (see
* {@link #beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest...)}).
+ *
+ * <p>
+ * <b>Manually initializing WorkManager</b>
+ * <p>
+ * You can manually initialize WorkManager and provide a custom {@link Configuration} for it.
+ * Please see {@link #initialize(Context, Configuration)}.
*/
public abstract class WorkManager {
@@ -144,11 +150,13 @@
* {@link Configuration}. By default, this method should not be called because WorkManager is
* automatically initialized. To initialize WorkManager yourself, please follow these steps:
* <p><ul>
- * <li>Disable {@code androidx.work.impl.WorkManagerInitializer} in your manifest
- * <li>In {@code Application#onCreate} or a {@code ContentProvider}, call this method before
- * calling {@link WorkManager#getInstance()}
+ * <li>Disable {@code androidx.work.impl.WorkManagerInitializer} in your manifest.
+ * <li>Invoke this method in {@code Application#onCreate} or a {@code ContentProvider}. Note
+ * that this method <b>must</b> be invoked in one of these two places or you risk getting a
+ * {@code NullPointerException} in {@link #getInstance()}.
* </ul></p>
- * This method has no effect if WorkManager is already initialized.
+ * <p>
+ * This method throws an exception if it is called multiple times.
*
* @param context A {@link Context} object for configuration purposes. Internally, this class
* will call {@link Context#getApplicationContext()}, so you may safely pass in
@@ -163,33 +171,21 @@
* Enqueues one or more items for background processing.
*
* @param workRequest One or more {@link WorkRequest} to enqueue
+ * @return An {@link Operation} that can be used to determine when the enqueue has completed
*/
- @SuppressWarnings("FutureReturnValueIgnored")
- public final void enqueue(@NonNull WorkRequest workRequest) {
- enqueueInternal(Collections.singletonList(workRequest));
- }
-
- /**
- * Enqueues one or more items for background processing.
- *
- * @param requests One or more {@link WorkRequest} to enqueue
- */
- @SuppressWarnings("FutureReturnValueIgnored")
- public void enqueue(@NonNull List<? extends WorkRequest> requests) {
- enqueueInternal(requests);
- }
-
- /**
- * Enqueues one or more items for background processing.
- *
- * @param requests One or more {@link WorkRequest} to enqueue
- * @return A {@link ListenableFuture} that completes when the enqueue operation is completed
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
- public abstract ListenableFuture<Void> enqueueInternal(
- @NonNull List<? extends WorkRequest> requests);
+ public final Operation enqueue(@NonNull WorkRequest workRequest) {
+ return enqueue(Collections.singletonList(workRequest));
+ }
+
+ /**
+ * Enqueues one or more items for background processing.
+ *
+ * @param requests One or more {@link WorkRequest} to enqueue
+ * @return An {@link Operation} that can be used to determine when the enqueue has completed
+ */
+ @NonNull
+ public abstract Operation enqueue(@NonNull List<? extends WorkRequest> requests);
/**
* Begins a chain with one or more {@link OneTimeWorkRequest}s, which can be enqueued together
@@ -290,13 +286,14 @@
* new OneTimeWorkRequests only if there is no pending work labelled with
* {@code uniqueWorkName}. {@code APPEND} will append the
* OneTimeWorkRequests as leaf nodes labelled with {@code uniqueWorkName}.
+ * @return An {@link Operation} that can be used to determine when the enqueue has completed
*/
- @SuppressWarnings("FutureReturnValueIgnored")
- public void enqueueUniqueWork(
+ @NonNull
+ public Operation enqueueUniqueWork(
@NonNull String uniqueWorkName,
@NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull OneTimeWorkRequest...work) {
- enqueueUniqueWorkInternal(uniqueWorkName, existingWorkPolicy, Arrays.asList(work));
+ return enqueueUniqueWork(uniqueWorkName, existingWorkPolicy, Arrays.asList(work));
}
/**
@@ -317,39 +314,10 @@
* new OneTimeWorkRequests only if there is no pending work labelled with
* {@code uniqueWorkName}. {@code APPEND} will append the
* OneTimeWorkRequests as leaf nodes labelled with {@code uniqueWorkName}.
+ * @return An {@link Operation} that can be used to determine when the enqueue has completed
*/
- @SuppressWarnings("FutureReturnValueIgnored")
- public void enqueueUniqueWork(
- @NonNull String uniqueWorkName,
- @NonNull ExistingWorkPolicy existingWorkPolicy,
- @NonNull List<OneTimeWorkRequest> work) {
- enqueueUniqueWorkInternal(uniqueWorkName, existingWorkPolicy, work);
- }
-
- /**
- * This method allows you to enqueue {@code work} requests to a uniquely-named
- * {@link WorkContinuation}, where only one continuation of a particular name can be active at
- * a time. For example, you may only want one sync operation to be active. If there is one
- * pending, you can choose to let it run or replace it with your new work.
- *
- * <p>
- * The {@code uniqueWorkName} uniquely identifies this {@link WorkContinuation}.
- * </p>
- *
- * @param uniqueWorkName A unique name which for this operation
- * @param existingWorkPolicy An {@link ExistingWorkPolicy}
- * @param work {@link OneTimeWorkRequest}s to enqueue. {@code REPLACE} ensures
- * that if there is pending work labelled with {@code uniqueWorkName}, it
- * will be cancelled and the new work will run. {@code KEEP} will run the
- * new OneTimeWorkRequests only if there is no pending work labelled with
- * {@code uniqueWorkName}. {@code APPEND} will append the
- * OneTimeWorkRequests as leaf nodes labelled with {@code uniqueWorkName}.
- * @return A {@link ListenableFuture} that completes when the enqueue operation is completed
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
- public abstract ListenableFuture<Void> enqueueUniqueWorkInternal(
+ public abstract Operation enqueueUniqueWork(
@NonNull String uniqueWorkName,
@NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull List<OneTimeWorkRequest> work);
@@ -371,38 +339,10 @@
* cancelled and the new work will run. {@code KEEP} will run the new
* PeriodicWorkRequest only if there is no pending work labelled with
* {@code uniqueWorkName}.
+ * @return An {@link Operation} that can be used to determine when the enqueue has completed
*/
- @SuppressWarnings("FutureReturnValueIgnored")
- public void enqueueUniquePeriodicWork(
- @NonNull String uniqueWorkName,
- @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
- @NonNull PeriodicWorkRequest periodicWork) {
- enqueueUniquePeriodicWorkInternal(uniqueWorkName, existingPeriodicWorkPolicy, periodicWork);
- }
-
- /**
- * This method allows you to enqueue a uniquely-named {@link PeriodicWorkRequest}, where only
- * one PeriodicWorkRequest of a particular name can be active at a time. For example, you may
- * only want one sync operation to be active. If there is one pending, you can choose to let it
- * run or replace it with your new work.
- *
- * <p>
- * The {@code uniqueWorkName} uniquely identifies this PeriodicWorkRequest.
- * </p>
- *
- * @param uniqueWorkName A unique name which for this operation
- * @param existingPeriodicWorkPolicy An {@link ExistingPeriodicWorkPolicy}
- * @param periodicWork A {@link PeriodicWorkRequest} to enqueue. {@code REPLACE} ensures that if
- * there is pending work labelled with {@code uniqueWorkName}, it will be
- * cancelled and the new work will run. {@code KEEP} will run the new
- * PeriodicWorkRequest only if there is no pending work labelled with
- * {@code uniqueWorkName}.
- * @return A {@link ListenableFuture} that completes when the enqueue operation is completed
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
- public abstract ListenableFuture<Void> enqueueUniquePeriodicWorkInternal(
+ public abstract Operation enqueueUniquePeriodicWork(
@NonNull String uniqueWorkName,
@NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
@NonNull PeriodicWorkRequest periodicWork);
@@ -412,126 +352,56 @@
* policy and work that is already executing may continue to run.
*
* @param id The id of the work
- */
- @SuppressWarnings("FutureReturnValueIgnored")
- public void cancelWorkById(@NonNull UUID id) {
- cancelWorkByIdInternal(id);
- }
-
- /**
- * Cancels work with the given id if it isn't finished. Note that cancellation is a best-effort
- * policy and work that is already executing may continue to run.
- *
- * @param id The id of the work
- * @return A {@link ListenableFuture} that completes when the cancelWorkById operation is
+ * @return An {@link Operation} that can be used to determine when the cancelWorkById has
* completed
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public abstract @NonNull ListenableFuture<Void> cancelWorkByIdInternal(@NonNull UUID id);
+ public abstract @NonNull Operation cancelWorkById(@NonNull UUID id);
/**
* Cancels all unfinished work with the given tag. Note that cancellation is a best-effort
* policy and work that is already executing may continue to run.
*
* @param tag The tag used to identify the work
- */
- @SuppressWarnings("FutureReturnValueIgnored")
- public void cancelAllWorkByTag(@NonNull String tag) {
- cancelAllWorkByTagInternal(tag);
- }
-
- /**
- * Cancels all unfinished work with the given tag. Note that cancellation is a best-effort
- * policy and work that is already executing may continue to run.
- *
- * @param tag The tag used to identify the work
- * @return A {@link ListenableFuture} that completes when the cancelAllWorkByTag operation is
+ * @return An {@link Operation} that can be used to determine when the cancelAllWorkByTag has
* completed
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public abstract @NonNull ListenableFuture<Void> cancelAllWorkByTagInternal(@NonNull String tag);
-
- /**
- * Cancels all unfinished work in the work chain with the given name. Note that cancellation is
- * a best-effort policy and work that is already executing may continue to run.
- *
- * @param uniqueWorkName The unique name used to identify the chain of wor
- */
- @SuppressWarnings("FutureReturnValueIgnored")
- public void cancelUniqueWork(@NonNull String uniqueWorkName) {
- cancelUniqueWorkInternal(uniqueWorkName);
- }
+ public abstract @NonNull Operation cancelAllWorkByTag(@NonNull String tag);
/**
* Cancels all unfinished work in the work chain with the given name. Note that cancellation is
* a best-effort policy and work that is already executing may continue to run.
*
* @param uniqueWorkName The unique name used to identify the chain of work
- * @return A {@link ListenableFuture} that completes when the cancelUniqueWork operation is
+ * @return An {@link Operation} that can be used to determine when the cancelUniqueWork has
* completed
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public abstract @NonNull
- ListenableFuture<Void> cancelUniqueWorkInternal(@NonNull String uniqueWorkName);
-
- /**
- * Cancels all unfinished work. <b>Use this method with extreme caution!</b> By invoking it,
- * you will potentially affect other modules or libraries in your codebase. It is strongly
- * recommended that you use one of the other cancellation methods at your disposal.
- */
- @SuppressWarnings("FutureReturnValueIgnored")
- public void cancelAllWork() {
- cancelAllWorkInternal();
- }
+ public abstract @NonNull Operation cancelUniqueWork(@NonNull String uniqueWorkName);
/**
* Cancels all unfinished work. <b>Use this method with extreme caution!</b> By invoking it,
* you will potentially affect other modules or libraries in your codebase. It is strongly
* recommended that you use one of the other cancellation methods at your disposal.
*
- * @return A {@link ListenableFuture} that completes when the cancelAllWork operation is
+ * @return An {@link Operation} that can be used to determine when the cancelAllWork has
* completed
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public abstract @NonNull ListenableFuture<Void> cancelAllWorkInternal();
+ public abstract @NonNull Operation cancelAllWork();
/**
* Prunes all eligible finished work from the internal database. Eligible work must be finished
- * ({@link State#SUCCEEDED}, {@link State#FAILED}, or {@link State#CANCELLED}), with zero
- * unfinished dependents.
+ * ({@link WorkInfo.State#SUCCEEDED}, {@link WorkInfo.State#FAILED}, or
+ * {@link WorkInfo.State#CANCELLED}), with zero unfinished dependents.
* <p>
* <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in
- * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work.
- * You do not normally need to call this method - WorkManager takes care to auto-prune its work
- * after a sane period of time. This method also ignores the
- * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy.
- */
- @SuppressWarnings("FutureReturnValueIgnored")
- public void pruneWork() {
- pruneWorkInternal();
- }
-
- /**
- * Prunes all eligible finished work from the internal database. Eligible work must be finished
- * ({@link State#SUCCEEDED}, {@link State#FAILED}, or {@link State#CANCELLED}), with zero
- * unfinished dependents.
- * <p>
- * <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in
- * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work.
+ * your codebase) will no longer be able to observe the {@link WorkInfo} of the pruned work.
* You do not normally need to call this method - WorkManager takes care to auto-prune its work
* after a sane period of time. This method also ignores the
* {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy.
*
- * @return A {@link ListenableFuture} that completes when the pruneWork operation is
+ * @return An {@link Operation} that can be used to determine when the pruneWork has
* completed
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public abstract @NonNull ListenableFuture<Void> pruneWorkInternal();
+ public abstract @NonNull Operation pruneWork();
/**
* Gets a {@link LiveData} of the last time all work was cancelled. This method is intended for
@@ -555,64 +425,64 @@
public abstract @NonNull ListenableFuture<Long> getLastCancelAllTimeMillis();
/**
- * Gets a {@link LiveData} of the {@link WorkStatus} for a given work id.
+ * Gets a {@link LiveData} of the {@link WorkInfo} for a given work id.
*
* @param id The id of the work
- * @return A {@link LiveData} of the {@link WorkStatus} associated with {@code id}; note that
- * this {@link WorkStatus} may be {@code null} if {@code id} is not known to
+ * @return A {@link LiveData} of the {@link WorkInfo} associated with {@code id}; note that
+ * this {@link WorkInfo} may be {@code null} if {@code id} is not known to
* WorkManager.
*/
- public abstract @NonNull LiveData<WorkStatus> getStatusByIdLiveData(@NonNull UUID id);
+ public abstract @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id);
/**
- * Gets a {@link ListenableFuture} of the {@link WorkStatus} for a given work id.
+ * Gets a {@link ListenableFuture} of the {@link WorkInfo} for a given work id.
*
* @param id The id of the work
- * @return A {@link ListenableFuture} of the {@link WorkStatus} associated with {@code id};
- * note that this {@link WorkStatus} may be {@code null} if {@code id} is not known to
+ * @return A {@link ListenableFuture} of the {@link WorkInfo} associated with {@code id};
+ * note that this {@link WorkInfo} may be {@code null} if {@code id} is not known to
* WorkManager
*/
- public abstract @NonNull ListenableFuture<WorkStatus> getStatusById(@NonNull UUID id);
+ public abstract @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id);
/**
- * Gets a {@link LiveData} of the {@link WorkStatus} for all work for a given tag.
+ * Gets a {@link LiveData} of the {@link WorkInfo} for all work for a given tag.
*
* @param tag The tag of the work
- * @return A {@link LiveData} list of {@link WorkStatus} for work tagged with {@code tag}
+ * @return A {@link LiveData} list of {@link WorkInfo} for work tagged with {@code tag}
*/
- public abstract @NonNull LiveData<List<WorkStatus>> getStatusesByTagLiveData(
+ public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(
@NonNull String tag);
/**
- * Gets a {@link ListenableFuture} of the {@link WorkStatus} for all work for a given tag.
+ * Gets a {@link ListenableFuture} of the {@link WorkInfo} for all work for a given tag.
*
* @param tag The tag of the work
- * @return A {@link ListenableFuture} list of {@link WorkStatus} for work tagged with
+ * @return A {@link ListenableFuture} list of {@link WorkInfo} for work tagged with
* {@code tag}
*/
- public abstract @NonNull ListenableFuture<List<WorkStatus>> getStatusesByTag(
+ public abstract @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosByTag(
@NonNull String tag);
/**
- * Gets a {@link LiveData} of the {@link WorkStatus} for all work in a work chain with a given
+ * Gets a {@link LiveData} of the {@link WorkInfo} for all work in a work chain with a given
* unique name.
*
* @param uniqueWorkName The unique name used to identify the chain of work
- * @return A {@link LiveData} of the {@link WorkStatus} for work in the chain named
+ * @return A {@link LiveData} of the {@link WorkInfo} for work in the chain named
* {@code uniqueWorkName}
*/
- public abstract @NonNull LiveData<List<WorkStatus>> getStatusesForUniqueWorkLiveData(
+ public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosForUniqueWorkLiveData(
@NonNull String uniqueWorkName);
/**
- * Gets a {@link ListenableFuture} of the {@link WorkStatus} for all work in a work chain
+ * Gets a {@link ListenableFuture} of the {@link WorkInfo} for all work in a work chain
* with a given unique name.
*
* @param uniqueWorkName The unique name used to identify the chain of work
- * @return A {@link ListenableFuture} of the {@link WorkStatus} for work in the chain named
+ * @return A {@link ListenableFuture} of the {@link WorkInfo} for work in the chain named
* {@code uniqueWorkName}
*/
- public abstract @NonNull ListenableFuture<List<WorkStatus>> getStatusesForUniqueWork(
+ public abstract @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(
@NonNull String uniqueWorkName);
/**
diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
index 7e5b7eb..b17385f 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
@@ -185,7 +185,7 @@
* WorkManager when there are no pending dependent jobs.
*
* When the results of a work are pruned, it becomes impossible to query for its
- * {@link WorkStatus}.
+ * {@link WorkInfo}.
*
* Specifying a long duration here may adversely affect performance in terms of app storage
* and database query time.
@@ -206,7 +206,7 @@
* WorkManager when there are no pending dependent jobs.
*
* When the results of a work are pruned, it becomes impossible to query for its
- * {@link WorkStatus}.
+ * {@link WorkInfo}.
*
* Specifying a long duration here may adversely affect performance in terms of app storage
* and database query time.
@@ -241,13 +241,13 @@
/**
* Set the initial state for this work. Used in testing only.
*
- * @param state The {@link State} to set
+ * @param state The {@link WorkInfo.State} to set
* @return The current {@link Builder}
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@VisibleForTesting
- public final @NonNull B setInitialState(@NonNull State state) {
+ public final @NonNull B setInitialState(@NonNull WorkInfo.State state) {
mWorkSpec.state = state;
return getThis();
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/OperationImpl.java b/work/workmanager/src/main/java/androidx/work/impl/OperationImpl.java
new file mode 100644
index 0000000..270f3bb
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/OperationImpl.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 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 androidx.work.impl;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import androidx.work.Operation;
+import androidx.work.impl.utils.futures.SettableFuture;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * A concrete implementation of a {@link Operation}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class OperationImpl implements Operation {
+
+ private final MutableLiveData<State> mOperationState;
+ private final SettableFuture<State.SUCCESS> mOperationFuture;
+
+ public OperationImpl() {
+ mOperationState = new MutableLiveData<>();
+ mOperationFuture = SettableFuture.create();
+ // Mark the operation as in progress.
+ setState(Operation.IN_PROGRESS);
+ }
+
+ @Override
+ public @NonNull ListenableFuture<State.SUCCESS> getResult() {
+ return mOperationFuture;
+ }
+
+ @Override
+ public @NonNull LiveData<State> getState() {
+ return mOperationState;
+ }
+
+ /**
+ * Updates the {@link androidx.work.Operation.State} of the {@link Operation}.
+ *
+ * @param state The current {@link Operation.State}
+ */
+ public void setState(@NonNull State state) {
+ mOperationState.postValue(state);
+
+ // Only terminal state get updates to the future.
+ if (state instanceof State.SUCCESS) {
+ mOperationFuture.set((State.SUCCESS) state);
+ } else if (state instanceof State.FAILURE) {
+ State.FAILURE failed = (State.FAILURE) state;
+ mOperationFuture.setException(failed.getException());
+ }
+ }
+}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
index d3137e3..f121d46 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
@@ -26,9 +26,10 @@
import androidx.work.ExistingWorkPolicy;
import androidx.work.Logger;
import androidx.work.OneTimeWorkRequest;
+import androidx.work.Operation;
import androidx.work.WorkContinuation;
+import androidx.work.WorkInfo;
import androidx.work.WorkRequest;
-import androidx.work.WorkStatus;
import androidx.work.impl.utils.EnqueueRunnable;
import androidx.work.impl.utils.StatusRunnable;
import androidx.work.impl.workers.CombineContinuationsWorker;
@@ -60,7 +61,7 @@
private final List<WorkContinuationImpl> mParents;
private boolean mEnqueued;
- private ListenableFuture<Void> mFuture;
+ private Operation mOperation;
@NonNull
public WorkManagerImpl getWorkManagerImpl() {
@@ -160,14 +161,14 @@
}
@Override
- public @NonNull LiveData<List<WorkStatus>> getStatusesLiveData() {
- return mWorkManagerImpl.getStatusesById(mAllIds);
+ public @NonNull LiveData<List<WorkInfo>> getWorkInfosLiveData() {
+ return mWorkManagerImpl.getWorkInfosById(mAllIds);
}
@NonNull
@Override
- public ListenableFuture<List<WorkStatus>> getStatuses() {
- StatusRunnable<List<WorkStatus>> runnable =
+ public ListenableFuture<List<WorkInfo>> getWorkInfos() {
+ StatusRunnable<List<WorkInfo>> runnable =
StatusRunnable.forStringIds(mWorkManagerImpl, mAllIds);
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
@@ -175,19 +176,19 @@
}
@Override
- public ListenableFuture<Void> enqueue() {
+ public @NonNull Operation enqueue() {
// Only enqueue if not already enqueued.
if (!mEnqueued) {
// The runnable walks the hierarchy of the continuations
// and marks them enqueued using the markEnqueued() method, parent first.
EnqueueRunnable runnable = new EnqueueRunnable(this);
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
- mFuture = runnable.getFuture();
+ mOperation = runnable.getOperation();
} else {
Logger.warning(TAG,
String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
}
- return mFuture;
+ return mOperation;
}
@Override
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index f5b7b97..e585285 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -31,12 +31,13 @@
import androidx.work.ExistingWorkPolicy;
import androidx.work.Logger;
import androidx.work.OneTimeWorkRequest;
+import androidx.work.Operation;
import androidx.work.PeriodicWorkRequest;
import androidx.work.R;
import androidx.work.WorkContinuation;
+import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
-import androidx.work.WorkStatus;
import androidx.work.WorkerParameters;
import androidx.work.impl.background.greedy.GreedyScheduler;
import androidx.work.impl.background.systemjob.SystemJobScheduler;
@@ -117,7 +118,9 @@
}
/**
- * Initializes the singleton instance of {@link WorkManagerImpl}.
+ * Initializes the singleton instance of {@link WorkManagerImpl}. You should only do this if
+ * you want to use a custom {@link Configuration} object and have disabled
+ * WorkManagerInitializer.
*
* @param context A {@link Context} object for configuration purposes. Internally, this class
* will call {@link Context#getApplicationContext()}, so you may safely pass in
@@ -129,6 +132,14 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
synchronized (sLock) {
+ if (sDelegatedInstance != null && sDefaultInstance != null) {
+ throw new IllegalStateException("WorkManager is already initialized. Did you "
+ + "try to initialize it manually without disabling "
+ + "WorkManagerInitializer? See "
+ + "WorkManager#initialize(Context, Configuration) or the class level"
+ + "Javadoc for more information.");
+ }
+
if (sDelegatedInstance == null) {
context = context.getApplicationContext();
if (sDefaultInstance == null) {
@@ -280,10 +291,10 @@
@Override
@NonNull
- public ListenableFuture<Void> enqueueInternal(
+ public Operation enqueue(
@NonNull List<? extends WorkRequest> workRequests) {
- // This error is not being propagated as part of the ListenableFuture, as we want the
+ // This error is not being propagated as part of the Operation, as we want the
// app to crash during development. Having no workRequests is always a developer error.
if (workRequests.isEmpty()) {
throw new IllegalArgumentException(
@@ -315,7 +326,7 @@
@NonNull
@Override
- public ListenableFuture<Void> enqueueUniqueWorkInternal(@NonNull String uniqueWorkName,
+ public Operation enqueueUniqueWork(@NonNull String uniqueWorkName,
@NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull List<OneTimeWorkRequest> work) {
return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, work).enqueue();
@@ -323,7 +334,7 @@
@Override
@NonNull
- public ListenableFuture<Void> enqueueUniquePeriodicWorkInternal(
+ public Operation enqueueUniquePeriodicWork(
@NonNull String uniqueWorkName,
@NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
@NonNull PeriodicWorkRequest periodicWork) {
@@ -353,32 +364,32 @@
}
@Override
- public @NonNull ListenableFuture<Void> cancelWorkByIdInternal(@NonNull UUID id) {
+ public @NonNull Operation cancelWorkById(@NonNull UUID id) {
CancelWorkRunnable runnable = CancelWorkRunnable.forId(id, this);
mWorkTaskExecutor.executeOnBackgroundThread(runnable);
- return runnable.getFuture();
+ return runnable.getOperation();
}
@Override
- public @NonNull ListenableFuture<Void> cancelAllWorkByTagInternal(@NonNull final String tag) {
+ public @NonNull Operation cancelAllWorkByTag(@NonNull final String tag) {
CancelWorkRunnable runnable = CancelWorkRunnable.forTag(tag, this);
mWorkTaskExecutor.executeOnBackgroundThread(runnable);
- return runnable.getFuture();
+ return runnable.getOperation();
}
@Override
@NonNull
- public ListenableFuture<Void> cancelUniqueWorkInternal(@NonNull String uniqueWorkName) {
+ public Operation cancelUniqueWork(@NonNull String uniqueWorkName) {
CancelWorkRunnable runnable = CancelWorkRunnable.forName(uniqueWorkName, this, true);
mWorkTaskExecutor.executeOnBackgroundThread(runnable);
- return runnable.getFuture();
+ return runnable.getOperation();
}
@Override
- public @NonNull ListenableFuture<Void> cancelAllWorkInternal() {
+ public @NonNull Operation cancelAllWork() {
CancelWorkRunnable runnable = CancelWorkRunnable.forAll(this);
mWorkTaskExecutor.executeOnBackgroundThread(runnable);
- return runnable.getFuture();
+ return runnable.getOperation();
}
@Override
@@ -405,84 +416,84 @@
}
@Override
- public @NonNull ListenableFuture<Void> pruneWorkInternal() {
+ public @NonNull Operation pruneWork() {
PruneWorkRunnable runnable = new PruneWorkRunnable(this);
mWorkTaskExecutor.executeOnBackgroundThread(runnable);
- return runnable.getFuture();
+ return runnable.getOperation();
}
@Override
- public @NonNull LiveData<WorkStatus> getStatusByIdLiveData(@NonNull UUID id) {
+ public @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id) {
WorkSpecDao dao = mWorkDatabase.workSpecDao();
- LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
+ LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
dao.getWorkStatusPojoLiveDataForIds(Collections.singletonList(id.toString()));
return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData,
- new Function<List<WorkSpec.WorkStatusPojo>, WorkStatus>() {
+ new Function<List<WorkSpec.WorkInfoPojo>, WorkInfo>() {
@Override
- public WorkStatus apply(List<WorkSpec.WorkStatusPojo> input) {
- WorkStatus workStatus = null;
+ public WorkInfo apply(List<WorkSpec.WorkInfoPojo> input) {
+ WorkInfo workInfo = null;
if (input != null && input.size() > 0) {
- workStatus = input.get(0).toWorkStatus();
+ workInfo = input.get(0).toWorkInfo();
}
- return workStatus;
+ return workInfo;
}
},
mWorkTaskExecutor);
}
@Override
- public @NonNull ListenableFuture<WorkStatus> getStatusById(@NonNull UUID id) {
- StatusRunnable<WorkStatus> runnable = StatusRunnable.forUUID(this, id);
+ public @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id) {
+ StatusRunnable<WorkInfo> runnable = StatusRunnable.forUUID(this, id);
mWorkTaskExecutor.getBackgroundExecutor().execute(runnable);
return runnable.getFuture();
}
@Override
- public @NonNull LiveData<List<WorkStatus>> getStatusesByTagLiveData(@NonNull String tag) {
+ public @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(@NonNull String tag) {
WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
- LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
+ LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
workSpecDao.getWorkStatusPojoLiveDataForTag(tag);
return LiveDataUtils.dedupedMappedLiveDataFor(
inputLiveData,
- WorkSpec.WORK_STATUS_MAPPER,
+ WorkSpec.WORK_INFO_MAPPER,
mWorkTaskExecutor);
}
@Override
- public @NonNull ListenableFuture<List<WorkStatus>> getStatusesByTag(@NonNull String tag) {
- StatusRunnable<List<WorkStatus>> runnable = StatusRunnable.forTag(this, tag);
+ public @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosByTag(@NonNull String tag) {
+ StatusRunnable<List<WorkInfo>> runnable = StatusRunnable.forTag(this, tag);
mWorkTaskExecutor.getBackgroundExecutor().execute(runnable);
return runnable.getFuture();
}
@Override
- public @NonNull LiveData<List<WorkStatus>> getStatusesForUniqueWorkLiveData(
- @NonNull String name) {
+ @NonNull
+ public LiveData<List<WorkInfo>> getWorkInfosForUniqueWorkLiveData(@NonNull String name) {
WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
- LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
+ LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
workSpecDao.getWorkStatusPojoLiveDataForName(name);
return LiveDataUtils.dedupedMappedLiveDataFor(
inputLiveData,
- WorkSpec.WORK_STATUS_MAPPER,
+ WorkSpec.WORK_INFO_MAPPER,
mWorkTaskExecutor);
}
@Override
- public @NonNull ListenableFuture<List<WorkStatus>> getStatusesForUniqueWork(
- @NonNull String name) {
- StatusRunnable<List<WorkStatus>> runnable =
+ @NonNull
+ public ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(@NonNull String name) {
+ StatusRunnable<List<WorkInfo>> runnable =
StatusRunnable.forUniqueWork(this, name);
mWorkTaskExecutor.getBackgroundExecutor().execute(runnable);
return runnable.getFuture();
}
- LiveData<List<WorkStatus>> getStatusesById(@NonNull List<String> workSpecIds) {
+ LiveData<List<WorkInfo>> getWorkInfosById(@NonNull List<String> workSpecIds) {
WorkSpecDao dao = mWorkDatabase.workSpecDao();
- LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
+ LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
dao.getWorkStatusPojoLiveDataForIds(workSpecIds);
return LiveDataUtils.dedupedMappedLiveDataFor(
inputLiveData,
- WorkSpec.WORK_STATUS_MAPPER,
+ WorkSpec.WORK_INFO_MAPPER,
mWorkTaskExecutor);
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index ac9e818..f989803 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -16,11 +16,11 @@
package androidx.work.impl;
-import static androidx.work.State.CANCELLED;
-import static androidx.work.State.ENQUEUED;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.RUNNING;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.CANCELLED;
+import static androidx.work.WorkInfo.State.ENQUEUED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.RUNNING;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
import android.content.Context;
@@ -37,7 +37,7 @@
import androidx.work.ListenableWorker;
import androidx.work.ListenableWorker.Result;
import androidx.work.Logger;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import androidx.work.impl.background.systemalarm.RescheduleReceiver;
@@ -264,7 +264,7 @@
if (!tryCheckForInterruptionAndResolve()) {
try {
mWorkDatabase.beginTransaction();
- State state = mWorkSpecDao.getState(mWorkSpecId);
+ WorkInfo.State state = mWorkSpecDao.getState(mWorkSpecId);
if (state == null) {
// state can be null here with a REPLACE on beginUniqueWork().
// Treat it as a failure, and rescheduleAndResolve() will
@@ -318,12 +318,12 @@
}
// Worker can be null if run() hasn't been called yet.
if (mWorker != null) {
- mWorker.stop(cancelled);
+ mWorker.stop();
}
}
private void resolveIncorrectStatus() {
- State status = mWorkSpecDao.getState(mWorkSpecId);
+ WorkInfo.State status = mWorkSpecDao.getState(mWorkSpecId);
if (status == RUNNING) {
Logger.debug(TAG, String.format("Status for %s is RUNNING;"
+ "not doing any work and rescheduling for later execution", mWorkSpecId));
@@ -338,7 +338,7 @@
private boolean tryCheckForInterruptionAndResolve() {
if (mInterrupted) {
Logger.info(TAG, String.format("Work interrupted for %s", mWorkDescription));
- State currentState = mWorkSpecDao.getState(mWorkSpecId);
+ WorkInfo.State currentState = mWorkSpecDao.getState(mWorkSpecId);
if (currentState == null) {
// This can happen because of a beginUniqueWork(..., REPLACE, ...). Notify the
// listeners so we can clean up any wake locks, etc.
@@ -408,7 +408,7 @@
boolean setToRunning = false;
mWorkDatabase.beginTransaction();
try {
- State currentState = mWorkSpecDao.getState(mWorkSpecId);
+ WorkInfo.State currentState = mWorkSpecDao.getState(mWorkSpecId);
if (currentState == ENQUEUED) {
mWorkSpecDao.setState(RUNNING, mWorkSpecId);
mWorkSpecDao.incrementWorkSpecRunAttemptCount(mWorkSpecId);
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
index 5e695ba..b174ab1 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
@@ -24,7 +24,7 @@
import android.text.TextUtils;
import androidx.work.Logger;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkManagerImpl;
@@ -78,7 +78,7 @@
List<WorkSpec> constrainedWorkSpecs = new ArrayList<>();
List<String> constrainedWorkSpecIds = new ArrayList<>();
for (WorkSpec workSpec: workSpecs) {
- if (workSpec.state == State.ENQUEUED
+ if (workSpec.state == WorkInfo.State.ENQUEUED
&& !workSpec.isPeriodic()
&& workSpec.initialDelay == 0L
&& !workSpec.isBackedOff()) {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
index daa5b96..8ac6d01 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
@@ -25,7 +25,7 @@
import android.support.annotation.WorkerThread;
import androidx.work.Logger;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
@@ -211,7 +211,7 @@
"Skipping scheduling " + workSpecId + " because it's no longer in "
+ "the DB");
return;
- } else if (workSpec.state != State.ENQUEUED) {
+ } else if (workSpec.state != WorkInfo.State.ENQUEUED) {
Logger.warning(TAG,
"Skipping scheduling " + workSpecId + " because it is no longer "
+ "enqueued");
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index 67ecd38..cdb7aef 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -26,7 +26,7 @@
import android.support.annotation.VisibleForTesting;
import androidx.work.Logger;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
@@ -92,7 +92,7 @@
"Skipping scheduling " + workSpec.id
+ " because it's no longer in the DB");
continue;
- } else if (currentDbWorkSpec.state != State.ENQUEUED) {
+ } else if (currentDbWorkSpec.state != WorkInfo.State.ENQUEUED) {
Logger.warning(
TAG,
"Skipping scheduling " + workSpec.id
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
index c501aad..d99e5b9 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
@@ -18,7 +18,7 @@
import static androidx.work.PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS;
import static androidx.work.PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS;
-import static androidx.work.State.ENQUEUED;
+import static androidx.work.WorkInfo.State.ENQUEUED;
import static androidx.work.WorkRequest.MAX_BACKOFF_MILLIS;
import static androidx.work.WorkRequest.MIN_BACKOFF_MILLIS;
@@ -36,9 +36,8 @@
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.Logger;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.WorkRequest;
-import androidx.work.WorkStatus;
import java.util.ArrayList;
import java.util.List;
@@ -64,7 +63,7 @@
@ColumnInfo(name = "state")
@NonNull
- public State state = ENQUEUED;
+ public WorkInfo.State state = ENQUEUED;
@ColumnInfo(name = "worker_class_name")
@NonNull
@@ -326,7 +325,7 @@
public String id;
@ColumnInfo(name = "state")
- public State state;
+ public WorkInfo.State state;
@Override
public boolean equals(Object o) {
@@ -350,13 +349,13 @@
/**
* A POJO containing the ID, state, output, and tags of a WorkSpec.
*/
- public static class WorkStatusPojo {
+ public static class WorkInfoPojo {
@ColumnInfo(name = "id")
public String id;
@ColumnInfo(name = "state")
- public State state;
+ public WorkInfo.State state;
@ColumnInfo(name = "output")
public Data output;
@@ -369,12 +368,12 @@
public List<String> tags;
/**
- * Converts this POJO to a {@link WorkStatus}.
+ * Converts this POJO to a {@link WorkInfo}.
*
- * @return The {@link WorkStatus} represented by this POJO
+ * @return The {@link WorkInfo} represented by this POJO
*/
- public WorkStatus toWorkStatus() {
- return new WorkStatus(UUID.fromString(id), state, output, tags);
+ public WorkInfo toWorkInfo() {
+ return new WorkInfo(UUID.fromString(id), state, output, tags);
}
@Override
@@ -382,7 +381,7 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- WorkStatusPojo that = (WorkStatusPojo) o;
+ WorkInfoPojo that = (WorkInfoPojo) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (state != that.state) return false;
@@ -400,16 +399,16 @@
}
}
- public static final Function<List<WorkStatusPojo>, List<WorkStatus>> WORK_STATUS_MAPPER =
- new Function<List<WorkStatusPojo>, List<WorkStatus>>() {
+ public static final Function<List<WorkInfoPojo>, List<WorkInfo>> WORK_INFO_MAPPER =
+ new Function<List<WorkInfoPojo>, List<WorkInfo>>() {
@Override
- public List<WorkStatus> apply(List<WorkStatusPojo> input) {
+ public List<WorkInfo> apply(List<WorkInfoPojo> input) {
if (input == null) {
return null;
}
- List<WorkStatus> output = new ArrayList<>(input.size());
- for (WorkStatusPojo in : input) {
- output.add(in.toWorkStatus());
+ List<WorkInfo> output = new ArrayList<>(input.size());
+ for (WorkInfoPojo in : input) {
+ output.add(in.toWorkInfo());
}
return output;
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
index dcade1f..b53caf7 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
@@ -28,7 +28,7 @@
import android.support.annotation.NonNull;
import androidx.work.Data;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import java.util.List;
@@ -94,7 +94,7 @@
* @return The number of rows that were updated
*/
@Query("UPDATE workspec SET state=:state WHERE id IN (:ids)")
- int setState(State state, String... ids);
+ int setState(WorkInfo.State state, String... ids);
/**
* Updates the output of a {@link WorkSpec}.
@@ -139,85 +139,85 @@
* @return The state of the {@link WorkSpec}
*/
@Query("SELECT state FROM workspec WHERE id=:id")
- State getState(String id);
+ WorkInfo.State getState(String id);
/**
- * For a {@link WorkSpec} identifier, retrieves its {@link WorkSpec.WorkStatusPojo}.
+ * For a {@link WorkSpec} identifier, retrieves its {@link WorkSpec.WorkInfoPojo}.
*
* @param id The identifier of the {@link WorkSpec}
- * @return A list of {@link WorkSpec.WorkStatusPojo}
+ * @return A list of {@link WorkSpec.WorkInfoPojo}
*/
@Transaction
@Query("SELECT id, state, output FROM workspec WHERE id=:id")
- WorkSpec.WorkStatusPojo getWorkStatusPojoForId(String id);
+ WorkSpec.WorkInfoPojo getWorkStatusPojoForId(String id);
/**
* For a list of {@link WorkSpec} identifiers, retrieves a {@link List} of their
- * {@link WorkSpec.WorkStatusPojo}.
+ * {@link WorkSpec.WorkInfoPojo}.
*
* @param ids The identifier of the {@link WorkSpec}s
- * @return A {@link List} of {@link WorkSpec.WorkStatusPojo}
+ * @return A {@link List} of {@link WorkSpec.WorkInfoPojo}
*/
@Transaction
@Query("SELECT id, state, output FROM workspec WHERE id IN (:ids)")
- List<WorkSpec.WorkStatusPojo> getWorkStatusPojoForIds(List<String> ids);
+ List<WorkSpec.WorkInfoPojo> getWorkStatusPojoForIds(List<String> ids);
/**
* For a list of {@link WorkSpec} identifiers, retrieves a {@link LiveData} list of their
- * {@link WorkSpec.WorkStatusPojo}.
+ * {@link WorkSpec.WorkInfoPojo}.
*
* @param ids The identifier of the {@link WorkSpec}s
- * @return A {@link LiveData} list of {@link WorkSpec.WorkStatusPojo}
+ * @return A {@link LiveData} list of {@link WorkSpec.WorkInfoPojo}
*/
@Transaction
@Query("SELECT id, state, output FROM workspec WHERE id IN (:ids)")
- LiveData<List<WorkSpec.WorkStatusPojo>> getWorkStatusPojoLiveDataForIds(List<String> ids);
+ LiveData<List<WorkSpec.WorkInfoPojo>> getWorkStatusPojoLiveDataForIds(List<String> ids);
/**
- * Retrieves a list of {@link WorkSpec.WorkStatusPojo} for all work with a given tag.
+ * Retrieves a list of {@link WorkSpec.WorkInfoPojo} for all work with a given tag.
*
* @param tag The tag for the {@link WorkSpec}s
- * @return A list of {@link WorkSpec.WorkStatusPojo}
+ * @return A list of {@link WorkSpec.WorkInfoPojo}
*/
@Transaction
@Query("SELECT id, state, output FROM workspec WHERE id IN "
+ "(SELECT work_spec_id FROM worktag WHERE tag=:tag)")
- List<WorkSpec.WorkStatusPojo> getWorkStatusPojoForTag(String tag);
+ List<WorkSpec.WorkInfoPojo> getWorkStatusPojoForTag(String tag);
/**
- * Retrieves a {@link LiveData} list of {@link WorkSpec.WorkStatusPojo} for all work with a
+ * Retrieves a {@link LiveData} list of {@link WorkSpec.WorkInfoPojo} for all work with a
* given tag.
*
* @param tag The tag for the {@link WorkSpec}s
- * @return A {@link LiveData} list of {@link WorkSpec.WorkStatusPojo}
+ * @return A {@link LiveData} list of {@link WorkSpec.WorkInfoPojo}
*/
@Transaction
@Query("SELECT id, state, output FROM workspec WHERE id IN "
+ "(SELECT work_spec_id FROM worktag WHERE tag=:tag)")
- LiveData<List<WorkSpec.WorkStatusPojo>> getWorkStatusPojoLiveDataForTag(String tag);
+ LiveData<List<WorkSpec.WorkInfoPojo>> getWorkStatusPojoLiveDataForTag(String tag);
/**
- * Retrieves a list of {@link WorkSpec.WorkStatusPojo} for all work with a given name.
+ * Retrieves a list of {@link WorkSpec.WorkInfoPojo} for all work with a given name.
*
* @param name The name of the {@link WorkSpec}s
- * @return A list of {@link WorkSpec.WorkStatusPojo}
+ * @return A list of {@link WorkSpec.WorkInfoPojo}
*/
@Transaction
@Query("SELECT id, state, output FROM workspec WHERE id IN "
+ "(SELECT work_spec_id FROM workname WHERE name=:name)")
- List<WorkSpec.WorkStatusPojo> getWorkStatusPojoForName(String name);
+ List<WorkSpec.WorkInfoPojo> getWorkStatusPojoForName(String name);
/**
- * Retrieves a {@link LiveData} list of {@link WorkSpec.WorkStatusPojo} for all work with a
+ * Retrieves a {@link LiveData} list of {@link WorkSpec.WorkInfoPojo} for all work with a
* given name.
*
* @param name The name for the {@link WorkSpec}s
- * @return A {@link LiveData} list of {@link WorkSpec.WorkStatusPojo}
+ * @return A {@link LiveData} list of {@link WorkSpec.WorkInfoPojo}
*/
@Transaction
@Query("SELECT id, state, output FROM workspec WHERE id IN "
+ "(SELECT work_spec_id FROM workname WHERE name=:name)")
- LiveData<List<WorkSpec.WorkStatusPojo>> getWorkStatusPojoLiveDataForName(String name);
+ LiveData<List<WorkSpec.WorkInfoPojo>> getWorkStatusPojoLiveDataForName(String name);
/**
* Gets all inputs coming from prerequisites for a particular {@link WorkSpec}. These are
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java
index afa348c..3f7eb90 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java
@@ -18,12 +18,12 @@
import static androidx.work.BackoffPolicy.EXPONENTIAL;
import static androidx.work.BackoffPolicy.LINEAR;
-import static androidx.work.State.BLOCKED;
-import static androidx.work.State.CANCELLED;
-import static androidx.work.State.ENQUEUED;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.RUNNING;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.BLOCKED;
+import static androidx.work.WorkInfo.State.CANCELLED;
+import static androidx.work.WorkInfo.State.ENQUEUED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.RUNNING;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import android.arch.persistence.room.TypeConverter;
import android.net.Uri;
@@ -31,7 +31,7 @@
import androidx.work.BackoffPolicy;
import androidx.work.ContentUriTriggers;
import androidx.work.NetworkType;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -46,7 +46,7 @@
public class WorkTypeConverters {
/**
- * Integer identifiers that map to {@link State}.
+ * Integer identifiers that map to {@link WorkInfo.State}.
*/
public interface StateIds {
int ENQUEUED = 0;
@@ -85,7 +85,7 @@
* @return The associated int constant
*/
@TypeConverter
- public static int stateToInt(State state) {
+ public static int stateToInt(WorkInfo.State state) {
switch (state) {
case ENQUEUED:
return StateIds.ENQUEUED;
@@ -118,7 +118,7 @@
* @return The associated State enum value
*/
@TypeConverter
- public static State intToState(int value) {
+ public static WorkInfo.State intToState(int value) {
switch (value) {
case StateIds.ENQUEUED:
return ENQUEUED;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
index 8fa72a7..5dcd0d0 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
@@ -16,15 +16,17 @@
package androidx.work.impl.utils;
-import static androidx.work.State.CANCELLED;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.CANCELLED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
-import androidx.work.State;
+import androidx.work.Operation;
+import androidx.work.WorkInfo;
+import androidx.work.impl.OperationImpl;
import androidx.work.impl.Processor;
import androidx.work.impl.Scheduler;
import androidx.work.impl.Schedulers;
@@ -32,9 +34,6 @@
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.model.DependencyDao;
import androidx.work.impl.model.WorkSpecDao;
-import androidx.work.impl.utils.futures.SettableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.UUID;
@@ -47,22 +46,22 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class CancelWorkRunnable implements Runnable {
- private final SettableFuture<Void> mFuture = SettableFuture.create();
+ private final OperationImpl mOperation = new OperationImpl();
/**
- * @return A {@link ListenableFuture} that completes when the cancel operation is completed
+ * @return The {@link Operation} that encapsulates the state of the {@link CancelWorkRunnable}.
*/
- public ListenableFuture<Void> getFuture() {
- return mFuture;
+ public Operation getOperation() {
+ return mOperation;
}
@Override
public void run() {
try {
runInternal();
- mFuture.set(null);
+ mOperation.setState(Operation.SUCCESS);
} catch (Throwable throwable) {
- mFuture.setException(throwable);
+ mOperation.setState(new Operation.State.FAILURE(throwable));
}
}
@@ -95,7 +94,7 @@
recursivelyCancelWorkAndDependents(workDatabase, id);
}
- State state = workSpecDao.getState(workSpecId);
+ WorkInfo.State state = workSpecDao.getState(workSpecId);
if (state != SUCCEEDED && state != FAILED) {
workSpecDao.setState(CANCELLED, workSpecId);
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
index d6adfb8..cc02bcb 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
@@ -18,12 +18,12 @@
import static androidx.work.ExistingWorkPolicy.APPEND;
import static androidx.work.ExistingWorkPolicy.KEEP;
-import static androidx.work.State.BLOCKED;
-import static androidx.work.State.CANCELLED;
-import static androidx.work.State.ENQUEUED;
-import static androidx.work.State.FAILED;
-import static androidx.work.State.RUNNING;
-import static androidx.work.State.SUCCEEDED;
+import static androidx.work.WorkInfo.State.BLOCKED;
+import static androidx.work.WorkInfo.State.CANCELLED;
+import static androidx.work.WorkInfo.State.ENQUEUED;
+import static androidx.work.WorkInfo.State.FAILED;
+import static androidx.work.WorkInfo.State.RUNNING;
+import static androidx.work.WorkInfo.State.SUCCEEDED;
import static androidx.work.impl.workers.ConstraintTrackingWorker.ARGUMENT_CLASS_NAME;
import android.content.Context;
@@ -37,8 +37,10 @@
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.Logger;
-import androidx.work.State;
+import androidx.work.Operation;
+import androidx.work.WorkInfo;
import androidx.work.WorkRequest;
+import androidx.work.impl.OperationImpl;
import androidx.work.impl.Schedulers;
import androidx.work.impl.WorkContinuationImpl;
import androidx.work.impl.WorkDatabase;
@@ -50,11 +52,8 @@
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
import androidx.work.impl.model.WorkTag;
-import androidx.work.impl.utils.futures.SettableFuture;
import androidx.work.impl.workers.ConstraintTrackingWorker;
-import com.google.common.util.concurrent.ListenableFuture;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -70,11 +69,11 @@
private static final String TAG = "EnqueueRunnable";
private final WorkContinuationImpl mWorkContinuation;
- private final SettableFuture<Void> mFuture;
+ private final OperationImpl mOperation;
public EnqueueRunnable(@NonNull WorkContinuationImpl workContinuation) {
mWorkContinuation = workContinuation;
- mFuture = SettableFuture.create();
+ mOperation = new OperationImpl();
}
@Override
@@ -92,14 +91,17 @@
PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
scheduleWorkInBackground();
}
- mFuture.set(null);
+ mOperation.setState(Operation.SUCCESS);
} catch (Throwable exception) {
- mFuture.setException(exception);
+ mOperation.setState(new Operation.State.FAILURE(exception));
}
}
- public ListenableFuture<Void> getFuture() {
- return mFuture;
+ /**
+ * @return The {@link Operation} that encapsulates the state of the {@link EnqueueRunnable}.
+ */
+ public Operation getOperation() {
+ return mOperation;
}
/**
@@ -199,7 +201,7 @@
return false;
}
- State prerequisiteState = prerequisiteWorkSpec.state;
+ WorkInfo.State prerequisiteState = prerequisiteWorkSpec.state;
hasCompletedAllPrerequisites &= (prerequisiteState == SUCCEEDED);
if (prerequisiteState == FAILED) {
hasFailedPrerequisites = true;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
index 7a7fa43..3783fcf 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
@@ -18,12 +18,11 @@
import android.support.annotation.RestrictTo;
+import androidx.work.Operation;
+import androidx.work.impl.OperationImpl;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.model.WorkSpecDao;
-import androidx.work.impl.utils.futures.SettableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
/**
* A Runnable that prunes work in the background. Pruned work meets the following criteria:
@@ -36,26 +35,30 @@
public class PruneWorkRunnable implements Runnable {
private final WorkManagerImpl mWorkManagerImpl;
- private final SettableFuture<Void> mFuture;
+ private final OperationImpl mOperation;
public PruneWorkRunnable(WorkManagerImpl workManagerImpl) {
mWorkManagerImpl = workManagerImpl;
- mFuture = SettableFuture.create();
+ mOperation = new OperationImpl();
}
- public ListenableFuture<Void> getFuture() {
- return mFuture;
+ /**
+ * @return The {@link Operation} that encapsulates the state of the {@link PruneWorkRunnable}.
+ */
+ public Operation getOperation() {
+ return mOperation;
}
+
@Override
public void run() {
try {
WorkDatabase workDatabase = mWorkManagerImpl.getWorkDatabase();
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
workSpecDao.pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast();
- mFuture.set(null);
+ mOperation.setState(Operation.SUCCESS);
} catch (Throwable exception) {
- mFuture.setException(exception);
+ mOperation.setState(new Operation.State.FAILURE(exception));
}
}
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java
index 1b9b0dc..ef275e5 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java
@@ -20,7 +20,7 @@
import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
-import androidx.work.WorkStatus;
+import androidx.work.WorkInfo;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.model.WorkSpec;
@@ -32,7 +32,7 @@
import java.util.UUID;
/**
- * A {@link Runnable} to get {@link WorkStatus}es.
+ * A {@link Runnable} to get {@link WorkInfo}es.
*
* @param <T> The expected return type for the {@link ListenableFuture}.
* @hide
@@ -66,18 +66,18 @@
* @param ids The {@link List} of {@link String} ids
* @return an instance of {@link StatusRunnable}
*/
- public static StatusRunnable<List<WorkStatus>> forStringIds(
+ public static StatusRunnable<List<WorkInfo>> forStringIds(
@NonNull final WorkManagerImpl workManager,
@NonNull final List<String> ids) {
- return new StatusRunnable<List<WorkStatus>>() {
+ return new StatusRunnable<List<WorkInfo>>() {
@Override
- public List<WorkStatus> runInternal() {
+ public List<WorkInfo> runInternal() {
WorkDatabase workDatabase = workManager.getWorkDatabase();
- List<WorkSpec.WorkStatusPojo> workStatusPojos =
+ List<WorkSpec.WorkInfoPojo> workInfoPojos =
workDatabase.workSpecDao().getWorkStatusPojoForIds(ids);
- return WorkSpec.WORK_STATUS_MAPPER.apply(workStatusPojos);
+ return WorkSpec.WORK_INFO_MAPPER.apply(workInfoPojos);
}
};
}
@@ -90,18 +90,18 @@
* @param id The workSpec {@link UUID}
* @return an instance of {@link StatusRunnable}
*/
- public static StatusRunnable<WorkStatus> forUUID(
+ public static StatusRunnable<WorkInfo> forUUID(
@NonNull final WorkManagerImpl workManager,
@NonNull final UUID id) {
- return new StatusRunnable<WorkStatus>() {
+ return new StatusRunnable<WorkInfo>() {
@Override
- WorkStatus runInternal() {
+ WorkInfo runInternal() {
WorkDatabase workDatabase = workManager.getWorkDatabase();
- WorkSpec.WorkStatusPojo workStatusPojo =
+ WorkSpec.WorkInfoPojo workInfoPojo =
workDatabase.workSpecDao().getWorkStatusPojoForId(id.toString());
- return workStatusPojo != null ? workStatusPojo.toWorkStatus() : null;
+ return workInfoPojo != null ? workInfoPojo.toWorkInfo() : null;
}
};
}
@@ -114,18 +114,18 @@
* @param tag The {@link String} tag
* @return an instance of {@link StatusRunnable}
*/
- public static StatusRunnable<List<WorkStatus>> forTag(
+ public static StatusRunnable<List<WorkInfo>> forTag(
@NonNull final WorkManagerImpl workManager,
@NonNull final String tag) {
- return new StatusRunnable<List<WorkStatus>>() {
+ return new StatusRunnable<List<WorkInfo>>() {
@Override
- List<WorkStatus> runInternal() {
+ List<WorkInfo> runInternal() {
WorkDatabase workDatabase = workManager.getWorkDatabase();
- List<WorkSpec.WorkStatusPojo> workStatusPojos =
+ List<WorkSpec.WorkInfoPojo> workInfoPojos =
workDatabase.workSpecDao().getWorkStatusPojoForTag(tag);
- return WorkSpec.WORK_STATUS_MAPPER.apply(workStatusPojos);
+ return WorkSpec.WORK_INFO_MAPPER.apply(workInfoPojos);
}
};
}
@@ -138,18 +138,18 @@
* @param name The {@link String} unique name
* @return an instance of {@link StatusRunnable}
*/
- public static StatusRunnable<List<WorkStatus>> forUniqueWork(
+ public static StatusRunnable<List<WorkInfo>> forUniqueWork(
@NonNull final WorkManagerImpl workManager,
@NonNull final String name) {
- return new StatusRunnable<List<WorkStatus>>() {
+ return new StatusRunnable<List<WorkInfo>>() {
@Override
- List<WorkStatus> runInternal() {
+ List<WorkInfo> runInternal() {
WorkDatabase workDatabase = workManager.getWorkDatabase();
- List<WorkSpec.WorkStatusPojo> workStatusPojos =
+ List<WorkSpec.WorkInfoPojo> workInfoPojos =
workDatabase.workSpecDao().getWorkStatusPojoForName(name);
- return WorkSpec.WORK_STATUS_MAPPER.apply(workStatusPojos);
+ return WorkSpec.WORK_INFO_MAPPER.apply(workInfoPojos);
}
};
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
index a86ecad..64e7bfc 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
@@ -19,14 +19,14 @@
import android.support.annotation.RestrictTo;
import androidx.work.Logger;
-import androidx.work.State;
+import androidx.work.WorkInfo;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.model.WorkSpecDao;
/**
- * A {@link Runnable} that can stop work and set the {@link State} to {@link State#ENQUEUED} if it's
- * in {@link State#RUNNING}.
+ * A {@link Runnable} that can stop work and set the {@link WorkInfo.State} to
+ * {@link WorkInfo.State#ENQUEUED} if it's in {@link WorkInfo.State#RUNNING}.
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -48,8 +48,8 @@
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
workDatabase.beginTransaction();
try {
- if (workSpecDao.getState(mWorkSpecId) == State.RUNNING) {
- workSpecDao.setState(State.ENQUEUED, mWorkSpecId);
+ if (workSpecDao.getState(mWorkSpecId) == WorkInfo.State.RUNNING) {
+ workSpecDao.setState(WorkInfo.State.ENQUEUED, mWorkSpecId);
}
boolean isStopped = mWorkManagerImpl.getProcessor().stopWork(mWorkSpecId);
Logger.debug(
diff --git a/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java b/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
index 522937c..f31e463 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
@@ -177,11 +177,11 @@
}
@Override
- public void onStopped(boolean cancelled) {
- super.onStopped(cancelled);
+ public void onStopped() {
+ super.onStopped();
if (mDelegate != null) {
// Stop is the method that sets the stopped and cancelled bits and invokes onStopped.
- mDelegate.stop(cancelled);
+ mDelegate.stop();
}
}