Merge "A few API naming/annotation tweaks:" into androidx-master-dev
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxSpy.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxSpy.java
deleted file mode 100644
index e66492e..0000000
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxSpy.java
+++ /dev/null
@@ -1,43 +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.appcompat.widget;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-import androidx.annotation.VisibleForTesting;
-
-/**
- * CompoundDrawable.getButtonDrawable() method was only added in API23. This class exposes the
- * mButton drawable for testing.
- */
-public class AppCompatCheckBoxSpy extends AppCompatCheckBox {
-
- @VisibleForTesting
- Drawable mButton;
-
- public AppCompatCheckBoxSpy(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setButtonDrawable(Drawable buttonDrawable) {
- super.setButtonDrawable(buttonDrawable);
- mButton = buttonDrawable;
- }
-}
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java
index a3bc312..a427189 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java
@@ -28,6 +28,7 @@
import androidx.appcompat.graphics.drawable.AnimatedStateListDrawableCompat;
import androidx.appcompat.test.R;
import androidx.core.content.res.ResourcesCompat;
+import androidx.core.widget.CompoundButtonCompat;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
@@ -68,8 +69,8 @@
@Test
public void testDefaultButton_isAnimated() {
// Given an ACCB with the theme's button drawable
- final AppCompatCheckBoxSpy checkBox = mContainer.findViewById(R.id.checkbox_button_compat);
- final Drawable button = checkBox.mButton;
+ final AppCompatCheckBox checkBox = mContainer.findViewById(R.id.checkbox_button_compat);
+ final Drawable button = CompoundButtonCompat.getButtonDrawable(checkBox);
// Then this drawable should be an animated-selector
assertTrue(button instanceof AnimatedStateListDrawableCompat
@@ -82,9 +83,9 @@
@Test
public void testNullCompatButton() {
// Given an ACCB which specifies a null app:buttonCompat
- final AppCompatCheckBoxSpy checkBox = mContainer.findViewById(
+ final AppCompatCheckBox checkBox = mContainer.findViewById(
R.id.checkbox_null_button_compat);
- final Drawable button = checkBox.mButton;
+ final Drawable button = CompoundButtonCompat.getButtonDrawable(checkBox);
boolean isAnimated = button instanceof AnimatedStateListDrawableCompat;
// Then the drawable should be present but not animated
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonSpy.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonSpy.java
deleted file mode 100644
index 7f0ce6d..0000000
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonSpy.java
+++ /dev/null
@@ -1,43 +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.appcompat.widget;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-import androidx.annotation.VisibleForTesting;
-
-/**
- * CompoundDrawable.getButtonDrawable() method was only added in API23. This class exposes the
- * mButton drawable for testing.
- */
-public class AppCompatRadioButtonSpy extends AppCompatRadioButton {
-
- @VisibleForTesting
- Drawable mButton;
-
- public AppCompatRadioButtonSpy(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setButtonDrawable(Drawable buttonDrawable) {
- super.setButtonDrawable(buttonDrawable);
- mButton = buttonDrawable;
- }
-}
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java
index ee60157..6d8b6b2 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java
@@ -28,6 +28,7 @@
import androidx.appcompat.graphics.drawable.AnimatedStateListDrawableCompat;
import androidx.appcompat.test.R;
import androidx.core.content.res.ResourcesCompat;
+import androidx.core.widget.CompoundButtonCompat;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
@@ -68,9 +69,9 @@
@Test
public void testDefaultButton_isAnimated() {
// Given an ACRB with the theme's button drawable
- final AppCompatRadioButtonSpy radio = mContainer.findViewById(
+ final AppCompatRadioButton radio = mContainer.findViewById(
R.id.radiobutton_button_compat);
- final Drawable button = radio.mButton;
+ final Drawable button = CompoundButtonCompat.getButtonDrawable(radio);
// Then this drawable should be an animated-selector
assertTrue(button instanceof AnimatedStateListDrawableCompat
@@ -83,9 +84,9 @@
@Test
public void testNullCompatButton() {
// Given an ACRB which specifies a null app:buttonCompat
- final AppCompatRadioButtonSpy radio = mContainer.findViewById(
+ final AppCompatRadioButton radio = mContainer.findViewById(
R.id.radiobutton_null_button_compat);
- final Drawable button = radio.mButton;
+ final Drawable button = CompoundButtonCompat.getButtonDrawable(radio);
boolean isAnimated = button instanceof AnimatedStateListDrawableCompat;
// Then the drawable should be present but not animated
diff --git a/appcompat/src/androidTest/res/layout/appcompat_checkbox_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_checkbox_activity.xml
index 81187e1..73d9d16 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_checkbox_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_checkbox_activity.xml
@@ -29,12 +29,12 @@
android:text="@string/sample_text1"
app:fontFamily="@font/samplefont" />
- <androidx.appcompat.widget.AppCompatCheckBoxSpy
+ <androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/checkbox_button_compat"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- <androidx.appcompat.widget.AppCompatCheckBoxSpy
+ <androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/checkbox_null_button_compat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/appcompat/src/androidTest/res/layout/appcompat_radiobutton_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_radiobutton_activity.xml
index 82f6a18..a2627dd 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_radiobutton_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_radiobutton_activity.xml
@@ -29,12 +29,12 @@
android:text="@string/sample_text1"
app:fontFamily="@font/samplefont"/>
- <androidx.appcompat.widget.AppCompatRadioButtonSpy
+ <androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/radiobutton_button_compat"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
- <androidx.appcompat.widget.AppCompatRadioButtonSpy
+ <androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/radiobutton_null_button_compat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/fragment/test/api/current.txt b/fragment/test/api/current.txt
deleted file mode 100644
index 4e7c46b..0000000
--- a/fragment/test/api/current.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-// Signature format: 2.0
-package androidx.fragment.app.test {
-
- public final class FragmentScenario<F extends androidx.fragment.app.Fragment> {
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.test.FragmentScenario<F> launch(Class<F>);
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.test.FragmentScenario<F> launch(Class<F>, android.os.Bundle?);
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.test.FragmentScenario<F> launch(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.test.FragmentScenario<F> launchInContainer(Class<F>);
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.test.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?);
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.test.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
- method public androidx.fragment.app.test.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State);
- method public androidx.fragment.app.test.FragmentScenario<F> onFragment(androidx.fragment.app.test.FragmentScenario.FragmentAction<F>);
- method public androidx.fragment.app.test.FragmentScenario<F> recreate();
- }
-
- public static interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
- method public void perform(F);
- }
-
-}
-
diff --git a/fragment/testing/api/current.txt b/fragment/testing/api/current.txt
new file mode 100644
index 0000000..db0c92e
--- /dev/null
+++ b/fragment/testing/api/current.txt
@@ -0,0 +1,21 @@
+// Signature format: 2.0
+package androidx.fragment.app.testing {
+
+ public final class FragmentScenario<F extends androidx.fragment.app.Fragment> {
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
+ method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State);
+ method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F>);
+ method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
+ }
+
+ public static interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
+ method public void perform(F);
+ }
+
+}
+
diff --git a/fragment/test/build.gradle b/fragment/testing/build.gradle
similarity index 100%
rename from fragment/test/build.gradle
rename to fragment/testing/build.gradle
diff --git a/fragment/test/src/androidTest/AndroidManifest.xml b/fragment/testing/src/androidTest/AndroidManifest.xml
similarity index 93%
rename from fragment/test/src/androidTest/AndroidManifest.xml
rename to fragment/testing/src/androidTest/AndroidManifest.xml
index 4c52e72..215f70b 100644
--- a/fragment/test/src/androidTest/AndroidManifest.xml
+++ b/fragment/testing/src/androidTest/AndroidManifest.xml
@@ -15,6 +15,6 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.fragment.test.test">
+ package="androidx.fragment.testing.test">
<uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
</manifest>
diff --git a/fragment/test/src/androidTest/java/androidx/fragment/app/test/FragmentScenarioKotlinTest.kt b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioKotlinTest.kt
similarity index 97%
rename from fragment/test/src/androidTest/java/androidx/fragment/app/test/FragmentScenarioKotlinTest.kt
rename to fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioKotlinTest.kt
index 910bde2..132c344 100644
--- a/fragment/test/src/androidTest/java/androidx/fragment/app/test/FragmentScenarioKotlinTest.kt
+++ b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioKotlinTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.fragment.app.test
+package androidx.fragment.app.testing
import androidx.lifecycle.Lifecycle.State
import androidx.test.runner.AndroidJUnit4
diff --git a/fragment/test/src/androidTest/java/androidx/fragment/app/test/FragmentScenarioTest.java b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioTest.java
similarity index 99%
rename from fragment/test/src/androidTest/java/androidx/fragment/app/test/FragmentScenarioTest.java
rename to fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioTest.java
index eef02f1..bb2f2015 100644
--- a/fragment/test/src/androidTest/java/androidx/fragment/app/test/FragmentScenarioTest.java
+++ b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.fragment.app.test;
+package androidx.fragment.app.testing;
import static com.google.common.truth.Truth.assertThat;
diff --git a/fragment/test/src/androidTest/java/androidx/fragment/app/test/StateRecordingFragment.java b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/StateRecordingFragment.java
similarity index 98%
rename from fragment/test/src/androidTest/java/androidx/fragment/app/test/StateRecordingFragment.java
rename to fragment/testing/src/androidTest/java/androidx/fragment/app/testing/StateRecordingFragment.java
index 85bc2b3..71c1dd4 100644
--- a/fragment/test/src/androidTest/java/androidx/fragment/app/test/StateRecordingFragment.java
+++ b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/StateRecordingFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.fragment.app.test;
+package androidx.fragment.app.testing;
import android.os.Bundle;
import android.view.LayoutInflater;
diff --git a/fragment/test/src/main/AndroidManifest.xml b/fragment/testing/src/main/AndroidManifest.xml
similarity index 90%
rename from fragment/test/src/main/AndroidManifest.xml
rename to fragment/testing/src/main/AndroidManifest.xml
index 7c9f63f..0b3eb19 100644
--- a/fragment/test/src/main/AndroidManifest.xml
+++ b/fragment/testing/src/main/AndroidManifest.xml
@@ -16,13 +16,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="androidx.fragment.test">
+ package="androidx.fragment.testing">
<!-- TODO: Remove this override after androidx.test:core lowers the level -->
<uses-sdk tools:overrideLibrary="androidx.test.core" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<application>
<activity
- android:name="androidx.fragment.app.test.FragmentScenario$EmptyFragmentActivity"
+ android:name="androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity"
android:theme="@style/FragmentScenarioEmptyFragmentActivityTheme"
android:taskAffinity=""
android:multiprocess="true"
diff --git a/fragment/test/src/main/java/androidx/fragment/app/test/FragmentScenario.java b/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.java
similarity index 99%
rename from fragment/test/src/main/java/androidx/fragment/app/test/FragmentScenario.java
rename to fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.java
index 355c7d8..4d05214 100644
--- a/fragment/test/src/main/java/androidx/fragment/app/test/FragmentScenario.java
+++ b/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.fragment.app.test;
+package androidx.fragment.app.testing;
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.core.util.Preconditions.checkNotNull;
diff --git a/fragment/test/src/main/res/values/styles.xml b/fragment/testing/src/main/res/values/styles.xml
similarity index 100%
rename from fragment/test/src/main/res/values/styles.xml
rename to fragment/testing/src/main/res/values/styles.xml
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 d7a80e8..6bc07f6 100644
--- a/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
+++ b/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
@@ -36,6 +36,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.Instrumentation;
@@ -45,7 +46,6 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.ResultReceiver;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -54,6 +54,7 @@
import androidx.media.widget.test.R;
import androidx.media2.FileMediaItem2;
import androidx.media2.MediaController2;
+import androidx.media2.MediaController2.ControllerResult;
import androidx.media2.MediaItem2;
import androidx.media2.SessionCommand2;
import androidx.media2.SessionCommandGroup2;
@@ -118,6 +119,11 @@
checkAttachedToWindow();
mControllerCallback = mock(MediaController2.ControllerCallback.class);
+ when(mControllerCallback.onCustomCommand(
+ nullable(MediaController2.class),
+ nullable(SessionCommand2.class),
+ nullable(Bundle.class))).thenReturn(
+ new ControllerResult(ControllerResult.RESULT_CODE_SUCCESS, null));
mController = new MediaController2(mVideoView.getContext(),
mVideoView.getMediaSessionToken2(), mMainHandlerExecutor, mControllerCallback);
}
@@ -268,8 +274,7 @@
verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onCustomCommand(
any(MediaController2.class),
argThat(new CommandMatcher(EVENT_UPDATE_TRACK_STATUS)),
- argThat(new CommandArgumentMatcher(KEY_SUBTITLE_TRACK_COUNT, 2)),
- nullable(ResultReceiver.class));
+ argThat(new CommandArgumentMatcher(KEY_SUBTITLE_TRACK_COUNT, 2)));
// Select the first subtitle track
Bundle extra = new Bundle();
@@ -279,8 +284,7 @@
verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onCustomCommand(
any(MediaController2.class),
argThat(new CommandMatcher(EVENT_UPDATE_SUBTITLE_SELECTED)),
- argThat(new CommandArgumentMatcher(KEY_SELECTED_SUBTITLE_INDEX, 0)),
- nullable(ResultReceiver.class));
+ argThat(new CommandArgumentMatcher(KEY_SELECTED_SUBTITLE_INDEX, 0)));
// Select the second subtitle track
extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, 1);
@@ -289,8 +293,7 @@
verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onCustomCommand(
any(MediaController2.class),
argThat(new CommandMatcher(EVENT_UPDATE_SUBTITLE_SELECTED)),
- argThat(new CommandArgumentMatcher(KEY_SELECTED_SUBTITLE_INDEX, 1)),
- nullable(ResultReceiver.class));
+ argThat(new CommandArgumentMatcher(KEY_SELECTED_SUBTITLE_INDEX, 1)));
// Deselect subtitle track
mController.sendCustomCommand(
@@ -298,8 +301,7 @@
verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onCustomCommand(
any(MediaController2.class),
argThat(new CommandMatcher(EVENT_UPDATE_SUBTITLE_DESELECTED)),
- nullable(Bundle.class),
- nullable(ResultReceiver.class));
+ nullable(Bundle.class));
}
class CommandMatcher implements ArgumentMatcher<SessionCommand2> {
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 74c4799..f473e96 100644
--- a/media-widget/src/main/java/androidx/media/widget/MediaControlView2.java
+++ b/media-widget/src/main/java/androidx/media/widget/MediaControlView2.java
@@ -16,6 +16,9 @@
package androidx.media.widget;
+import static androidx.media2.MediaController2.ControllerResult.RESULT_CODE_NOT_SUPPORTED;
+import static androidx.media2.MediaController2.ControllerResult.RESULT_CODE_SUCCESS;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -31,7 +34,6 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.ResultReceiver;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -2426,9 +2428,9 @@
}
@Override
- public void onCustomCommand(@NonNull MediaController2 controller,
- @NonNull SessionCommand2 command, @Nullable Bundle args,
- @Nullable ResultReceiver receiver) {
+ public MediaController2.ControllerResult onCustomCommand(
+ @NonNull MediaController2 controller, @NonNull SessionCommand2 command,
+ @Nullable Bundle args) {
if (DEBUG) {
Log.d(TAG, "onCustomCommand(): command: " + command);
}
@@ -2519,7 +2521,11 @@
mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
}
break;
+ default:
+ return new MediaController2.ControllerResult(
+ RESULT_CODE_NOT_SUPPORTED, null);
}
+ return new MediaController2.ControllerResult(RESULT_CODE_SUCCESS, null);
}
}
}
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 6cbef60..eb25eea 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
@@ -739,7 +739,7 @@
Bundle data = new Bundle();
data.putInt(MediaControlView2.KEY_SELECTED_SUBTITLE_INDEX,
mSubtitleTracks.indexOfKey(trackIndex));
- mMediaSession.sendCustomCommand(
+ mMediaSession.broadcastCustomCommand(
new SessionCommand2(MediaControlView2.EVENT_UPDATE_SUBTITLE_SELECTED, null),
data);
}
@@ -753,7 +753,7 @@
mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
mSubtitleAnchorView.setVisibility(View.GONE);
- mMediaSession.sendCustomCommand(
+ mMediaSession.broadcastCustomCommand(
new SessionCommand2(MediaControlView2.EVENT_UPDATE_SUBTITLE_DESELECTED, null),
null);
}
@@ -996,7 +996,7 @@
if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
Bundle data = extractTrackInfoData();
if (data != null) {
- mMediaSession.sendCustomCommand(
+ mMediaSession.broadcastCustomCommand(
new SessionCommand2(MediaControlView2.EVENT_UPDATE_TRACK_STATUS,
null), data);
}
@@ -1081,7 +1081,7 @@
if (mMediaSession != null) {
Bundle data = extractTrackInfoData();
if (data != null) {
- mMediaSession.sendCustomCommand(
+ mMediaSession.broadcastCustomCommand(
new SessionCommand2(MediaControlView2.EVENT_UPDATE_TRACK_STATUS,
null), data);
}
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 9e98fcd..a32ab12 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
@@ -39,6 +39,7 @@
import androidx.versionedparcelable.ParcelImpl;
import androidx.versionedparcelable.ParcelUtils;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -180,6 +181,19 @@
}
@Override
+ public void setPlaylistWithSize(String controllerId, int size, Bundle metadata)
+ throws RemoteException {
+ MediaController2 controller2 = mMediaController2Map.get(controllerId);
+ List<MediaItem2> list = new ArrayList<>();
+ MediaItem2.Builder builder = new MediaItem2.Builder(0 /* flags */);
+ for (int i = 0; i < size; i++) {
+ // Make media ID of each item same with its index.
+ list.add(builder.setMediaId(Integer.toString(i)).build());
+ }
+ controller2.setPlaylist(list, MediaMetadata2.fromBundle(metadata));
+ }
+
+ @Override
public void setMediaItem(String controllerId, Bundle item) throws RemoteException {
MediaController2 controller2 = mMediaController2Map.get(controllerId);
controller2.setMediaItem(MediaItem2.fromBundle(item));
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 232c817..7a4c4ba 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
@@ -41,7 +41,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.support.mediacompat.testlib.IRemoteMediaSession2;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log;
@@ -222,20 +221,19 @@
}
}
- public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args) {
+ public void broadcastCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args) {
try {
- mBinder.sendCustomCommand(mSessionId, command.toBundle(), args);
+ mBinder.broadcastCustomCommand(mSessionId, command.toBundle(), args);
} catch (RemoteException ex) {
- Log.e(TAG, "Failed to call sendCustomCommand()");
+ Log.e(TAG, "Failed to call broadcastCustomCommand()");
}
}
public void sendCustomCommand(@NonNull ControllerInfo controller,
- @NonNull SessionCommand2 command, @Nullable Bundle args,
- @Nullable ResultReceiver receiver) {
+ @NonNull SessionCommand2 command, @Nullable Bundle args) {
try {
// TODO: ControllerInfo should be handled.
- mBinder.sendCustomCommand2(mSessionId, null, command.toBundle(), args, receiver);
+ mBinder.sendCustomCommand(mSessionId, null, command.toBundle(), args);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to call sendCustomCommand2()");
}
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 7df9a04..86230da 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
@@ -42,7 +42,6 @@
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
-import android.os.ResultReceiver;
import androidx.annotation.CallSuper;
import androidx.annotation.GuardedBy;
@@ -550,19 +549,19 @@
}
@Override
- public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
- Bundle args, ResultReceiver receiver) {
- mCallbackProxy.onCustomCommand(controller, command, args, receiver);
+ public MediaController2.ControllerResult onCustomCommand(MediaController2 controller,
+ SessionCommand2 command, Bundle args) {
synchronized (this) {
if (mOnCustomCommandRunnable != null) {
mOnCustomCommandRunnable.run();
}
}
+ return mCallbackProxy.onCustomCommand(controller, command, args);
}
@Override
- public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
- mCallbackProxy.onCustomLayoutChanged(controller, layout);
+ public int onSetCustomLayout(MediaController2 controller, List<CommandButton> layout) {
+ return mCallbackProxy.onSetCustomLayout(controller, layout);
}
@Override
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 ca5f3e9..97a1c19 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
@@ -23,7 +23,9 @@
import static androidx.media.test.lib.CommonConstants.INDEX_FOR_NULL_ITEM;
import static androidx.media.test.lib.CommonConstants.INDEX_FOR_UNKONWN_ITEM;
import static androidx.media.test.lib.CommonConstants.MOCK_MEDIA_LIBRARY_SERVICE;
-import static androidx.media.test.lib.MediaSession2Constants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
+import static androidx.media.test.lib.MediaSession2Constants
+ .TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
+import static androidx.media2.MediaController2.ControllerResult.RESULT_CODE_SUCCESS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -34,7 +36,6 @@
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
-import android.os.ResultReceiver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -604,21 +605,21 @@
final MediaController2.ControllerCallback callback =
new MediaController2.ControllerCallback() {
@Override
- public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
- Bundle args, ResultReceiver receiver) {
+ public MediaController2.ControllerResult onCustomCommand(MediaController2 controller,
+ SessionCommand2 command, Bundle args) {
assertEquals(testCommand, command);
assertTrue(TestUtils.equals(testArgs, args));
- assertNull(receiver);
latch.countDown();
+ return new MediaController2.ControllerResult(RESULT_CODE_SUCCESS, null);
}
};
MediaController2 controller = createController(mRemoteSession2.getToken(), true, callback);
// TODO(jaewan): Test with multiple controllers
- mRemoteSession2.sendCustomCommand(testCommand, testArgs);
+ mRemoteSession2.broadcastCustomCommand(testCommand, testArgs);
// TODO(jaewan): Test receivers as well.
- mRemoteSession2.sendCustomCommand(TEST_CONTROLLER_INFO, testCommand, testArgs, null);
+ mRemoteSession2.sendCustomCommand(TEST_CONTROLLER_INFO, testCommand, testArgs);
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@@ -637,7 +638,7 @@
final MediaController2.ControllerCallback callback =
new MediaController2.ControllerCallback() {
@Override
- public void onCustomLayoutChanged(MediaController2 controller2,
+ public int onSetCustomLayout(MediaController2 controller2,
List<MediaSession2.CommandButton> layout) {
assertEquals(layout.size(), buttons.size());
for (int i = 0; i < layout.size(); i++) {
@@ -645,6 +646,7 @@
assertEquals(layout.get(i).getDisplayName(), buttons.get(i).getDisplayName());
}
latch.countDown();
+ return RESULT_CODE_SUCCESS;
}
};
final MediaController2 controller =
@@ -701,7 +703,7 @@
});
SessionCommand2 customCommand = new SessionCommand2("testNoInteraction", null);
- mRemoteSession2.sendCustomCommand(customCommand, null);
+ mRemoteSession2.broadcastCustomCommand(customCommand, null);
assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
setRunnableForOnCustomCommand(mController, null);
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test_copied.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test_copied.java
index c186b6c..14ea357 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test_copied.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaController2Test_copied.java
@@ -94,7 +94,7 @@
// assertTrue(mPlayer.mPlayCalled);
//
// // Test command from session service to controller.
-// mSession.sendCustomCommand(testCommand, null);
+// mSession.broadcastCustomCommand(testCommand, null);
// assertTrue(controllerLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
// }
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaSession2TestBase.java b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaSession2TestBase.java
index 60d7894b..b170a98 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaSession2TestBase.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/androidx/media/test/client/tests/MediaSession2TestBase.java
@@ -24,7 +24,6 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
-import android.os.ResultReceiver;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.CallSuper;
@@ -306,14 +305,14 @@
}
@Override
- public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
- Bundle args, ResultReceiver receiver) {
- mCallbackProxy.onCustomCommand(controller, command, args, receiver);
+ public MediaController2.ControllerResult onCustomCommand(MediaController2 controller,
+ SessionCommand2 command, Bundle args) {
synchronized (this) {
if (mOnCustomCommandRunnable != null) {
mOnCustomCommandRunnable.run();
}
}
+ return mCallbackProxy.onCustomCommand(controller, command, args);
}
@Override
@@ -323,8 +322,8 @@
}
@Override
- public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
- mCallbackProxy.onCustomLayoutChanged(controller, layout);
+ public int onSetCustomLayout(MediaController2 controller, List<CommandButton> layout) {
+ return mCallbackProxy.onSetCustomLayout(controller, layout);
}
@Override
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 60443542..8aa5815 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
@@ -31,9 +31,11 @@
import static androidx.media.test.lib.CommonConstants.KEY_PLAYLIST;
import static androidx.media.test.lib.CommonConstants.KEY_SPEED;
import static androidx.media.test.lib.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
-import static androidx.media.test.lib.MediaSession2Constants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
+import static androidx.media.test.lib.MediaSession2Constants
+ .TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
import static androidx.media.test.lib.MediaSession2Constants.TEST_GET_SESSION_ACTIVITY;
-import static androidx.media.test.lib.MediaSession2Constants.TEST_ON_PLAYLIST_METADATA_CHANGED_SESSION_SET_PLAYLIST;
+import static androidx.media.test.lib.MediaSession2Constants
+ .TEST_ON_PLAYLIST_METADATA_CHANGED_SESSION_SET_PLAYLIST;
import android.app.PendingIntent;
import android.app.Service;
@@ -41,7 +43,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.support.mediacompat.testlib.IRemoteMediaSession2;
import android.util.Log;
@@ -225,18 +226,18 @@
}
@Override
- public void sendCustomCommand(String sessionId, Bundle command, Bundle args)
+ public void broadcastCustomCommand(String sessionId, Bundle command, Bundle args)
throws RemoteException {
MediaSession2 session2 = mSession2Map.get(sessionId);
- session2.sendCustomCommand(SessionCommand2.fromBundle(command), args);
+ session2.broadcastCustomCommand(SessionCommand2.fromBundle(command), args);
}
@Override
- public void sendCustomCommand2(String sessionId, Bundle controller, Bundle command,
- Bundle args, ResultReceiver receiver) throws RemoteException {
+ public void sendCustomCommand(String sessionId, Bundle controller, Bundle command,
+ Bundle args) throws RemoteException {
MediaSession2 session2 = mSession2Map.get(sessionId);
ControllerInfo info = MediaTestUtils.getTestControllerInfo(session2);
- session2.sendCustomCommand(info, SessionCommand2.fromBundle(command), args, receiver);
+ session2.sendCustomCommand(info, SessionCommand2.fromBundle(command), args);
}
@Override
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 aa13ba3..f168d89 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
@@ -148,6 +148,22 @@
}
}
+ /**
+ * Client app will automatically create a playlist of size {@param size},
+ * and call setPlaylist() with the list. Each item's media ID will be its index.
+ *
+ * Note: This is introduced for testing large data transaction. It can prevent test helper
+ * classes from sending/receiving large data between them.
+ */
+ public void setPlaylistWithSize(int size, @Nullable MediaMetadata2 metadata) {
+ try {
+ mBinder.setPlaylistWithSize(mControllerId, size,
+ metadata == null ? null : metadata.toBundle());
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to call setPlaylistWithSize()");
+ }
+ }
+
public void setMediaItem(@NonNull MediaItem2 item) {
try {
mBinder.setMediaItem(mControllerId, item.toBundle());
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 b5a7eea..83b7b79 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
@@ -35,6 +35,7 @@
import androidx.media2.MediaSession2;
import androidx.media2.SessionCommandGroup2;
import androidx.media2.SessionPlayer2;
+import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -211,6 +212,25 @@
}
@Test
+ @LargeTest
+ public void testSetPlaylistByControllerWithLongPlaylist() throws InterruptedException {
+ final int listSize = 5000;
+ // Make client app to generate a long list, and call setPlaylist() with it.
+ mController2.setPlaylistWithSize(listSize, null /* metadata */);
+ assertTrue(mPlayer.mCountDownLatch.await(10, TimeUnit.SECONDS));
+
+ assertTrue(mPlayer.mSetPlaylistCalled);
+ assertNull(mPlayer.mMetadata);
+
+ assertNotNull(mPlayer.mPlaylist);
+ assertEquals(listSize, mPlayer.mPlaylist.size());
+ for (int i = 0; i < listSize; i++) {
+ // Each item's media ID will be same as its index.
+ assertEquals(Integer.toString(i), mPlayer.mPlaylist.get(i).getMediaId());
+ }
+ }
+
+ @Test
public void testUpdatePlaylistMetadataBySession() {
prepareLooper();
final MediaMetadata2 testMetadata = MediaTestUtils.createMetadata();
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 ff2094e..31610ba 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
@@ -34,6 +34,7 @@
void seekTo(String controllerId, long pos);
void setPlaybackSpeed(String controllerId, float speed);
void setPlaylist(String controllerId, in List<Bundle> list, in Bundle metadata);
+ void setPlaylistWithSize(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);
diff --git a/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSession2.aidl b/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSession2.aidl
index f028d6b..05232b0 100644
--- a/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSession2.aidl
+++ b/media/version-compat-tests/lib/src/main/aidl/android/support/mediacompat/testlib/IRemoteMediaSession2.aidl
@@ -29,9 +29,9 @@
ParcelImpl getToken(String sessionId);
Bundle getCompatToken(String sessionId);
void updatePlayer(String sessionId, in Bundle playerBundle);
- void sendCustomCommand(String sessionId, in Bundle command, in Bundle args);
- void sendCustomCommand2(String sessionId, in Bundle controller, in Bundle command,
- in Bundle args, in ResultReceiver receiver);
+ void broadcastCustomCommand(String sessionId, in Bundle command, in Bundle args);
+ void sendCustomCommand(String sessionId, in Bundle controller, in Bundle command,
+ in Bundle args);
void close(String sessionId);
void setAllowedCommands(String sessionId, in Bundle controller, in Bundle commands);
void notifyRoutesInfoChanged(String sessionId, in Bundle controller, in List<Bundle> routes);
diff --git a/media2/api/current.txt b/media2/api/current.txt
index 49c12cb..3c0964c 100644
--- a/media2/api/current.txt
+++ b/media2/api/current.txt
@@ -105,8 +105,7 @@
method public void onBufferingStateChanged(androidx.media2.MediaController2, androidx.media2.MediaItem2, int);
method public void onConnected(androidx.media2.MediaController2, androidx.media2.SessionCommandGroup2);
method public void onCurrentMediaItemChanged(androidx.media2.MediaController2, androidx.media2.MediaItem2);
- method public void onCustomCommand(androidx.media2.MediaController2, androidx.media2.SessionCommand2, android.os.Bundle, android.os.ResultReceiver);
- method public void onCustomLayoutChanged(androidx.media2.MediaController2, java.util.List<androidx.media2.MediaSession2.CommandButton>);
+ method public androidx.media2.MediaController2.ControllerResult onCustomCommand(androidx.media2.MediaController2, androidx.media2.SessionCommand2, android.os.Bundle);
method public void onDisconnected(androidx.media2.MediaController2);
method public void onPlaybackCompleted(androidx.media2.MediaController2);
method public void onPlaybackInfoChanged(androidx.media2.MediaController2, androidx.media2.MediaController2.PlaybackInfo);
@@ -116,10 +115,11 @@
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);
+ method public int onSetCustomLayout(androidx.media2.MediaController2, java.util.List<androidx.media2.MediaSession2.CommandButton>);
method public void onShuffleModeChanged(androidx.media2.MediaController2, int);
}
- public static class MediaController2.ControllerResult {
+ public static class MediaController2.ControllerResult implements androidx.versionedparcelable.VersionedParcelable {
ctor public MediaController2.ControllerResult(int, android.os.Bundle);
method public long getCompletionTime();
method public android.os.Bundle getCustomCommandResult();
@@ -282,15 +282,15 @@
}
public class MediaSession2 implements java.lang.AutoCloseable {
+ method public void broadcastCustomCommand(androidx.media2.SessionCommand2, android.os.Bundle);
method public void close();
method public java.util.List<androidx.media2.MediaSession2.ControllerInfo> getConnectedControllers();
method public java.lang.String getId();
method public androidx.media2.SessionPlayer2 getPlayer();
method public androidx.media2.SessionToken2 getToken();
- method public void sendCustomCommand(androidx.media2.SessionCommand2, android.os.Bundle);
- method public void sendCustomCommand(androidx.media2.MediaSession2.ControllerInfo, androidx.media2.SessionCommand2, android.os.Bundle, android.os.ResultReceiver);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaSession2.SessionResult> sendCustomCommand(androidx.media2.MediaSession2.ControllerInfo, androidx.media2.SessionCommand2, android.os.Bundle);
method public void setAllowedCommands(androidx.media2.MediaSession2.ControllerInfo, androidx.media2.SessionCommandGroup2);
- method public void setCustomLayout(androidx.media2.MediaSession2.ControllerInfo, java.util.List<androidx.media2.MediaSession2.CommandButton>);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.MediaSession2.SessionResult> setCustomLayout(androidx.media2.MediaSession2.ControllerInfo, java.util.List<androidx.media2.MediaSession2.CommandButton>);
method public void updatePlayer(androidx.media2.SessionPlayer2);
}
diff --git a/media2/src/androidTest/java/androidx/media2/MediaController2Test.java b/media2/src/androidTest/java/androidx/media2/MediaController2Test.java
index 84fc933..89c045f 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaController2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaController2Test.java
@@ -36,7 +36,6 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
-import android.os.ResultReceiver;
import androidx.annotation.NonNull;
import androidx.media.AudioAttributesCompat;
@@ -1331,11 +1330,12 @@
mController = createController(TestUtils.getServiceToken(mContext, id), true,
new ControllerCallback() {
@Override
- public void onCustomCommand(MediaController2 controller,
- SessionCommand2 command, Bundle args, ResultReceiver receiver) {
+ public MediaController2.ControllerResult onCustomCommand(
+ MediaController2 controller, SessionCommand2 command, Bundle args) {
if (testCommand.equals(command)) {
controllerLatch.countDown();
}
+ return new MediaController2.ControllerResult(RESULT_CODE_SUCCESS);
}
}
);
@@ -1347,7 +1347,7 @@
assertTrue(mPlayer.mPlayCalled);
// Test command from session service to controller.
- mSession.sendCustomCommand(testCommand, null);
+ mSession.broadcastCustomCommand(testCommand, null);
assertTrue(controllerLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@@ -1553,7 +1553,7 @@
}
});
SessionCommand2 customCommand = new SessionCommand2("testNoInteraction", null);
- mSession.sendCustomCommand(customCommand, null);
+ mSession.broadcastCustomCommand(customCommand, null);
assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
setRunnableForOnCustomCommand(mController, null);
}
diff --git a/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java b/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java
index b19e0d5..aa802b0 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaSession2Test.java
@@ -36,7 +36,6 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
-import android.os.ResultReceiver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -510,8 +509,8 @@
final ControllerCallback callback = new ControllerCallback() {
@Override
- public void onCustomLayoutChanged(MediaController2 controller2,
- List<CommandButton> layout) {
+ public int onSetCustomLayout(
+ MediaController2 controller2, List<CommandButton> layout) {
assertEquals(customLayout.size(), layout.size());
for (int i = 0; i < layout.size(); i++) {
assertEquals(customLayout.get(i).getCommand(), layout.get(i).getCommand());
@@ -519,6 +518,7 @@
layout.get(i).getDisplayName());
}
latch.countDown();
+ return RESULT_CODE_SUCCESS;
}
};
MediaController2 controller = createController(session.getToken(), true, callback);
@@ -572,23 +572,23 @@
final CountDownLatch latch = new CountDownLatch(2);
final ControllerCallback callback = new ControllerCallback() {
@Override
- public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
- Bundle args, ResultReceiver receiver) {
+ public MediaController2.ControllerResult onCustomCommand(MediaController2 controller,
+ SessionCommand2 command, Bundle args) {
assertEquals(testCommand, command);
assertTrue(TestUtils.equals(testArgs, args));
- assertNull(receiver);
latch.countDown();
+ return new MediaController2.ControllerResult(RESULT_CODE_SUCCESS);
}
};
final MediaController2 controller =
createController(mSession.getToken(), true, callback);
// TODO(jaewan): Test with multiple controllers
- mSession.sendCustomCommand(testCommand, testArgs);
+ mSession.broadcastCustomCommand(testCommand, testArgs);
ControllerInfo controllerInfo = getTestControllerInfo();
assertNotNull(controllerInfo);
// TODO(jaewan): Test receivers as well.
- mSession.sendCustomCommand(controllerInfo, testCommand, testArgs, null);
+ mSession.sendCustomCommand(controllerInfo, testCommand, testArgs);
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
diff --git a/media2/src/androidTest/java/androidx/media2/MockBrowserCallback.java b/media2/src/androidTest/java/androidx/media2/MockBrowserCallback.java
index c9036fd..8230299 100644
--- a/media2/src/androidTest/java/androidx/media2/MockBrowserCallback.java
+++ b/media2/src/androidTest/java/androidx/media2/MockBrowserCallback.java
@@ -20,7 +20,6 @@
import static junit.framework.Assert.assertTrue;
import android.os.Bundle;
-import android.os.ResultReceiver;
import androidx.annotation.CallSuper;
import androidx.annotation.GuardedBy;
@@ -97,19 +96,19 @@
}
@Override
- public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
- Bundle args, ResultReceiver receiver) {
- mCallbackProxy.onCustomCommand(controller, command, args, receiver);
+ public MediaController2.ControllerResult onCustomCommand(
+ MediaController2 controller, SessionCommand2 command, Bundle args) {
synchronized (this) {
if (mOnCustomCommandRunnable != null) {
mOnCustomCommandRunnable.run();
}
}
+ return mCallbackProxy.onCustomCommand(controller, command, args);
}
@Override
- public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
- mCallbackProxy.onCustomLayoutChanged(controller, layout);
+ public int onSetCustomLayout(MediaController2 controller, List<CommandButton> layout) {
+ return mCallbackProxy.onSetCustomLayout(controller, layout);
}
@Override
diff --git a/media2/src/androidTest/java/androidx/media2/MockControllerCallback.java b/media2/src/androidTest/java/androidx/media2/MockControllerCallback.java
index 2fa5678..181a30d 100644
--- a/media2/src/androidTest/java/androidx/media2/MockControllerCallback.java
+++ b/media2/src/androidTest/java/androidx/media2/MockControllerCallback.java
@@ -20,7 +20,6 @@
import static junit.framework.Assert.assertTrue;
import android.os.Bundle;
-import android.os.ResultReceiver;
import androidx.annotation.CallSuper;
import androidx.annotation.GuardedBy;
@@ -91,14 +90,14 @@
}
@Override
- public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
- Bundle args, ResultReceiver receiver) {
- mCallbackProxy.onCustomCommand(controller, command, args, receiver);
+ public MediaController2.ControllerResult onCustomCommand(MediaController2 controller,
+ SessionCommand2 command, Bundle args) {
synchronized (this) {
if (mOnCustomCommandRunnable != null) {
mOnCustomCommandRunnable.run();
}
}
+ return mCallbackProxy.onCustomCommand(controller, command, args);
}
@Override
@@ -108,8 +107,8 @@
}
@Override
- public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
- mCallbackProxy.onCustomLayoutChanged(controller, layout);
+ public int onSetCustomLayout(MediaController2 controller, List<CommandButton> layout) {
+ return mCallbackProxy.onSetCustomLayout(controller, layout);
}
@Override
diff --git a/media2/src/main/aidl/androidx/media2/IMediaController2.aidl b/media2/src/main/aidl/androidx/media2/IMediaController2.aidl
index 88d00de..463fe29 100644
--- a/media2/src/main/aidl/androidx/media2/IMediaController2.aidl
+++ b/media2/src/main/aidl/androidx/media2/IMediaController2.aidl
@@ -18,7 +18,6 @@
import android.app.PendingIntent;
import android.os.Bundle;
-import android.os.ResultReceiver;
import androidx.media2.IMediaSession2;
import androidx.versionedparcelable.ParcelImpl;
@@ -31,8 +30,6 @@
* @hide
*/
oneway interface IMediaController2 {
- void onSessionResult(int seq, in ParcelImpl sessionResult) = 24;
-
void onCurrentMediaItemChanged(in ParcelImpl item) = 0;
void onPlayerStateChanged(long eventTimeMs, long positionMs, int state) = 1;
void onPlaybackSpeedChanged(long eventTimeMs, long positionMs, float speed) = 2;
@@ -52,10 +49,12 @@
in List<ParcelImpl> playlist, in PendingIntent sessionActivity) = 12;
void onDisconnected() = 13;
- void onCustomLayoutChanged(in List<ParcelImpl> commandButtonlist) = 14;
+ void onSetCustomLayout(int seq, in List<ParcelImpl> commandButtonlist) = 14;
void onAllowedCommandsChanged(in ParcelImpl commandGroup) = 15;
- void onCustomCommand(in ParcelImpl command, in Bundle args, in ResultReceiver receiver) = 16;
+ void onCustomCommand(int seq, in ParcelImpl command, in Bundle args) = 16;
+
+ void onSessionResult(int seq, in ParcelImpl sessionResult) = 24;
//////////////////////////////////////////////////////////////////////////////////////////////
// Browser sepcific
diff --git a/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl b/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl
index e23473d..4b43cdc 100644
--- a/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl
+++ b/media2/src/main/aidl/androidx/media2/IMediaSession2.aidl
@@ -17,10 +17,10 @@
package androidx.media2;
import android.os.Bundle;
-import android.os.ResultReceiver;
import android.net.Uri;
import androidx.media2.IMediaController2;
+import androidx.media2.ParcelImplListSlice;
import androidx.versionedparcelable.ParcelImpl;
/**
@@ -55,7 +55,7 @@
void setRating(IMediaController2 caller, int seq, String mediaId, in ParcelImpl rating2) = 18;
void setPlaybackSpeed(IMediaController2 caller, int seq, float speed) = 19;
- void setPlaylist(IMediaController2 caller, int seq, in List<ParcelImpl> playlist,
+ 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;
@@ -74,6 +74,9 @@
void unsubscribeRoutesInfo(IMediaController2 caller, int seq) = 31;
void selectRoute(IMediaController2 caller, int seq, in Bundle route) = 32;
+ void onControllerResult(IMediaController2 caller, int seq,
+ in ParcelImpl controllerResult) = 41;
+
//////////////////////////////////////////////////////////////////////////////////////////////
// library service specific
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -86,5 +89,5 @@
in Bundle extras) = 37;
void subscribe(IMediaController2 caller, String parentId, in Bundle extras) = 38;
void unsubscribe(IMediaController2 caller, String parentId) = 39;
- // Next Id : 41
+ // Next Id : 42
}
diff --git a/media2/src/main/aidl/androidx/media2/ParcelImplListSlice.aidl b/media2/src/main/aidl/androidx/media2/ParcelImplListSlice.aidl
new file mode 100644
index 0000000..a69c19b
--- /dev/null
+++ b/media2/src/main/aidl/androidx/media2/ParcelImplListSlice.aidl
@@ -0,0 +1,3 @@
+package androidx.media2;
+
+parcelable ParcelImplListSlice;
diff --git a/media2/src/main/java/androidx/media2/ConnectedControllersManager.java b/media2/src/main/java/androidx/media2/ConnectedControllersManager.java
index d61a1b8..9174e1b5e 100644
--- a/media2/src/main/java/androidx/media2/ConnectedControllersManager.java
+++ b/media2/src/main/java/androidx/media2/ConnectedControllersManager.java
@@ -16,9 +16,12 @@
package androidx.media2;
+import static androidx.media2.MediaSession2.SessionResult.RESULT_CODE_SKIPPED;
+
import android.util.Log;
import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import androidx.media.MediaSessionManager.RemoteUserInfo;
import androidx.media2.MediaSession2.ControllerInfo;
@@ -40,6 +43,9 @@
private final ArrayMap<ControllerInfo, SessionCommandGroup2> mAllowedCommandGroupMap =
new ArrayMap<>();
@GuardedBy("mLock")
+ private final ArrayMap<ControllerInfo, SequencedFutureManager>
+ mControllerToSequencedFutureManager = new ArrayMap<>();
+ @GuardedBy("mLock")
private final ArrayMap<T, ControllerInfo> mControllers = new ArrayMap<>();
@GuardedBy("mLock")
private final ArrayMap<ControllerInfo, T> mKeys = new ArrayMap<>();
@@ -60,6 +66,8 @@
}
synchronized (mLock) {
mAllowedCommandGroupMap.put(controller, commands);
+ mControllerToSequencedFutureManager.put(controller, new SequencedFutureManager(
+ new MediaSession2.SessionResult(RESULT_CODE_SKIPPED)));
mControllers.put(key, controller);
mKeys.put(controller, key);
}
@@ -85,10 +93,15 @@
return;
}
final ControllerInfo controller;
+ final SequencedFutureManager manager;
synchronized (mLock) {
controller = mControllers.remove(key);
mKeys.remove(controller);
mAllowedCommandGroupMap.remove(controller);
+ manager = mControllerToSequencedFutureManager.remove(controller);
+ }
+ if (manager != null) {
+ manager.close();
}
notifyDisconnected(controller);
}
@@ -97,10 +110,15 @@
if (controller == null) {
return;
}
+ final SequencedFutureManager manager;
synchronized (mLock) {
T key = mKeys.remove(controller);
mControllers.remove(key);
mAllowedCommandGroupMap.remove(controller);
+ manager = mControllerToSequencedFutureManager.remove(controller);
+ }
+ if (manager != null) {
+ manager.close();
}
notifyDisconnected(controller);
}
@@ -136,7 +154,38 @@
public boolean isConnected(ControllerInfo controller) {
synchronized (mLock) {
- return mKeys.get(controller) != null;
+ return controller != null && mKeys.get(controller) != null;
+ }
+ }
+
+ /**
+ * Gets the sequenced future manager.
+ *
+ * @param controller controller
+ * @return sequenced future manager. Can be {@code null} if the controller was null or
+ * disconencted.
+ */
+ public @Nullable SequencedFutureManager getSequencedFutureManager(
+ @Nullable ControllerInfo controller) {
+ if (controller == null) {
+ return null;
+ }
+ synchronized (mLock) {
+ return isConnected(controller)
+ ? mControllerToSequencedFutureManager.get(controller) : null;
+ }
+ }
+
+ /**
+ * Gets the sequenced future manager.
+ *
+ * @param key key
+ * @return sequenced future manager. Can be {@code null} if the controller was null or
+ * disconencted.
+ */
+ public @Nullable SequencedFutureManager getSequencedFutureManager(@Nullable T key) {
+ synchronized (mLock) {
+ return getSequencedFutureManager(getController(key));
}
}
diff --git a/media2/src/main/java/androidx/media2/MediaController2.java b/media2/src/main/java/androidx/media2/MediaController2.java
index 4a36b8d..d1ff028 100644
--- a/media2/src/main/java/androidx/media2/MediaController2.java
+++ b/media2/src/main/java/androidx/media2/MediaController2.java
@@ -1110,12 +1110,16 @@
* <p>
* Can be called before {@link #onConnected(MediaController2, SessionCommandGroup2)}
* is called.
+ * <p>
+ * Default implementation returns {@link ControllerResult#RESULT_CODE_NOT_SUPPORTED}.
*
* @param controller the controller for this event
* @param layout
*/
- public void onCustomLayoutChanged(@NonNull MediaController2 controller,
- @NonNull List<CommandButton> layout) { }
+ public @ControllerResult.ResultCode int onSetCustomLayout(
+ @NonNull MediaController2 controller, @NonNull List<CommandButton> layout) {
+ return ControllerResult.RESULT_CODE_NOT_SUPPORTED;
+ }
/**
* Called when the session has changed anything related with the {@link PlaybackInfo}.
@@ -1150,16 +1154,21 @@
@NonNull SessionCommandGroup2 commands) { }
/**
- * Called when the session sent a custom command.
+ * Called when the session sent a custom command. Returns a {@link ControllerResult} for
+ * session to get notification back. If the {@code null} is returned,
+ * {@link ControllerResult#RESULT_CODE_UNKNOWN_ERROR} will be returned.
+ * <p>
+ * Default implementation returns {@link ControllerResult#RESULT_CODE_NOT_SUPPORTED}.
*
* @param controller the controller for this event
* @param command
* @param args
- * @param receiver
+ * @return result of handling custom command
*/
- public void onCustomCommand(@NonNull MediaController2 controller,
- @NonNull SessionCommand2 command, @Nullable Bundle args,
- @Nullable ResultReceiver receiver) { }
+ public @NonNull ControllerResult onCustomCommand(@NonNull MediaController2 controller,
+ @NonNull SessionCommand2 command, @Nullable Bundle args) {
+ return new ControllerResult(ControllerResult.RESULT_CODE_NOT_SUPPORTED);
+ }
/**
* Called when the player state is changed.
@@ -1423,7 +1432,7 @@
* Result class to be used with {@link ListenableFuture} for asynchronous calls.
*/
@VersionedParcelize
- public static class ControllerResult implements RemoteResult2 {
+ public static class ControllerResult implements RemoteResult2, VersionedParcelable {
/**
* Result code representing that the command is successfully completed.
* <p>
@@ -1467,6 +1476,13 @@
@ParcelField(4)
MediaItem2 mItem;
+ /**
+ * Constructor to be used by
+ * {@link ControllerCallback#onCustomCommand(MediaController2, SessionCommand2, Bundle)}.
+ *
+ * @param resultCode result code
+ * @param customCommandResult custom command result
+ */
public ControllerResult(@ResultCode int resultCode, @Nullable Bundle customCommandResult) {
this(resultCode, customCommandResult, null);
}
@@ -1477,7 +1493,7 @@
}
ControllerResult(@ResultCode int resultCode) {
- this(resultCode, null);
+ this(resultCode, null, null);
}
ControllerResult(@ResultCode int resultCode, @Nullable Bundle customCommandResult,
diff --git a/media2/src/main/java/androidx/media2/MediaController2ImplBase.java b/media2/src/main/java/androidx/media2/MediaController2ImplBase.java
index 3fb680d..6aadd37 100644
--- a/media2/src/main/java/androidx/media2/MediaController2ImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaController2ImplBase.java
@@ -19,6 +19,7 @@
import static androidx.media2.MediaController2.ControllerResult.RESULT_CODE_DISCONNECTED;
import static androidx.media2.MediaController2.ControllerResult.RESULT_CODE_PERMISSION_DENIED;
import static androidx.media2.MediaController2.ControllerResult.RESULT_CODE_SKIPPED;
+import static androidx.media2.MediaController2.ControllerResult.RESULT_CODE_UNKNOWN_ERROR;
import static androidx.media2.MediaMetadata2.METADATA_KEY_DURATION;
import static androidx.media2.SessionCommand2.COMMAND_CODE_CUSTOM;
import static androidx.media2.SessionCommand2.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM;
@@ -64,7 +65,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.os.SystemClock;
import android.support.v4.media.MediaBrowserCompat;
import android.util.Log;
@@ -72,7 +72,6 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.concurrent.futures.ResolvableFuture;
import androidx.media2.MediaController2.ControllerCallback;
import androidx.media2.MediaController2.ControllerResult;
import androidx.media2.MediaController2.MediaController2Impl;
@@ -92,6 +91,8 @@
import java.util.concurrent.Executor;
class MediaController2ImplBase implements MediaController2Impl {
+ private static final boolean THROW_EXCEPTION_FOR_NULL_RESULT = true;
+
static final String TAG = "MC2ImplBase";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -275,9 +276,7 @@
// Don't create Future with SequencedFutureManager.
// Otherwise session would receive discontinued sequence number, and it would make
// future work item 'keeping call sequence when session execute commands' impossible.
- final ResolvableFuture<ControllerResult> result = ResolvableFuture.create();
- result.set(new ControllerResult(RESULT_CODE_PERMISSION_DENIED));
- return result;
+ return ControllerResult.createFutureWithResult(RESULT_CODE_PERMISSION_DENIED);
}
}
@@ -575,7 +574,8 @@
@Override
public void run(IMediaSession2 iSession2, int seq) throws RemoteException {
iSession2.setPlaylist(mControllerStub, seq,
- MediaUtils2.convertMediaItem2ListToParcelImplList(list),
+ new ParcelImplListSlice(
+ MediaUtils2.convertMediaItem2ListToParcelImplList(list)),
(metadata == null) ? null : metadata.toBundle());
}
});
@@ -1103,15 +1103,40 @@
}
}
- void onCustomCommand(final SessionCommand2 command, final Bundle args,
- final ResultReceiver receiver) {
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void sendControllerResult(int seq, @NonNull ControllerResult result) {
+ final IMediaSession2 iSession2;
+ synchronized (mLock) {
+ iSession2 = mISession2;
+ }
+ if (iSession2 == null) {
+ return;
+ }
+ try {
+ iSession2.onControllerResult(mControllerStub, seq,
+ (ParcelImpl) ParcelUtils.toParcelable(result));
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error in sending");
+ }
+ }
+
+ void onCustomCommand(final int seq, final SessionCommand2 command, final Bundle args) {
if (DEBUG) {
- Log.d(TAG, "onCustomCommand cmd=" + command);
+ Log.d(TAG, "onCustomCommand cmd=" + command.getCustomCommand());
}
mCallbackExecutor.execute(new Runnable() {
@Override
public void run() {
- mCallback.onCustomCommand(mInstance, command, args, receiver);
+ ControllerResult result = mCallback.onCustomCommand(mInstance, command, args);
+ if (result == null) {
+ if (THROW_EXCEPTION_FOR_NULL_RESULT) {
+ throw new RuntimeException("ControllerCallback#onCustomCommand() has"
+ + " returned null, command=" + command.getCustomCommand());
+ } else {
+ result = new ControllerResult(RESULT_CODE_UNKNOWN_ERROR);
+ }
+ }
+ sendControllerResult(seq, result);
}
});
}
@@ -1125,11 +1150,13 @@
});
}
- void onCustomLayoutChanged(final List<MediaSession2.CommandButton> layout) {
+ void onSetCustomLayout(final int seq, final List<MediaSession2.CommandButton> layout) {
mCallbackExecutor.execute(new Runnable() {
@Override
public void run() {
- mCallback.onCustomLayoutChanged(mInstance, layout);
+ int resultCode = mCallback.onSetCustomLayout(mInstance, layout);
+ ControllerResult result = new ControllerResult(resultCode);
+ sendControllerResult(seq, result);
}
});
}
diff --git a/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java b/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java
index c40f0d0..f58ce66 100644
--- a/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java
+++ b/media2/src/main/java/androidx/media2/MediaController2ImplLegacy.java
@@ -1067,8 +1067,8 @@
mCallbackExecutor.execute(new Runnable() {
@Override
public void run() {
- mCallback.onCustomCommand(mInstance, new SessionCommand2(event, null), extras,
- null);
+ // Ignore return because legacy session cannot get result back.
+ mCallback.onCustomCommand(mInstance, new SessionCommand2(event, null), extras);
}
});
}
@@ -1199,7 +1199,7 @@
public void run() {
mCallback.onCustomCommand(mInstance,
new SessionCommand2(SESSION_COMMAND_ON_EXTRA_CHANGED, null),
- extras, null);
+ extras);
}
});
}
@@ -1221,7 +1221,7 @@
public void run() {
mCallback.onCustomCommand(mInstance,
new SessionCommand2(SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED,
- null), null, null);
+ null), null);
}
});
}
diff --git a/media2/src/main/java/androidx/media2/MediaController2Stub.java b/media2/src/main/java/androidx/media2/MediaController2Stub.java
index 9644a40..36e67b9 100644
--- a/media2/src/main/java/androidx/media2/MediaController2Stub.java
+++ b/media2/src/main/java/androidx/media2/MediaController2Stub.java
@@ -18,7 +18,6 @@
import android.app.PendingIntent;
import android.os.Bundle;
-import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
@@ -272,9 +271,9 @@
}
@Override
- public void onCustomLayoutChanged(List<ParcelImpl> commandButtonlist) {
- if (commandButtonlist == null) {
- Log.w(TAG, "onCustomLayoutChanged(): Ignoring null commandButtonlist");
+ public void onSetCustomLayout(int seq, List<ParcelImpl> commandButtonList) {
+ if (commandButtonList == null) {
+ Log.w(TAG, "setCustomLayout(): Ignoring null commandButtonList");
return;
}
final MediaController2ImplBase controller;
@@ -289,13 +288,13 @@
return;
}
List<CommandButton> layout = new ArrayList<>();
- for (int i = 0; i < commandButtonlist.size(); i++) {
- CommandButton button = ParcelUtils.fromParcelable(commandButtonlist.get(i));
+ for (int i = 0; i < commandButtonList.size(); i++) {
+ CommandButton button = ParcelUtils.fromParcelable(commandButtonList.get(i));
if (button != null) {
layout.add(button);
}
}
- controller.onCustomLayoutChanged(layout);
+ controller.onSetCustomLayout(seq, layout);
}
@Override
@@ -320,7 +319,7 @@
}
@Override
- public void onCustomCommand(ParcelImpl commandParcel, Bundle args, ResultReceiver receiver) {
+ public void onCustomCommand(int seq, ParcelImpl commandParcel, Bundle args) {
final MediaController2ImplBase controller;
try {
controller = getController();
@@ -330,10 +329,10 @@
}
SessionCommand2 command = ParcelUtils.fromParcelable(commandParcel);
if (command == null) {
- Log.w(TAG, "onCustomCommand(): Ignoring null command");
+ Log.w(TAG, "sendCustomCommand(): Ignoring null command");
return;
}
- controller.onCustomCommand(command, args, receiver);
+ controller.onCustomCommand(seq, command, args);
}
////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java b/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java
index e528e6ec..17fdc10 100644
--- a/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java
+++ b/media2/src/main/java/androidx/media2/MediaLibraryService2LegacyStub.java
@@ -24,7 +24,6 @@
import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.session.MediaSessionCompat;
@@ -334,7 +333,7 @@
}
@Override
- final void onCustomLayoutChanged(List<CommandButton> layout) throws RemoteException {
+ final void setCustomLayout(int seq, List<CommandButton> layout) throws RemoteException {
// No-op. BrowserCompat doesn't understand Controller features.
}
@@ -349,7 +348,7 @@
}
@Override
- final void onCustomCommand(SessionCommand2 command, Bundle args, ResultReceiver receiver)
+ final void sendCustomCommand(int seq, SessionCommand2 command, Bundle args)
throws RemoteException {
// No-op. BrowserCompat doesn't understand Controller features.
}
diff --git a/media2/src/main/java/androidx/media2/MediaSession2.java b/media2/src/main/java/androidx/media2/MediaSession2.java
index 8acfbe8..c6b1ec2 100644
--- a/media2/src/main/java/androidx/media2/MediaSession2.java
+++ b/media2/src/main/java/androidx/media2/MediaSession2.java
@@ -30,7 +30,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.os.SystemClock;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
@@ -40,9 +39,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.concurrent.futures.ResolvableFuture;
import androidx.core.content.ContextCompat;
import androidx.core.util.ObjectsCompat;
import androidx.media.MediaSessionManager.RemoteUserInfo;
+import androidx.media2.MediaController2.ControllerResult;
import androidx.media2.MediaController2.PlaybackInfo;
import androidx.media2.MediaSession2.SessionResult.ResultCode;
import androidx.media2.SessionPlayer2.BuffState;
@@ -128,7 +129,7 @@
* {@link SessionPlayer2#PLAYER_STATE_PLAYING}</li>
* <li>{@link SessionPlayer2#play()} otherwise</li></ul>
* <li>For a double tap, {@link SessionPlayer2#skipToNextPlaylistItem()}</li></ul></td>
- * </td>
+ * </tr>
* </table>
* @see MediaSessionService2
*/
@@ -235,15 +236,23 @@
* Sets ordered list of {@link CommandButton} for controllers to build UI with it.
* <p>
* It's up to controller's decision how to represent the layout in its own UI.
- * Here's the same way
- * (layout[i] means a CommandButton at index i in the given list)
- * For 5 icons row
- * layout[3] layout[1] layout[0] layout[2] layout[4]
- * For 3 icons row
- * layout[1] layout[0] layout[2]
- * For 5 icons row with overflow icon (can show +5 extra buttons with overflow button)
- * expanded row: layout[5] layout[6] layout[7] layout[8] layout[9]
- * main row: layout[3] layout[1] layout[0] layout[2] layout[4]
+ * Here are some examples.
+ * <p>
+ * Note: <code>layout[i]</code> means a CommandButton at index i in the given list
+ * <table>
+ * <tr><th>Controller UX layout</th><th>Layout example</th></tr>
+ * <tr><td>Row with 3 icons</td>
+ * <td><code>layout[1]</code> <code>layout[0]</code> <code>layout[2]</code></td></tr>
+ * <tr><td>Row with 5 icons</td>
+ * <td><code>layout[3]</code> <code>layout[1]</code> <code>layout[0]</code>
+ * <code>layout[2]</code> <code>layout[4]</code></td></tr>
+ * <tr><td rowspan=2>Row with 5 icons and an overflow icon, and another expandable row with 5
+ * extra icons</td>
+ * <td><code>layout[3]</code> <code>layout[1]</code> <code>layout[0]</code>
+ * <code>layout[2]</code> <code>layout[4]</code></td></tr>
+ * <tr><td><code>layout[3]</code> <code>layout[1]</code> <code>layout[0]</code>
+ * <code>layout[2]</code> <code>layout[4]</code></td></tr>
+ * </table>
* <p>
* This API can be called in the
* {@link SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
@@ -251,13 +260,18 @@
* @param controller controller to specify layout.
* @param layout ordered list of layout.
*/
- public void setCustomLayout(@NonNull ControllerInfo controller,
- @NonNull List<CommandButton> layout) {
- mImpl.setCustomLayout(controller, layout);
+ public @NonNull ListenableFuture<SessionResult> setCustomLayout(
+ @NonNull ControllerInfo controller, @NonNull List<CommandButton> layout) {
+ return mImpl.setCustomLayout(controller, layout);
}
/**
- * Set the new allowed command group for the controller
+ * Sets the new allowed command group for the controller.
+ * <p>
+ * This is synchronous call. Changes in the allowed commands take effect immediately regardless
+ * of the controller notified about the change through
+ * {@link MediaController2.ControllerCallback
+ * #onAllowedCommandsChanged(MediaController2, SessionCommandGroup2)}
*
* @param controller controller to change allowed commands
* @param commands new allowed commands
@@ -268,13 +282,17 @@
}
/**
- * Send custom command to all connected controllers.
+ * Broadcasts custom command to all connected controllers.
+ * <p>
+ * This is synchronous call and doesn't wait for result from the controller. Use
+ * {@link #sendCustomCommand(ControllerInfo, SessionCommand2, Bundle)} for getting the result.
*
* @param command a command
* @param args optional argument
+ * @see #sendCustomCommand(ControllerInfo, SessionCommand2, Bundle)
*/
- public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args) {
- mImpl.sendCustomCommand(command, args);
+ public void broadcastCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args) {
+ mImpl.broadcastCustomCommand(command, args);
}
/**
@@ -282,12 +300,12 @@
*
* @param command a command
* @param args optional argument
- * @param receiver result receiver for the session
+ * @see #broadcastCustomCommand(SessionCommand2, Bundle)
*/
- public void sendCustomCommand(@NonNull ControllerInfo controller,
- @NonNull SessionCommand2 command, @Nullable Bundle args,
- @Nullable ResultReceiver receiver) {
- mImpl.sendCustomCommand(controller, command, args, receiver);
+ public @NonNull ListenableFuture<SessionResult> sendCustomCommand(
+ @NonNull ControllerInfo controller, @NonNull SessionCommand2 command,
+ @Nullable Bundle args) {
+ return mImpl.sendCustomCommand(controller, command, args);
}
/**
@@ -1097,18 +1115,19 @@
}
}
+ // TODO: Drop 'Cb' from the name.
abstract static class ControllerCb {
abstract void onPlayerResult(int seq, PlayerResult result) throws RemoteException;
abstract void onSessionResult(int seq, SessionResult result) throws RemoteException;
// Mostly matched with the methods in MediaController2.ControllerCallback
- abstract void onCustomLayoutChanged(@NonNull List<CommandButton> layout)
+ abstract void setCustomLayout(int seq, @NonNull List<CommandButton> layout)
throws RemoteException;
+ abstract void sendCustomCommand(int seq, @NonNull SessionCommand2 command,
+ @Nullable Bundle args) throws RemoteException;
abstract void onPlaybackInfoChanged(@NonNull PlaybackInfo info) throws RemoteException;
abstract void onAllowedCommandsChanged(@NonNull SessionCommandGroup2 commands)
throws RemoteException;
- abstract void onCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args,
- @Nullable ResultReceiver receiver) throws RemoteException;
abstract void onPlayerStateChanged(long eventTimeMs, long positionMs, int playerState)
throws RemoteException;
abstract void onPlaybackSpeedChanged(long eventTimeMs, long positionMs, float speed)
@@ -1155,14 +1174,13 @@
@NonNull List<ControllerInfo> getConnectedControllers();
boolean isConnected(@NonNull ControllerInfo controller);
- void setCustomLayout(@NonNull ControllerInfo controller,
+ ListenableFuture<SessionResult> setCustomLayout(@NonNull ControllerInfo controller,
@NonNull List<CommandButton> layout);
void setAllowedCommands(@NonNull ControllerInfo controller,
@NonNull SessionCommandGroup2 commands);
- void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args);
- void sendCustomCommand(@NonNull ControllerInfo controller,
- @NonNull SessionCommand2 command, @Nullable Bundle args,
- @Nullable ResultReceiver receiver);
+ void broadcastCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args);
+ ListenableFuture<SessionResult> sendCustomCommand(@NonNull ControllerInfo controller,
+ @NonNull SessionCommand2 command, @Nullable Bundle args);
void notifyRoutesInfoChanged(@NonNull ControllerInfo controller,
@Nullable List<Bundle> routes);
@@ -1355,6 +1373,20 @@
result.getCompletionTime());
}
+ static @Nullable SessionResult from(@Nullable ControllerResult result) {
+ if (result == null) {
+ return null;
+ }
+ return new SessionResult(result.getResultCode(), result.getCustomCommandResult(),
+ result.getMediaItem(), result.getCompletionTime());
+ }
+
+ static ListenableFuture<SessionResult> createFutureWithResult(@ResultCode int resultCode) {
+ ResolvableFuture<SessionResult> result = ResolvableFuture.create();
+ result.set(new SessionResult(resultCode));
+ return result;
+ }
+
/**
* Gets the result code.
*
@@ -1382,11 +1414,12 @@
}
/**
- * Gets the result of {@link #sendCustomCommand(SessionCommand2, Bundle)}. This is only
- * valid when it's returned by the {@link #sendCustomCommand(SessionCommand2, Bundle)} and
- * will be {@code null} otherwise.
+ * Gets the result of {@link #sendCustomCommand(ControllerInfo, SessionCommand2, Bundle)}.
+ * This is only valid when it's returned by the
+ * {@link #sendCustomCommand(ControllerInfo, SessionCommand2, Bundle)} and will be
+ * {@code null} otherwise.
*
- * @see #sendCustomCommand(SessionCommand2, Bundle)
+ * @see #sendCustomCommand(ControllerInfo, SessionCommand2, Bundle)
* @return result of send custom command
*/
public @Nullable Bundle getCustomCommandResult() {
diff --git a/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java b/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java
index fd6178a..9380f12 100644
--- a/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaSession2ImplBase.java
@@ -19,7 +19,10 @@
import static androidx.media2.MediaSession2.ControllerCb;
import static androidx.media2.MediaSession2.ControllerInfo;
import static androidx.media2.MediaSession2.SessionCallback;
+import static androidx.media2.MediaSession2.SessionResult.RESULT_CODE_DISCONNECTED;
import static androidx.media2.MediaSession2.SessionResult.RESULT_CODE_INVALID_STATE;
+import static androidx.media2.MediaSession2.SessionResult.RESULT_CODE_SUCCESS;
+import static androidx.media2.MediaSession2.SessionResult.RESULT_CODE_UNKNOWN_ERROR;
import static androidx.media2.MediaUtils2.DIRECT_EXECUTOR;
import static androidx.media2.SessionPlayer2.PLAYER_STATE_IDLE;
import static androidx.media2.SessionPlayer2.UNKNOWN_TIME;
@@ -38,7 +41,6 @@
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.os.SystemClock;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.Token;
@@ -56,6 +58,8 @@
import androidx.media.VolumeProviderCompat;
import androidx.media2.MediaController2.PlaybackInfo;
import androidx.media2.MediaSession2.MediaSession2Impl;
+import androidx.media2.MediaSession2.SessionResult;
+import androidx.media2.SequencedFutureManager.SequencedFuture;
import androidx.media2.SessionPlayer2.PlayerResult;
import com.google.common.util.concurrent.ListenableFuture;
@@ -329,6 +333,9 @@
@Override
public boolean isConnected(ControllerInfo controller) {
+ if (controller == null) {
+ return false;
+ }
if (controller.equals(mSessionLegacyStub.getControllersForAll())) {
return true;
}
@@ -337,7 +344,7 @@
}
@Override
- public void setCustomLayout(@NonNull ControllerInfo controller,
+ public ListenableFuture<SessionResult> setCustomLayout(@NonNull ControllerInfo controller,
@NonNull final List<MediaSession2.CommandButton> layout) {
if (controller == null) {
throw new IllegalArgumentException("controller shouldn't be null");
@@ -345,10 +352,10 @@
if (layout == null) {
throw new IllegalArgumentException("layout shouldn't be null");
}
- notifyToController(controller, new NotifyRunnable() {
+ return sendCommand(controller, new ControllerCommand() {
@Override
- public void run(ControllerCb callback) throws RemoteException {
- callback.onCustomLayoutChanged(layout);
+ public void run(int seq, ControllerCb controller) throws RemoteException {
+ controller.setCustomLayout(seq, layout);
}
});
}
@@ -379,33 +386,33 @@
}
@Override
- public void sendCustomCommand(@NonNull final SessionCommand2 command,
+ public void broadcastCustomCommand(@NonNull final SessionCommand2 command,
@Nullable final Bundle args) {
if (command == null) {
throw new IllegalArgumentException("command shouldn't be null");
}
- notifyToAllControllers(new NotifyRunnable() {
+ broadcastCommand(new ControllerCommand() {
@Override
- public void run(ControllerCb callback) throws RemoteException {
- callback.onCustomCommand(command, args, null);
+ public void run(int seq, ControllerCb controller) throws RemoteException {
+ controller.sendCustomCommand(seq, command, args);
}
});
}
@Override
- public void sendCustomCommand(@NonNull ControllerInfo controller,
- @NonNull final SessionCommand2 command, @Nullable final Bundle args,
- @Nullable final ResultReceiver receiver) {
+ public ListenableFuture<SessionResult> sendCustomCommand(
+ @NonNull ControllerInfo controller, @NonNull final SessionCommand2 command,
+ @Nullable final Bundle args) {
if (controller == null) {
throw new IllegalArgumentException("controller shouldn't be null");
}
if (command == null) {
throw new IllegalArgumentException("command shouldn't be null");
}
- notifyToController(controller, new NotifyRunnable() {
+ return sendCommand(controller, new ControllerCommand() {
@Override
- public void run(ControllerCb callback) throws RemoteException {
- callback.onCustomCommand(command, args, receiver);
+ public void run(int seq, ControllerCb controller) throws RemoteException {
+ controller.sendCustomCommand(seq, command, args);
}
});
}
@@ -1018,23 +1025,14 @@
void notifyToController(@NonNull final ControllerInfo controller,
@NonNull NotifyRunnable runnable) {
- if (controller == null) {
- return;
- }
if (!isConnected(controller)) {
// Do not send command to an unconnected controller.
return;
}
-
try {
runnable.run(controller.getControllerCb());
} catch (DeadObjectException e) {
- if (DEBUG) {
- Log.d(TAG, controller.toString() + " is gone", e);
- }
- // Note: Only removing from MediaSession2Stub would be fine for now, because other
- // (legacy) stubs wouldn't throw DeadObjectException.
- mSession2Stub.getConnectedControllersManager().removeController(controller);
+ onDeadObjectException(controller, e);
} catch (RemoteException e) {
// Currently it's TransactionTooLargeException or DeadSystemException.
// We'd better to leave log for those cases because
@@ -1055,6 +1053,90 @@
notifyToController(controller, runnable);
}
+ private ListenableFuture<SessionResult> sendCommand(@NonNull ControllerInfo controller,
+ @NonNull ControllerCommand command) {
+ if (!isConnected(controller)) {
+ return SessionResult.createFutureWithResult(RESULT_CODE_DISCONNECTED);
+ }
+ try {
+ final ListenableFuture<SessionResult> result;
+ final int seq;
+ final SequencedFutureManager manager = mSession2Stub.getConnectedControllersManager()
+ .getSequencedFutureManager(controller);
+ if (manager != null) {
+ result = manager.createSequencedFuture();
+ seq = ((SequencedFuture<SessionResult>) result).getSequenceNumber();
+ } else {
+ // Can be null in two cases. Use the 0 as sequence number in both cases because
+ // Case 1) Controller is from the legacy stub
+ // -> Sequence number isn't needed, so 0 is OK
+ // Case 2) Controller is removed after the connection check above
+ // -> Call will fail below or ignored by the controller, so 0 is OK.
+ seq = 0;
+ result = SessionResult.createFutureWithResult(RESULT_CODE_SUCCESS);
+ }
+ command.run(seq, controller.getControllerCb());
+ return result;
+ } catch (DeadObjectException e) {
+ onDeadObjectException(controller, e);
+ return SessionResult.createFutureWithResult(RESULT_CODE_DISCONNECTED);
+ } catch (RemoteException e) {
+ // Currently it's TransactionTooLargeException or DeadSystemException.
+ // We'd better to leave log for those cases because
+ // - TransactionTooLargeException means that we may need to fix our code.
+ // (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);
+ return SessionResult.createFutureWithResult(RESULT_CODE_UNKNOWN_ERROR);
+ }
+ }
+
+ private void broadcastCommand(@NonNull ControllerCommand command) {
+ List<ControllerInfo> controllers =
+ mSession2Stub.getConnectedControllersManager().getConnectedControllers();
+ controllers.add(mSessionLegacyStub.getControllersForAll());
+ for (int i = 0; i < controllers.size(); i++) {
+ ControllerInfo controller = controllers.get(i);
+ try {
+ final SequencedFutureManager manager = mSession2Stub
+ .getConnectedControllersManager().getSequencedFutureManager(controller);
+ final int seq;
+ if (manager != null) {
+ seq = manager.obtainNextSequenceNumber();
+ // Can be null in two cases. Use the 0 as sequence number in both cases because
+ // Case 1) Controller is from the legacy stub
+ // -> Sequence number isn't needed, so 0 is OK
+ // Case 2) Controller is removed after the connection check above
+ // -> Call will fail below or ignored by the controller, so 0 is OK.
+ } else {
+ seq = 0;
+ }
+ command.run(seq, controller.getControllerCb());
+ } catch (DeadObjectException e) {
+ onDeadObjectException(controller, e);
+ } catch (RemoteException e) {
+ // Currently it's TransactionTooLargeException or DeadSystemException.
+ // We'd better to leave log for those cases because
+ // - TransactionTooLargeException means that we may need to fix our code.
+ // (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);
+ }
+ }
+ }
+
+ /**
+ * Removes controller. Call this when DeadObjectException is happened with binder call.
+ */
+ private void onDeadObjectException(ControllerInfo controller, DeadObjectException e) {
+ if (DEBUG) {
+ Log.d(TAG, controller.toString() + " is gone", e);
+ }
+ // Note: Only removing from MediaSession2Stub and ignoring (legacy) stubs would be fine for
+ // now. Because calls to the legacy stubs doesn't throw DeadObjectException.
+ mSession2Stub.getConnectedControllersManager().removeController(controller);
+ }
+
///////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////
@@ -1068,6 +1150,11 @@
void run(ControllerCb callback) throws RemoteException;
}
+ @FunctionalInterface
+ interface ControllerCommand {
+ void run(int seq, ControllerCb controller) throws RemoteException;
+ }
+
private static class SessionPlayerCallback extends SessionPlayer2.PlayerCallback {
private final WeakReference<MediaSession2ImplBase> mSession;
diff --git a/media2/src/main/java/androidx/media2/MediaSession2Stub.java b/media2/src/main/java/androidx/media2/MediaSession2Stub.java
index e80b9c5..2d537f7 100644
--- a/media2/src/main/java/androidx/media2/MediaSession2Stub.java
+++ b/media2/src/main/java/androidx/media2/MediaSession2Stub.java
@@ -31,7 +31,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.os.SystemClock;
import android.support.v4.media.session.MediaSessionCompat;
import android.text.TextUtils;
@@ -141,7 +140,6 @@
}
}
-
private void onSessionCommand(@NonNull IMediaController2 caller, int seq,
final @CommandCode int commandCode,
final @NonNull Command command) {
@@ -390,6 +388,18 @@
}
@Override
+ public void onControllerResult(final IMediaController2 caller, int seq,
+ final ParcelImpl controllerResult) {
+ SequencedFutureManager manager = mConnectedControllersManager.getSequencedFutureManager(
+ caller.asBinder());
+ if (manager == null) {
+ return;
+ }
+ MediaController2.ControllerResult result = ParcelUtils.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 {
onSessionCommand(caller, seq, SessionCommand2.COMMAND_CODE_VOLUME_SET_VOLUME,
@@ -654,15 +664,16 @@
@Override
public void setPlaylist(final IMediaController2 caller, int seq,
- final List<ParcelImpl> playlist, final Bundle metadata) {
+ final ParcelImplListSlice listSlice, final Bundle metadata) {
onSessionCommand(caller, seq, SessionCommand2.COMMAND_CODE_PLAYER_SET_PLAYLIST,
new PlayerCommand() {
@Override
public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
- if (playlist == null) {
+ if (listSlice == null || listSlice.getList() == 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,
@@ -1030,6 +1041,7 @@
}
final class Controller2Cb extends ControllerCb {
+ // TODO: Drop 'Callback' from the name.
private final IMediaController2 mIControllerCallback;
Controller2Cb(@NonNull IMediaController2 callback) {
@@ -1055,8 +1067,8 @@
}
@Override
- void onCustomLayoutChanged(List<CommandButton> layout) throws RemoteException {
- mIControllerCallback.onCustomLayoutChanged(
+ void setCustomLayout(int seq, List<CommandButton> layout) throws RemoteException {
+ mIControllerCallback.onSetCustomLayout(seq,
MediaUtils2.convertCommandButtonListToParcelImplList(layout));
}
@@ -1072,10 +1084,10 @@
}
@Override
- void onCustomCommand(SessionCommand2 command, Bundle args, ResultReceiver receiver)
+ void sendCustomCommand(int seq, SessionCommand2 command, Bundle args)
throws RemoteException {
- mIControllerCallback.onCustomCommand((ParcelImpl) ParcelUtils.toParcelable(command),
- args, receiver);
+ mIControllerCallback.onCustomCommand(seq,
+ (ParcelImpl) ParcelUtils.toParcelable(command), args);
}
@Override
diff --git a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
index 2147a62..e5d35c7 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
@@ -573,7 +573,7 @@
}
@Override
- void onCustomLayoutChanged(List<CommandButton> layout) throws RemoteException {
+ void setCustomLayout(int seq, List<CommandButton> layout) throws RemoteException {
// no-op.
}
@@ -588,7 +588,7 @@
}
@Override
- void onCustomCommand(SessionCommand2 command, Bundle args, ResultReceiver receiver)
+ void sendCustomCommand(int seq, SessionCommand2 command, Bundle args)
throws RemoteException {
// no-op
}
@@ -710,7 +710,7 @@
}
@Override
- void onCustomLayoutChanged(List<CommandButton> layout) throws RemoteException {
+ void setCustomLayout(int seq, List<CommandButton> layout) throws RemoteException {
throw new AssertionError("This shouldn't be called.");
}
@@ -726,7 +726,7 @@
}
@Override
- void onCustomCommand(SessionCommand2 command, Bundle args, ResultReceiver receiver)
+ void sendCustomCommand(int seq, SessionCommand2 command, Bundle args)
throws RemoteException {
// no-op
}
diff --git a/media2/src/main/java/androidx/media2/ParcelImplListSlice.java b/media2/src/main/java/androidx/media2/ParcelImplListSlice.java
new file mode 100644
index 0000000..68d08eb
--- /dev/null
+++ b/media2/src/main/java/androidx/media2/ParcelImplListSlice.java
@@ -0,0 +1,204 @@
+/*
+ * 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.media2;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.RestrictTo;
+import androidx.versionedparcelable.ParcelImpl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Transfer a large list of {@link ParcelImpl} objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * Note: Using this class makes synchronous binder calls, and also loses oneway property.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class ParcelImplListSlice implements Parcelable {
+ private static final String TAG = "ParcelImplListSlice";
+ private static final boolean DEBUG = false;
+
+ // TODO: get this number from somewhere else.
+ private static final int MAX_IPC_SIZE = 4 * 1024;
+
+ final List<ParcelImpl> mList;
+
+ public ParcelImplListSlice(List<ParcelImpl> list) {
+ mList = list;
+ }
+
+ ParcelImplListSlice(Parcel p) {
+ final int itemCount = p.readInt();
+ mList = new ArrayList<>(itemCount);
+ if (DEBUG) {
+ Log.d(TAG, "Retrieving " + itemCount + " items");
+ }
+ if (itemCount <= 0) {
+ return;
+ }
+
+ int i = 0;
+ while (i < itemCount) {
+ if (p.readInt() == 0) {
+ break;
+ }
+
+ final ParcelImpl parcelImpl = p.readParcelable(ParcelImpl.class.getClassLoader());
+ mList.add(parcelImpl);
+
+ if (DEBUG) {
+ Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1));
+ }
+ i++;
+ }
+ if (i >= itemCount) {
+ return;
+ }
+ final IBinder retriever = p.readStrongBinder();
+ while (i < itemCount) {
+ if (DEBUG) {
+ Log.d(TAG, "Reading more @" + i + " of " + itemCount + ": retriever=" + retriever);
+ }
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInt(i);
+ try {
+ retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure retrieving array; only received " + i + " of " + itemCount, e);
+ return;
+ }
+ while (i < itemCount && reply.readInt() != 0) {
+ final ParcelImpl parcelImpl = reply.readParcelable(
+ ParcelImpl.class.getClassLoader());
+ mList.add(parcelImpl);
+
+ if (DEBUG) {
+ Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1));
+ }
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ public List<ParcelImpl> getList() {
+ return mList;
+ }
+
+ /**
+ * Write this to another Parcel. Note that this discards the internal Parcel
+ * and should not be used anymore. This is so we can pass this to a Binder
+ * where we won't have a chance to call recycle on this.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int itemCount = mList.size();
+ dest.writeInt(itemCount);
+ if (DEBUG) {
+ Log.d(TAG, "Writing " + itemCount + " items");
+ }
+ if (itemCount > 0) {
+ int i = 0;
+ while (i < itemCount && dest.dataSize() < MAX_IPC_SIZE) {
+ dest.writeInt(1);
+
+ final ParcelImpl parcelable = mList.get(i);
+ dest.writeParcelable(parcelable, flags);
+
+ if (DEBUG) {
+ Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+ }
+ i++;
+ }
+ if (i < itemCount) {
+ dest.writeInt(0);
+ Binder retriever = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code != FIRST_CALL_TRANSACTION) {
+ return super.onTransact(code, data, reply, flags);
+ }
+ int i = data.readInt();
+ if (DEBUG) {
+ Log.d(TAG, "Writing more @" + i + " of " + itemCount);
+ }
+ while (i < itemCount && reply.dataSize() < MAX_IPC_SIZE) {
+ reply.writeInt(1);
+
+ final ParcelImpl parcelable = mList.get(i);
+ reply.writeParcelable(parcelable, flags);
+
+ if (DEBUG) {
+ Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+ }
+ i++;
+ }
+ if (i < itemCount) {
+ if (DEBUG) {
+ Log.d(TAG, "Breaking @" + i + " of " + itemCount);
+ }
+ reply.writeInt(0);
+ }
+ return true;
+ }
+ };
+ if (DEBUG) {
+ Log.d(TAG, "Breaking @" + i + " of " + itemCount + ": retriever=" + retriever);
+ }
+ dest.writeStrongBinder(retriever);
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ int contents = 0;
+ final List<ParcelImpl> list = getList();
+ for (int i = 0; i < list.size(); i++) {
+ contents |= list.get(i).describeContents();
+ }
+ return contents;
+ }
+
+ public static final Parcelable.Creator<ParcelImplListSlice> CREATOR =
+ new Parcelable.Creator<ParcelImplListSlice>() {
+ @Override
+ public ParcelImplListSlice createFromParcel(Parcel in) {
+ return new ParcelImplListSlice(in);
+ }
+
+ @Override
+ public ParcelImplListSlice[] newArray(int size) {
+ return new ParcelImplListSlice[size];
+ }
+ };
+}
diff --git a/media2/src/main/java/androidx/media2/SequencedFutureManager.java b/media2/src/main/java/androidx/media2/SequencedFutureManager.java
index 171b3af..2b4a60b 100644
--- a/media2/src/main/java/androidx/media2/SequencedFutureManager.java
+++ b/media2/src/main/java/androidx/media2/SequencedFutureManager.java
@@ -34,6 +34,7 @@
*/
@TargetApi(Build.VERSION_CODES.P)
class SequencedFutureManager<T> implements AutoCloseable {
+ private static final boolean DEBUG = false;
private static final String TAG = "SequencedFutureManager";
private final Object mLock = new Object();
private final T mResultWhenClosed;
@@ -94,8 +95,12 @@
if (future != null) {
future.set(result);
} else {
- Log.w(TAG, "Unexpected sequence number, seq=" + seq,
- new IllegalArgumentException());
+ if (DEBUG) {
+ // Note: May not be an error if the caller doesn't return ListenableFuture
+ // e.g. MediaSession2#broadcastCustomCommand
+ Log.d(TAG, "Unexpected sequence number, seq=" + seq,
+ new IllegalArgumentException());
+ }
}
}
}
diff --git a/settings.gradle b/settings.gradle
index 7ed5d05..6062745 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -68,7 +68,7 @@
includeProject(":exifinterface", "exifinterface")
includeProject(":fragment", "fragment")
includeProject(":fragment-ktx", "fragment/ktx")
-includeProject(":fragment-test", "fragment/test")
+includeProject(":fragment-testing", "fragment/testing")
includeProject(":gridlayout", "gridlayout")
includeProject(":heifwriter", "heifwriter")
includeProject(":interpolator", "interpolator")
diff --git a/textclassifier/api/current.txt b/textclassifier/api/current.txt
index 11932f6..2a7e78e 100644
--- a/textclassifier/api/current.txt
+++ b/textclassifier/api/current.txt
@@ -110,8 +110,7 @@
}
public final class TextLinks {
- method public int apply(android.widget.TextView, androidx.textclassifier.TextLinksParams);
- method public int apply(android.content.Context, android.text.Spannable, androidx.textclassifier.TextLinksParams);
+ method public int apply(android.text.Spannable, androidx.textclassifier.TextClassifier, androidx.textclassifier.TextLinksParams);
method public static androidx.textclassifier.TextLinks createFromBundle(android.os.Bundle);
method public java.util.Collection<androidx.textclassifier.TextLinks.TextLink> getLinks();
method public android.os.Bundle toBundle();
diff --git a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/MainActivity.java b/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/MainActivity.java
index 580fd9d..569b4cd 100644
--- a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/MainActivity.java
+++ b/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/MainActivity.java
@@ -18,9 +18,9 @@
import android.os.Bundle;
import android.text.Spannable;
+import android.text.method.LinkMovementMethod;
import android.view.View;
import android.widget.AdapterView;
-import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
@@ -44,20 +44,15 @@
private static final Executor sWorkerThreadExecutor = Executors.newSingleThreadExecutor();
private static final Executor sMainThreadExecutor = new MainThreadExecutor();
- private EditText mInput;
-
+ private TextView mInput;
private TextView mStatusTextView;
- private TextClassificationManager mTextClassificationManager;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- mTextClassificationManager = TextClassificationManager.of(this);
-
setContentView(R.layout.activity_main);
mInput = findViewById(R.id.textView_input);
+ setLinkMovementMethod(mInput);
mStatusTextView = findViewById(R.id.textView_tc);
findViewById(R.id.button_generate_links).setOnClickListener(v -> linkifyAsync(mInput));
updateStatusText();
@@ -70,18 +65,15 @@
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long l) {
if (pos == DEFAULT) {
- mTextClassificationManager.setTextClassifier(null);
+ setTextClassifier(null);
} else {
- mTextClassificationManager.setTextClassifier(
- new SimpleTextClassifier(MainActivity.this));
+ setTextClassifier(new SimpleTextClassifier(MainActivity.this));
}
updateStatusText();
}
@Override
- public void onNothingSelected(AdapterView<?> adapterView) {
-
- }
+ public void onNothingSelected(AdapterView<?> adapterView) {}
});
}
@@ -107,12 +99,23 @@
sWorkerThreadExecutor.execute(() -> {
TextLinks.Request request = new TextLinks.Request.Builder(textView.getText()).build();
TextLinks textLinks = getTextClassifier().generateLinks(request);
- sMainThreadExecutor.execute(
- () -> textLinks.apply(textView, TextLinksParams.DEFAULT_PARAMS));
+ sMainThreadExecutor.execute(() ->
+ textLinks.apply(
+ (Spannable) textView.getText(),
+ getTextClassifier(),
+ TextLinksParams.DEFAULT_PARAMS));
});
}
private TextClassifier getTextClassifier() {
- return mTextClassificationManager.getTextClassifier();
+ return TextClassificationManager.of(this).getTextClassifier();
+ }
+
+ private void setTextClassifier(TextClassifier textClassifier) {
+ TextClassificationManager.of(this).setTextClassifier(textClassifier);
+ }
+
+ private void setLinkMovementMethod(TextView textView) {
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
}
}
diff --git a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java b/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java
index bcc76e0..e1ca0e4a 100644
--- a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java
+++ b/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java
@@ -67,9 +67,8 @@
@Override
public TextLinks generateLinks(TextLinks.Request request) {
- TextLinks.Builder builder = new TextLinks.Builder(request.getText().toString());
- CharSequence text = request.getText();
-
+ String text = request.getText().toString();
+ TextLinks.Builder builder = new TextLinks.Builder(text);
final Spannable spannable = new SpannableString(text);
if (LinkifyCompat.addLinks(spannable, Pattern.compile("android", Pattern.CASE_INSENSITIVE),
null, null, (matcher, s) -> "https://www.android.com")) {
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java
index 343b948..764389c 100644
--- a/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java
@@ -28,9 +28,7 @@
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
-import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
-import android.widget.TextView;
import androidx.collection.ArrayMap;
import androidx.core.os.LocaleListCompat;
@@ -173,7 +171,8 @@
TextLinks textLinks = new TextLinks.Builder(text).build();
Context context = InstrumentationRegistry.getContext();
- int status = textLinks.apply(context, text, TextLinksParams.DEFAULT_PARAMS);
+ TextClassifier textClassifier = TextClassificationManager.of(context).getTextClassifier();
+ int status = textLinks.apply(text, textClassifier, TextLinksParams.DEFAULT_PARAMS);
assertThat(status).isEqualTo(TextLinks.STATUS_NO_LINKS_FOUND);
final TextLinks.TextLinkSpan[] spans =
@@ -189,29 +188,13 @@
.build();
Context context = InstrumentationRegistry.getContext();
- int status = textLinks.apply(context, text, TextLinksParams.DEFAULT_PARAMS);
+ TextClassifier textClassifier = TextClassificationManager.of(context).getTextClassifier();
+ int status = textLinks.apply(text, textClassifier, TextLinksParams.DEFAULT_PARAMS);
assertThat(status).isEqualTo(TextLinks.STATUS_LINKS_APPLIED);
assertAppliedSpannable(text);
}
- @Test
- public void testApply_textview() {
- SpannableString text = new SpannableString(FULL_TEXT);
- TextLinks textLinks = new TextLinks.Builder(text)
- .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_PHONE, 1.0f))
- .build();
-
- final TextView textView = new TextView(InstrumentationRegistry.getTargetContext());
- textView.setText(text);
-
- int status = textLinks.apply(textView, TextLinksParams.DEFAULT_PARAMS);
- assertThat(status).isEqualTo(TextLinks.STATUS_LINKS_APPLIED);
- assertThat(textView.getMovementMethod()).isInstanceOf(LinkMovementMethod.class);
-
- assertAppliedSpannable((Spannable) textView.getText());
- }
-
private void assertAppliedSpannable(Spannable spannable) {
TextLinks.TextLinkSpan[] spans =
spannable.getSpans(0, spannable.length(), TextLinks.TextLinkSpan.class);
diff --git a/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java b/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java
index bbc97ab..3e3dbd9 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java
@@ -20,13 +20,10 @@
import static androidx.textclassifier.ConvertUtils.unwrapLocalListCompat;
import android.app.PendingIntent;
-import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.text.Spannable;
-import android.text.SpannableString;
import android.text.Spanned;
-import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
@@ -40,7 +37,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
-import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.RemoteActionCompat;
import androidx.core.os.LocaleListCompat;
@@ -164,64 +160,30 @@
}
/**
- * Similar to {@link #apply(Context, Spannable, TextLinksParams)}, except the links are applied
- * to a TextView directly. This also adds a LinkMovementMethod to the TextView if necessary.
- *
- * @see #apply(Context, Spannable, TextLinksParams)
- */
- @UiThread
- @Status
- public int apply(@NonNull TextView textView, TextLinksParams textLinksParams) {
- Preconditions.checkNotNull(textView);
-
- addLinkMovementMethod(textView);
-
- SpannableString spannableString = SpannableString.valueOf(textView.getText());
- int status = apply(textView.getContext(), spannableString, textLinksParams);
- if (status == TextLinks.STATUS_LINKS_APPLIED) {
- textView.setText(spannableString);
- }
- return status;
- }
-
- /**
* Annotates the given text with the generated links.
*
- * <p> A text classifier returned by {@link TextClassificationManager#getTextClassifier()} is
- * used.
- *
* <p><strong>NOTE: </strong>It may be necessary to set a LinkMovementMethod on the TextView
* widget to properly handle links. See {@link TextView#setMovementMethod(MovementMethod)}
*
- * @param context context
* @param text the text to apply the links to. Must match the original text
- * @param textLinksParams the param that specifies how the links should be applied.
+ * @param textClassifier the TextClassifier to use to classify a clicked link. Should usually
+ * be the one used to generate the links
+ * @param textLinksParams the param that specifies how the links should be applied
*
* @return the status code which indicates the operation is success or not.
*/
@Status
public int apply(
- @NonNull Context context,
@NonNull Spannable text,
+ @NonNull TextClassifier textClassifier,
@NonNull TextLinksParams textLinksParams) {
- Preconditions.checkNotNull(context);
Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(textClassifier);
Preconditions.checkNotNull(textLinksParams);
- TextClassifier textClassifier = TextClassificationManager.of(context).getTextClassifier();
-
return textLinksParams.apply(text, this, textClassifier);
}
- private void addLinkMovementMethod(@NonNull TextView textView) {
- MovementMethod method = textView.getMovementMethod();
- if (!(method instanceof LinkMovementMethod)) {
- if (textView.getLinksClickable()) {
- textView.setMovementMethod(LinkMovementMethod.getInstance());
- }
- }
- }
-
/**
* A link, identifying a substring of text and possible entity types for it.
*/
diff --git a/textclassifier/src/main/java/androidx/textclassifier/TextLinksParams.java b/textclassifier/src/main/java/androidx/textclassifier/TextLinksParams.java
index 5e3ad67..d2bd619 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/TextLinksParams.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/TextLinksParams.java
@@ -16,7 +16,6 @@
package androidx.textclassifier;
-import android.content.Context;
import android.text.Spannable;
import android.text.style.ClickableSpan;
@@ -31,7 +30,7 @@
/**
* Used to specify how to apply links when using
- * {@link TextLinks#apply(Context, Spannable, TextLinksParams)} APIs.
+ * {@link TextLinks#apply(Spannable, TextClassifier, TextLinksParams)} APIs.
*/
public final class TextLinksParams {
@@ -49,7 +48,7 @@
/**
* Default configuration of applying a TextLinks to a spannable or a TextView.
*
- * @see TextLinks#apply(Context, Spannable, TextLinksParams)
+ * @see TextLinks#apply(Spannable, TextClassifier, TextLinksParams)
*/
public static final TextLinksParams DEFAULT_PARAMS = new TextLinksParams.Builder().build();
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index 826e4ab..0a7d318 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -135,8 +135,8 @@
method public final java.util.List<android.net.Uri> getTriggeredContentUris();
method public final boolean isCancelled();
method public final boolean isStopped();
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> onStartWork();
method public void onStopped(boolean);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> startWork();
}
public static final class ListenableWorker.Payload {
@@ -261,7 +261,6 @@
}
public final class WorkStatus {
- ctor public WorkStatus(java.util.UUID, androidx.work.State, androidx.work.Data, java.util.List<java.lang.String>);
method public java.util.UUID getId();
method public androidx.work.Data getOutputData();
method public androidx.work.State getState();
@@ -272,8 +271,8 @@
ctor public Worker(android.content.Context, androidx.work.WorkerParameters);
method public abstract androidx.work.ListenableWorker.Result doWork();
method public final androidx.work.Data getOutputData();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> onStartWork();
method public final void setOutputData(androidx.work.Data);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Payload> startWork();
}
public abstract class WorkerFactory {
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index f979aaf..af3286403 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -36,10 +36,10 @@
/**
* The basic object that performs work. Worker classes are instantiated at runtime by the
- * {@link WorkerFactory} specified in the {@link Configuration}. The {@link #onStartWork()} method
+ * {@link WorkerFactory} specified in the {@link Configuration}. The {@link #startWork()} method
* is called on the background thread. In case the work is preempted and later restarted for any
* reason, a new instance of {@link ListenableWorker} is created. This means that
- * {@code onStartWork} is called exactly once per {@link ListenableWorker} instance.
+ * {@code startWork} is called exactly once per {@link ListenableWorker} instance.
*/
public abstract class ListenableWorker {
@@ -182,7 +182,7 @@
* cancel this Future, WorkManager will treat this unit of work as failed.
*/
@MainThread
- public abstract @NonNull ListenableFuture<Payload> onStartWork();
+ public abstract @NonNull ListenableFuture<Payload> startWork();
/**
* Returns {@code true} if this Worker has been told to stop. This could be because of an
@@ -274,7 +274,7 @@
/**
- * The payload of an {@link #onStartWork()} computation that contains both the result and the
+ * The payload of an {@link #startWork()} computation that contains both the result and the
* output data.
*/
public static final class Payload {
@@ -285,7 +285,7 @@
/**
* Constructs a Payload with the given {@link Result} and an empty output.
*
- * @param result The result of the {@link #onStartWork()} computation
+ * @param result The result of the {@link #startWork()} computation
*/
public Payload(@NonNull Result result) {
this(result, Data.EMPTY);
@@ -294,8 +294,8 @@
/**
* Constructs a Payload with the given {@link Result} and output.
*
- * @param result The result of the {@link #onStartWork()} computation
- * @param output The output {@link Data} of the {@link #onStartWork()} computation
+ * @param result The result of the {@link #startWork()} computation
+ * @param output The output {@link Data} of the {@link #startWork()} computation
*/
public Payload(@NonNull Result result, @NonNull Data output) {
mResult = result;
diff --git a/work/workmanager/src/main/java/androidx/work/WorkStatus.java b/work/workmanager/src/main/java/androidx/work/WorkStatus.java
index 57e0c52..66870e3 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkStatus.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkStatus.java
@@ -17,6 +17,7 @@
package androidx.work;
import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
import java.util.HashSet;
import java.util.List;
@@ -36,6 +37,10 @@
private @NonNull Data mOutputData;
private @NonNull Set<String> mTags;
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public WorkStatus(
@NonNull UUID id,
@NonNull State state,
diff --git a/work/workmanager/src/main/java/androidx/work/Worker.java b/work/workmanager/src/main/java/androidx/work/Worker.java
index 011c51c..a4d683d 100644
--- a/work/workmanager/src/main/java/androidx/work/Worker.java
+++ b/work/workmanager/src/main/java/androidx/work/Worker.java
@@ -49,7 +49,7 @@
public abstract @NonNull Result doWork();
@Override
- public final @NonNull ListenableFuture<Payload> onStartWork() {
+ public final @NonNull ListenableFuture<Payload> startWork() {
mFuture = SettableFuture.create();
getBackgroundExecutor().execute(new Runnable() {
@Override
@@ -70,7 +70,7 @@
* {@link OverwritingInputMerger}, unless otherwise specified using the
* {@link OneTimeWorkRequest.Builder#setInputMerger(Class)} method.
* <p>
- * This method is invoked after {@code onStartWork} and returns
+ * This method is invoked after {@code startWork} and returns
* {@link ListenableWorker.Result#SUCCESS} or a
* {@link ListenableWorker.Result#FAILURE}.
* <p>
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 51521d7..5193a00 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -216,13 +216,13 @@
}
final SettableFuture<ListenableWorker.Payload> future = SettableFuture.create();
- // Call mWorker.onStartWork() on the main thread.
+ // Call mWorker.startWork() on the main thread.
mWorkTaskExecutor.getMainThreadExecutor()
.execute(new Runnable() {
@Override
public void run() {
try {
- mInnerFuture = mWorker.onStartWork();
+ mInnerFuture = mWorker.startWork();
future.setFuture(mInnerFuture);
} catch (Throwable e) {
future.setException(e);
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 a3e4d7e..522937c 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
@@ -81,7 +81,7 @@
@NonNull
@Override
- public ListenableFuture<Payload> onStartWork() {
+ public ListenableFuture<Payload> startWork() {
getBackgroundExecutor().execute(new Runnable() {
@Override
public void run() {
@@ -132,7 +132,7 @@
// changes in constraints can cause the worker to throw RuntimeExceptions, and
// that should cause a retry.
try {
- final ListenableFuture<Payload> innerFuture = mDelegate.onStartWork();
+ final ListenableFuture<Payload> innerFuture = mDelegate.startWork();
innerFuture.addListener(new Runnable() {
@Override
public void run() {
@@ -147,7 +147,7 @@
}, getBackgroundExecutor());
} catch (Throwable exception) {
Logger.debug(TAG, String.format(
- "Delegated worker %s threw exception in onStartWork.", className),
+ "Delegated worker %s threw exception in startWork.", className),
exception);
synchronized (mLock) {
if (mAreConstraintsUnmet) {