Merge "AML: Make UserHandle.myUserId() available"
diff --git a/media/libstagefright/SimpleDecodingSource.cpp b/media/libstagefright/SimpleDecodingSource.cpp
index 404c537..babdc7a 100644
--- a/media/libstagefright/SimpleDecodingSource.cpp
+++ b/media/libstagefright/SimpleDecodingSource.cpp
@@ -37,6 +37,7 @@
const int64_t kTimeoutWaitForOutputUs = 500000; // 0.5 seconds
const int64_t kTimeoutWaitForInputUs = 5000; // 5 milliseconds
+const int kTimeoutMaxRetries = 20;
//static
sp<SimpleDecodingSource> SimpleDecodingSource::Create(
@@ -242,7 +243,7 @@
return ERROR_END_OF_STREAM;
}
- for (int retries = 0; ++retries; ) {
+ for (int retries = 0; retries < kTimeoutMaxRetries; ++retries) {
// If we fill all available input buffers, we should expect that
// the codec produces at least one output buffer. Also, the codec
// should produce an output buffer in at most 1 seconds. Retry a
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
deleted file mode 100644
index fff3a62..0000000
--- a/packages/MediaComponents/Android.mk
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Copyright 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-ifneq ($(TARGET_BUILD_PDK),true) # Build MediaComponents only if this is not a PDK build. MediaComponents won't
-# build in PDK builds because frameworks/base/core/java is not available but
-# IMediaSession2.aidl and IMediaController2.aidl are using classes from
-# frameworks/base/core/java.
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := MediaComponents
-LOCAL_MODULE_OWNER := google
-
-# TODO: create a separate key for this package.
-LOCAL_CERTIFICATE := platform
-
-# TODO: Use System SDK once public APIs are approved
-# LOCAL_SDK_VERSION := system_current
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-Iaidl-files-under, src)
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
-
-LOCAL_MULTILIB := first
-
-LOCAL_JAVA_LIBRARIES += androidx.annotation_annotation
-
-# To embed native libraries in package, uncomment the lines below.
-#LOCAL_MODULE_TAGS := samples
-#LOCAL_JNI_SHARED_LIBRARIES := \
-# libaacextractor \
-# libamrextractor \
-# libflacextractor \
-# libmidiextractor \
-# libmkvextractor \
-# libmp3extractor \
-# libmp4extractor \
-# libmpeg2extractor \
-# liboggextractor \
-# libwavextractor \
-
-# TODO: Remove dependency with other support libraries.
-LOCAL_STATIC_ANDROID_LIBRARIES += \
- androidx.legacy_legacy-support-v4 \
- androidx.appcompat_appcompat \
- androidx.palette_palette
-LOCAL_USE_AAPT2 := true
-
-include $(BUILD_PACKAGE)
-
-endif # ifneq ($(TARGET_BUILD_PDK),true)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/MediaComponents/AndroidManifest.xml b/packages/MediaComponents/AndroidManifest.xml
deleted file mode 100644
index 50fdca1..0000000
--- a/packages/MediaComponents/AndroidManifest.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.media.update"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <application
- android:label="Media Components Update"
- android:multiArch="true"
- android:allowBackup="false"
- android:hasCode="false"
- android:extractNativeLibs="false">
- </application>
-
-</manifest>
diff --git a/packages/MediaComponents/proguard.cfg b/packages/MediaComponents/proguard.cfg
deleted file mode 100644
index d7bf730..0000000
--- a/packages/MediaComponents/proguard.cfg
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# Copyright 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Keep entry point for updatable Java classes
--keep public class com.android.media.update.ApiFactory {
- public static com.android.media.update.ApiFactory initialize(android.content.pm.ApplicationInfo);
-}
diff --git a/packages/MediaComponents/runcts.sh b/packages/MediaComponents/runcts.sh
deleted file mode 100644
index 61b1a1e..0000000
--- a/packages/MediaComponents/runcts.sh
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/bin/bash
-# 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.
-
-# Usage '. runcts.sh'
-
-function _runtest_cts_mediacomponent_usage() {
- echo 'runtest-cts-MediaComponents [option]: Build, flash device,'
- echo ' and run subset of CtsMediaTestCases that MediaComponents covers.'
- echo ' *Warning* This bypasses CTS setup (e.g. download media contents from server)'
- echo ' For running CTS in official way, use atest or cts-tradefed '
- echo ' -h|--help: This help'
- echo ' --skip: Skip build and flash. Just rerun-tests'
- echo ' --min: Only rebuild tests and updatable library.'
- echo ' --test: Only rebuild tests'
- echo ' -s [device_id]: Specify a device name to run test against.'
- echo ' You can define ${ADBHOST} instead.'
- echo ' -r [count]: Repeat tests for given count. It will stop when fails.'
- echo ' --ignore: Keep repeating tests even when it fails.'
- echo ' -t [test]: Only run the specific test. Can be either a class or a method.'
-}
-
-function runtest-cts-MediaComponents() {
- # Edit here if you want to support other tests.
- # List up libs and apks in the media_api needed for tests, and place test target at the last.
- local TEST_PACKAGE_DIR=("frameworks/av/packages/MediaComponents/test")
- local TEST_PACKAGE=("android.media.cts")
- local BUILD_TARGETS=("MediaComponents" "CtsMediaTestCases")
- # Don't include MediaComponents -- if we simply install it, system server
- # wouldn't use the installed one.
- local INSTALL_TARGETS=("CtsMediaTestCases")
- local TEST_RUNNER="android.support.test.runner.AndroidJUnitRunner"
- local DEPENDENCIES=("mockito-target-minus-junit4" "android-support-test" "compatibility-device-util")
- local DEFAULT_TEST_TARGET=""
- DEFAULT_TEST_TARGET+="android.media.cts.MediaBrowser2Test"
- DEFAULT_TEST_TARGET+=",android.media.cts.MediaController2Test"
- DEFAULT_TEST_TARGET+=",android.media.cts.MediaMetadata2Test"
- DEFAULT_TEST_TARGET+=",android.media.cts.MediaSession2Test"
- DEFAULT_TEST_TARGET+=",android.media.cts.MediaSession2_PermissionTest"
- DEFAULT_TEST_TARGET+=",android.media.cts.MediaSessionManager_MediaSession2Test"
- DEFAULT_TEST_TARGET+=",android.media.cts.SessionToken2Test"
- if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
- echo "Needs to lunch a target first"
- return
- fi
-
- local old_path=${OLDPWD}
- while true; do
- local OPTION_SKIP="false"
- local OPTION_MIN="false"
- local OPTION_TEST="false"
- local OPTION_REPEAT_COUNT="1"
- local OPTION_IGNORE="false"
- local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
- local adbhost_local
- while (( "$#" )); do
- case "${1}" in
- -h|--help)
- _runtest_cts_mediacomponent_usage
- return
- ;;
- --skip)
- OPTION_SKIP="true"
- ;;
- --min)
- OPTION_MIN="true"
- ;;
- --test)
- OPTION_TEST="true"
- ;;
- -s)
- shift
- adbhost_local=${1}
- ;;
- -r)
- shift
- OPTION_REPEAT_COUNT="${1}"
- ;;
- --ignore)
- OPTION_IGNORE="true"
- ;;
- -t)
- shift
- OPTION_TEST_TARGET="${1}"
- esac
- shift
- done
-
- # Build adb command.
- local adb
- if [[ -z "${adbhost_local}" ]]; then
- adbhost_local=${ADBHOST}
- fi
- if [[ -z "${adbhost_local}" ]]; then
- local device_count=$(adb devices | sed '/^[[:space:]]*$/d' | wc -l)
- if [[ "${device_count}" != "2" ]]; then
- echo "Too many devices. Specify a device." && break
- fi
- adb="adb"
- else
- adb="adb -s ${adbhost_local}"
- fi
-
- local target_dir="${ANDROID_BUILD_TOP}/${TEST_PACKAGE_DIR}"
- #local TEST_PACKAGE=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\.]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
-
- if [[ "${OPTION_SKIP}" != "true" ]]; then
- # Build dependencies if needed.
- local dependency
- local build_dependency=""
- for dependency in ${DEPENDENCIES[@]}; do
- if [[ "${dependency}" == "out/"* ]]; then
- if [[ ! -f ${ANDROID_BUILD_TOP}/${dependency} ]]; then
- build_dependency="true"
- break
- fi
- else
- if [[ "$(find ${OUT} -name ${dependency}_intermediates | wc -l)" == "0" ]]; then
- build_dependency="true"
- break
- fi
- fi
- done
- if [[ "${build_dependency}" == "true" ]]; then
- echo "Building dependencies. Will only print stderr."
- m ${DEPENDENCIES[@]} -j > /dev/null
- fi
-
- # Build test apk and required apk.
- local build_targets
- if [[ "${OPTION_TEST}" == "true" ]]; then
- build_targets="${INSTALL_TARGETS[@]}"
- elif [[ "${OPTION_MIN}" == "true" ]]; then
- build_targets="${BUILD_TARGETS[@]}"
- else
- build_targets="${BUILD_TARGETS[@]} droid"
- fi
- m ${build_targets} -j || break
-
- if [[ "${OPTION_TEST}" != "true" ]]; then
- # Flash only when needed
- local device_build_type="$(${adb} shell getprop ro.build.type)"
- if [[ "${device_build_type}" == "user" ]]; then
- # User build. Cannot adb sync
- ${adb} reboot bootloader
- fastboot flashall
- else
- ${adb} root
- local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
- if [[ "${device_verity_mode}" != "disabled" ]]; then
- ${adb} disable-verity
- ${adb} reboot
- ${adb} wait-for-device || break
- ${adb} root
- fi
- ${adb} remount
- ${adb} shell stop
- ${adb} shell setprop log.tag.MediaSessionService DEBUG
- ${adb} sync
- ${adb} shell start
- fi
- ${adb} wait-for-device || break
- # Ensure package manager is loaded.
- # TODO(jaewan): Find better way to wait
- sleep 15
- fi
-
- # Install apks
- local install_failed="false"
- for target in ${INSTALL_TARGETS[@]}; do
- local apk_path=$(find ${OUT}/system ${OUT}/data -name ${target}.apk)
- local apk_num=$(find ${OUT}/system ${OUT}/data -name ${target}.apk | wc -l)
- if [[ "${apk_num}" != "1" ]]; then
- echo "Cannot locate a ${target}.apk. Found ${apk_num} apks" && break
- fi
- echo "Installing ${target}.apk. path=${apk_path}"
- ${adb} install -r ${apk_path}
- if [[ "${?}" != "0" ]]; then
- install_failed="true"
- break
- fi
- done
- if [[ "${install_failed}" == "true" ]]; then
- echo "Failed to install. Test wouldn't run."
- break
- fi
- fi
-
- local test_target=""
- if [[ -n "${OPTION_TEST_TARGET}" ]]; then
- test_target="-e class ${OPTION_TEST_TARGET}"
- fi
-
- local i
- local tmpfile=$(tempfile)
- for ((i=1; i <= ${OPTION_REPEAT_COUNT}; i++)); do
- echo "Run test ${i}/${OPTION_REPEAT_COUNT}"
- ${adb} shell am instrument ${test_target} -w ${TEST_PACKAGE}/${TEST_RUNNER} >& ${tmpfile}
- cat ${tmpfile}
- if [[ "${OPTION_IGNORE}" != "true" ]]; then
- if [[ -n "$(grep ${tmpfile} -e 'FAILURE\|crashed')" ]]; then
- # am instrument doesn't return error code so need to grep result message instead
- break
- fi
- fi
- done
- rm ${tmpfile}
- break
- done
-}
-
-echo "Following functions are added to your environment:"
-_runtest_cts_mediacomponent_usage
diff --git a/packages/MediaComponents/src/com/android/media/IMediaController2.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
deleted file mode 100644
index cc5acf9..0000000
--- a/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
+++ /dev/null
@@ -1,55 +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 com.android.media;
-
-import android.app.PendingIntent;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-import com.android.media.IMediaSession2;
-
-/**
- * Interface from MediaSession2 to MediaController2.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from session to make session owner(s) frozen.
- */
-// TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
-// Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
-oneway interface IMediaController2 {
- void onPlayerStateChanged(int state);
- void onPositionChanged(long eventTimeMs, long positionMs);
- void onPlaybackSpeedChanged(float speed);
- void onBufferedPositionChanged(long bufferedPositionMs);
- void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
- void onPlaylistMetadataChanged(in Bundle metadata);
- void onPlaybackInfoChanged(in Bundle playbackInfo);
- void onRepeatModeChanged(int repeatMode);
- void onShuffleModeChanged(int shuffleMode);
- void onError(int errorCode, in Bundle extras);
-
- void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
- int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
- long bufferedPositionMs, in Bundle playbackInfo, int repeatMode, int shuffleMode,
- in List<Bundle> playlist, in PendingIntent sessionActivity);
- void onDisconnected();
-
- void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
- void onAllowedCommandsChanged(in Bundle commands);
-
- void onCustomCommand(in Bundle command, in Bundle args, in ResultReceiver receiver);
-}
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
deleted file mode 100644
index 5761455..0000000
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ /dev/null
@@ -1,72 +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 com.android.media;
-
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.net.Uri;
-
-import com.android.media.IMediaController2;
-
-/**
- * Interface from MediaController2 to MediaSession2.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from session to make session owner(s) frozen.
- */
- // TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
- // Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
-oneway interface IMediaSession2 {
- // TODO(jaewan): add onCommand() to send private command
-
- // TODO(jaewan): (Post P) We may consider to add another binder just for the connection
- // not to expose other methods to the controller whose connection wasn't accepted.
- // But this would be enough for now because it's the same as existing
- // MediaBrowser and MediaBrowserService.
- void connect(IMediaController2 caller, String callingPackage);
- void release(IMediaController2 caller);
-
- void setVolumeTo(IMediaController2 caller, int value, int flags);
- void adjustVolume(IMediaController2 caller, int direction, int flags);
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- // send command
- //////////////////////////////////////////////////////////////////////////////////////////////
- void sendTransportControlCommand(IMediaController2 caller,
- int commandCode, in Bundle args);
- void sendCustomCommand(IMediaController2 caller, in Bundle command, in Bundle args,
- in ResultReceiver receiver);
-
- void prepareFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
- void prepareFromSearch(IMediaController2 caller, String query, in Bundle extras);
- void prepareFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
- void playFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
- void playFromSearch(IMediaController2 caller, String query, in Bundle extras);
- void playFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
- void setRating(IMediaController2 caller, String mediaId, in Bundle rating);
-
- void setPlaylist(IMediaController2 caller, in List<Bundle> playlist, in Bundle metadata);
- void updatePlaylistMetadata(IMediaController2 caller, in Bundle metadata);
- void addPlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
- void removePlaylistItem(IMediaController2 caller, in Bundle mediaItem);
- void replacePlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
- void skipToPlaylistItem(IMediaController2 caller, in Bundle mediaItem);
- void skipToPreviousItem(IMediaController2 caller);
- void skipToNextItem(IMediaController2 caller);
- void setRepeatMode(IMediaController2 caller, int repeatMode);
- void setShuffleMode(IMediaController2 caller, int shuffleMode);
-}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
deleted file mode 100644
index 1c4cf82..0000000
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ /dev/null
@@ -1,1085 +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 com.android.media;
-
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI;
-import static android.media.SessionCommand2.COMMAND_CODE_SET_VOLUME;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.media.AudioAttributes;
-import android.media.MediaController2;
-import android.media.MediaController2.ControllerCallback;
-import android.media.MediaController2.PlaybackInfo;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlaylistAgent.RepeatMode;
-import android.media.MediaPlaylistAgent.ShuffleMode;
-import android.media.MediaSession2.CommandButton;
-import android.media.Rating2;
-import android.media.SessionCommand2;
-import android.media.SessionCommandGroup2;
-import android.media.SessionToken2;
-import android.media.update.MediaController2Provider;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-public class MediaController2Impl implements MediaController2Provider {
- private static final String TAG = "MediaController2";
- private static final boolean DEBUG = true; // TODO(jaewan): Change
-
- private final MediaController2 mInstance;
- private final Context mContext;
- private final Object mLock = new Object();
-
- private final MediaController2Stub mControllerStub;
- private final SessionToken2 mToken;
- private final ControllerCallback mCallback;
- private final Executor mCallbackExecutor;
- private final IBinder.DeathRecipient mDeathRecipient;
-
- @GuardedBy("mLock")
- private SessionServiceConnection mServiceConnection;
- @GuardedBy("mLock")
- private boolean mIsReleased;
- @GuardedBy("mLock")
- private List<MediaItem2> mPlaylist;
- @GuardedBy("mLock")
- private MediaMetadata2 mPlaylistMetadata;
- @GuardedBy("mLock")
- private @RepeatMode int mRepeatMode;
- @GuardedBy("mLock")
- private @ShuffleMode int mShuffleMode;
- @GuardedBy("mLock")
- private int mPlayerState;
- @GuardedBy("mLock")
- private long mPositionEventTimeMs;
- @GuardedBy("mLock")
- private long mPositionMs;
- @GuardedBy("mLock")
- private float mPlaybackSpeed;
- @GuardedBy("mLock")
- private long mBufferedPositionMs;
- @GuardedBy("mLock")
- private PlaybackInfo mPlaybackInfo;
- @GuardedBy("mLock")
- private PendingIntent mSessionActivity;
- @GuardedBy("mLock")
- private SessionCommandGroup2 mAllowedCommands;
-
- // Assignment should be used with the lock hold, but should be used without a lock to prevent
- // potential deadlock.
- // Postfix -Binder is added to explicitly show that it's potentially remote process call.
- // Technically -Interface is more correct, but it may misread that it's interface (vs class)
- // so let's keep this postfix until we find better postfix.
- @GuardedBy("mLock")
- private volatile IMediaSession2 mSessionBinder;
-
- // TODO(jaewan): Require session activeness changed listener, because controller can be
- // available when the session's player is null.
- public MediaController2Impl(Context context, MediaController2 instance, SessionToken2 token,
- Executor executor, ControllerCallback callback) {
- mInstance = instance;
- if (context == null) {
- throw new IllegalArgumentException("context shouldn't be null");
- }
- if (token == null) {
- throw new IllegalArgumentException("token shouldn't be null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
- if (executor == null) {
- throw new IllegalArgumentException("executor shouldn't be null");
- }
- mContext = context;
- mControllerStub = new MediaController2Stub(this);
- mToken = token;
- mCallback = callback;
- mCallbackExecutor = executor;
- mDeathRecipient = () -> {
- mInstance.close();
- };
-
- mSessionBinder = null;
- }
-
- @Override
- public void initialize() {
- // TODO(jaewan): More sanity checks.
- if (mToken.getType() == SessionToken2.TYPE_SESSION) {
- // Session
- mServiceConnection = null;
- connectToSession(SessionToken2Impl.from(mToken).getSessionBinder());
- }
- }
-
- private void connectToSession(IMediaSession2 sessionBinder) {
- try {
- sessionBinder.connect(mControllerStub, mContext.getPackageName());
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to call connection request. Framework will retry"
- + " automatically");
- }
- }
-
- @Override
- public void close_impl() {
- if (DEBUG) {
- Log.d(TAG, "release from " + mToken);
- }
- final IMediaSession2 binder;
- synchronized (mLock) {
- if (mIsReleased) {
- // Prevent re-enterance from the ControllerCallback.onDisconnected()
- return;
- }
- mIsReleased = true;
- if (mServiceConnection != null) {
- mContext.unbindService(mServiceConnection);
- mServiceConnection = null;
- }
- binder = mSessionBinder;
- mSessionBinder = null;
- mControllerStub.destroy();
- }
- if (binder != null) {
- try {
- binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
- binder.release(mControllerStub);
- } catch (RemoteException e) {
- // No-op.
- }
- }
- mCallbackExecutor.execute(() -> {
- mCallback.onDisconnected(mInstance);
- });
- }
-
- IMediaSession2 getSessionBinder() {
- return mSessionBinder;
- }
-
- MediaController2Stub getControllerStub() {
- return mControllerStub;
- }
-
- Executor getCallbackExecutor() {
- return mCallbackExecutor;
- }
-
- Context getContext() {
- return mContext;
- }
-
- MediaController2 getInstance() {
- return mInstance;
- }
-
- // Returns session binder if the controller can send the command.
- IMediaSession2 getSessionBinderIfAble(int commandCode) {
- synchronized (mLock) {
- if (!mAllowedCommands.hasCommand(commandCode)) {
- // Cannot send because isn't allowed to.
- Log.w(TAG, "Controller isn't allowed to call command, commandCode="
- + commandCode);
- return null;
- }
- }
- // TODO(jaewan): Should we do this with the lock hold?
- final IMediaSession2 binder = mSessionBinder;
- if (binder == null) {
- // Cannot send because disconnected.
- Log.w(TAG, "Session is disconnected");
- }
- return binder;
- }
-
- // Returns session binder if the controller can send the command.
- IMediaSession2 getSessionBinderIfAble(SessionCommand2 command) {
- synchronized (mLock) {
- if (!mAllowedCommands.hasCommand(command)) {
- Log.w(TAG, "Controller isn't allowed to call command, command=" + command);
- return null;
- }
- }
- // TODO(jaewan): Should we do this with the lock hold?
- final IMediaSession2 binder = mSessionBinder;
- if (binder == null) {
- // Cannot send because disconnected.
- Log.w(TAG, "Session is disconnected");
- }
- return binder;
- }
-
- @Override
- public SessionToken2 getSessionToken_impl() {
- return mToken;
- }
-
- @Override
- public boolean isConnected_impl() {
- final IMediaSession2 binder = mSessionBinder;
- return binder != null;
- }
-
- @Override
- public void play_impl() {
- sendTransportControlCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY);
- }
-
- @Override
- public void pause_impl() {
- sendTransportControlCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE);
- }
-
- @Override
- public void stop_impl() {
- sendTransportControlCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_STOP);
- }
-
- @Override
- public void skipToPlaylistItem_impl(MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- try {
- binder.skipToPlaylistItem(mControllerStub, item.toBundle());
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public void skipToPreviousItem_impl() {
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- try {
- binder.skipToPreviousItem(mControllerStub);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public void skipToNextItem_impl() {
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- try {
- binder.skipToNextItem(mControllerStub);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- private void sendTransportControlCommand(int commandCode) {
- sendTransportControlCommand(commandCode, null);
- }
-
- private void sendTransportControlCommand(int commandCode, Bundle args) {
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- try {
- binder.sendTransportControlCommand(mControllerStub, commandCode, args);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public PendingIntent getSessionActivity_impl() {
- return mSessionActivity;
- }
-
- @Override
- public void setVolumeTo_impl(int value, int flags) {
- // TODO(hdmoon): sanity check
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_SET_VOLUME);
- if (binder != null) {
- try {
- binder.setVolumeTo(mControllerStub, value, flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public void adjustVolume_impl(int direction, int flags) {
- // TODO(hdmoon): sanity check
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_SET_VOLUME);
- if (binder != null) {
- try {
- binder.adjustVolume(mControllerStub, direction, flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public void prepareFromUri_impl(Uri uri, Bundle extras) {
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_SESSION_PREPARE_FROM_URI);
- if (uri == null) {
- throw new IllegalArgumentException("uri shouldn't be null");
- }
- if (binder != null) {
- try {
- binder.prepareFromUri(mControllerStub, uri, extras);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- // TODO(jaewan): Handle.
- }
- }
-
- @Override
- public void prepareFromSearch_impl(String query, Bundle extras) {
- final IMediaSession2 binder = getSessionBinderIfAble(
- COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH);
- if (TextUtils.isEmpty(query)) {
- throw new IllegalArgumentException("query shouldn't be empty");
- }
- if (binder != null) {
- try {
- binder.prepareFromSearch(mControllerStub, query, extras);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- // TODO(jaewan): Handle.
- }
- }
-
- @Override
- public void prepareFromMediaId_impl(String mediaId, Bundle extras) {
- final IMediaSession2 binder = getSessionBinderIfAble(
- COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID);
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
- }
- if (binder != null) {
- try {
- binder.prepareFromMediaId(mControllerStub, mediaId, extras);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- // TODO(jaewan): Handle.
- }
- }
-
- @Override
- public void playFromUri_impl(Uri uri, Bundle extras) {
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_SESSION_PLAY_FROM_URI);
- if (uri == null) {
- throw new IllegalArgumentException("uri shouldn't be null");
- }
- if (binder != null) {
- try {
- binder.playFromUri(mControllerStub, uri, extras);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- // TODO(jaewan): Handle.
- }
- }
-
- @Override
- public void playFromSearch_impl(String query, Bundle extras) {
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_SESSION_PLAY_FROM_SEARCH);
- if (TextUtils.isEmpty(query)) {
- throw new IllegalArgumentException("query shouldn't be empty");
- }
- if (binder != null) {
- try {
- binder.playFromSearch(mControllerStub, query, extras);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- // TODO(jaewan): Handle.
- }
- }
-
- @Override
- public void playFromMediaId_impl(String mediaId, Bundle extras) {
- final IMediaSession2 binder = getSessionBinderIfAble(
- COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID);
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
- }
- if (binder != null) {
- try {
- binder.playFromMediaId(mControllerStub, mediaId, extras);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- // TODO(jaewan): Handle.
- }
- }
-
- @Override
- public void setRating_impl(String mediaId, Rating2 rating) {
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
- }
- if (rating == null) {
- throw new IllegalArgumentException("rating shouldn't be null");
- }
-
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- try {
- binder.setRating(mControllerStub, mediaId, rating.toBundle());
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- // TODO(jaewan): Handle.
- }
- }
-
- @Override
- public void sendCustomCommand_impl(SessionCommand2 command, Bundle args, ResultReceiver cb) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- final IMediaSession2 binder = getSessionBinderIfAble(command);
- if (binder != null) {
- try {
- binder.sendCustomCommand(mControllerStub, command.toBundle(), args, cb);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public List<MediaItem2> getPlaylist_impl() {
- synchronized (mLock) {
- return mPlaylist;
- }
- }
-
- @Override
- public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
- if (list == null) {
- throw new IllegalArgumentException("list shouldn't be null");
- }
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_LIST);
- if (binder != null) {
- List<Bundle> bundleList = new ArrayList<>();
- for (int i = 0; i < list.size(); i++) {
- bundleList.add(list.get(i).toBundle());
- }
- Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
- try {
- binder.setPlaylist(mControllerStub, bundleList, metadataBundle);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public MediaMetadata2 getPlaylistMetadata_impl() {
- synchronized (mLock) {
- return mPlaylistMetadata;
- }
- }
-
- @Override
- public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
- final IMediaSession2 binder = getSessionBinderIfAble(
- COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
- if (binder != null) {
- Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
- try {
- binder.updatePlaylistMetadata(mControllerStub, metadataBundle);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public void prepare_impl() {
- sendTransportControlCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE);
- }
-
- @Override
- public void fastForward_impl() {
- // TODO(jaewan): Implement this. Note that fast forward isn't a transport command anymore
- //sendTransportControlCommand(MediaSession2.COMMAND_CODE_SESSION_FAST_FORWARD);
- }
-
- @Override
- public void rewind_impl() {
- // TODO(jaewan): Implement this. Note that rewind isn't a transport command anymore
- //sendTransportControlCommand(MediaSession2.COMMAND_CODE_SESSION_REWIND);
- }
-
- @Override
- public void seekTo_impl(long pos) {
- if (pos < 0) {
- throw new IllegalArgumentException("position shouldn't be negative");
- }
- Bundle args = new Bundle();
- args.putLong(MediaSession2Stub.ARGUMENT_KEY_POSITION, pos);
- sendTransportControlCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO, args);
- }
-
- @Override
- public void addPlaylistItem_impl(int index, MediaItem2 item) {
- if (index < 0) {
- throw new IllegalArgumentException("index shouldn't be negative");
- }
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_ADD_ITEM);
- if (binder != null) {
- try {
- binder.addPlaylistItem(mControllerStub, index, item.toBundle());
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public void removePlaylistItem_impl(MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
- if (binder != null) {
- try {
- binder.removePlaylistItem(mControllerStub, item.toBundle());
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public void replacePlaylistItem_impl(int index, MediaItem2 item) {
- if (index < 0) {
- throw new IllegalArgumentException("index shouldn't be negative");
- }
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
- if (binder != null) {
- try {
- binder.replacePlaylistItem(mControllerStub, index, item.toBundle());
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public int getShuffleMode_impl() {
- return mShuffleMode;
- }
-
- @Override
- public void setShuffleMode_impl(int shuffleMode) {
- final IMediaSession2 binder = getSessionBinderIfAble(
- COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE);
- if (binder != null) {
- try {
- binder.setShuffleMode(mControllerStub, shuffleMode);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public int getRepeatMode_impl() {
- return mRepeatMode;
- }
-
- @Override
- public void setRepeatMode_impl(int repeatMode) {
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE);
- if (binder != null) {
- try {
- binder.setRepeatMode(mControllerStub, repeatMode);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- }
-
- @Override
- public PlaybackInfo getPlaybackInfo_impl() {
- synchronized (mLock) {
- return mPlaybackInfo;
- }
- }
-
- @Override
- public int getPlayerState_impl() {
- synchronized (mLock) {
- return mPlayerState;
- }
- }
-
- @Override
- public long getCurrentPosition_impl() {
- synchronized (mLock) {
- long timeDiff = System.currentTimeMillis() - mPositionEventTimeMs;
- long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
- return Math.max(0, expectedPosition);
- }
- }
-
- @Override
- public float getPlaybackSpeed_impl() {
- synchronized (mLock) {
- return mPlaybackSpeed;
- }
- }
-
- @Override
- public long getBufferedPosition_impl() {
- synchronized (mLock) {
- return mBufferedPositionMs;
- }
- }
-
- @Override
- public MediaItem2 getCurrentMediaItem_impl() {
- // TODO(jaewan): Implement
- return null;
- }
-
- void pushPlayerStateChanges(final int state) {
- synchronized (mLock) {
- mPlayerState = state;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlayerStateChanged(mInstance, state);
- });
- }
-
- // TODO(jaewan): Rename to seek completed
- void pushPositionChanges(final long eventTimeMs, final long positionMs) {
- synchronized (mLock) {
- mPositionEventTimeMs = eventTimeMs;
- mPositionMs = positionMs;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onSeekCompleted(mInstance, positionMs);
- });
- }
-
- void pushPlaybackSpeedChanges(final float speed) {
- synchronized (mLock) {
- mPlaybackSpeed = speed;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaybackSpeedChanged(mInstance, speed);
- });
- }
-
- void pushBufferedPositionChanges(final long bufferedPositionMs) {
- synchronized (mLock) {
- mBufferedPositionMs = bufferedPositionMs;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- // TODO(jaewan): Fix this -- it's now buffered state
- //mCallback.onBufferedPositionChanged(mInstance, bufferedPositionMs);
- });
- }
-
- void pushPlaybackInfoChanges(final PlaybackInfo info) {
- synchronized (mLock) {
- mPlaybackInfo = info;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaybackInfoChanged(mInstance, info);
- });
- }
-
- void pushPlaylistChanges(final List<MediaItem2> playlist, final MediaMetadata2 metadata) {
- synchronized (mLock) {
- mPlaylist = playlist;
- mPlaylistMetadata = metadata;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaylistChanged(mInstance, playlist, metadata);
- });
- }
-
- void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
- synchronized (mLock) {
- mPlaylistMetadata = metadata;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaylistMetadataChanged(mInstance, metadata);
- });
- }
-
- void pushShuffleModeChanges(int shuffleMode) {
- synchronized (mLock) {
- mShuffleMode = shuffleMode;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onShuffleModeChanged(mInstance, shuffleMode);
- });
- }
-
- void pushRepeatModeChanges(int repeatMode) {
- synchronized (mLock) {
- mRepeatMode = repeatMode;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onRepeatModeChanged(mInstance, repeatMode);
- });
- }
-
- void pushError(int errorCode, Bundle extras) {
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onError(mInstance, errorCode, extras);
- });
- }
-
- // Should be used without a lock to prevent potential deadlock.
- void onConnectedNotLocked(IMediaSession2 sessionBinder,
- final SessionCommandGroup2 allowedCommands,
- final int playerState,
- final long positionEventTimeMs,
- final long positionMs,
- final float playbackSpeed,
- final long bufferedPositionMs,
- final PlaybackInfo info,
- final int repeatMode,
- final int shuffleMode,
- final List<MediaItem2> playlist,
- final PendingIntent sessionActivity) {
- if (DEBUG) {
- Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
- + ", allowedCommands=" + allowedCommands);
- }
- boolean close = false;
- try {
- if (sessionBinder == null || allowedCommands == null) {
- // Connection rejected.
- close = true;
- return;
- }
- synchronized (mLock) {
- if (mIsReleased) {
- return;
- }
- if (mSessionBinder != null) {
- Log.e(TAG, "Cannot be notified about the connection result many times."
- + " Probably a bug or malicious app.");
- close = true;
- return;
- }
- mAllowedCommands = allowedCommands;
- mPlayerState = playerState;
- mPositionEventTimeMs = positionEventTimeMs;
- mPositionMs = positionMs;
- mPlaybackSpeed = playbackSpeed;
- mBufferedPositionMs = bufferedPositionMs;
- mPlaybackInfo = info;
- mRepeatMode = repeatMode;
- mShuffleMode = shuffleMode;
- mPlaylist = playlist;
- mSessionActivity = sessionActivity;
- mSessionBinder = sessionBinder;
- try {
- // Implementation for the local binder is no-op,
- // so can be used without worrying about deadlock.
- mSessionBinder.asBinder().linkToDeath(mDeathRecipient, 0);
- } catch (RemoteException e) {
- if (DEBUG) {
- Log.d(TAG, "Session died too early.", e);
- }
- close = true;
- return;
- }
- }
- // TODO(jaewan): Keep commands to prevents illegal API calls.
- mCallbackExecutor.execute(() -> {
- // Note: We may trigger ControllerCallbacks with the initial values
- // But it's hard to define the order of the controller callbacks
- // Only notify about the
- mCallback.onConnected(mInstance, allowedCommands);
- });
- } finally {
- if (close) {
- // Trick to call release() without holding the lock, to prevent potential deadlock
- // with the developer's custom lock within the ControllerCallback.onDisconnected().
- mInstance.close();
- }
- }
- }
-
- void onCustomCommand(final SessionCommand2 command, final Bundle args,
- final ResultReceiver receiver) {
- if (DEBUG) {
- Log.d(TAG, "onCustomCommand cmd=" + command);
- }
- mCallbackExecutor.execute(() -> {
- // TODO(jaewan): Double check if the controller exists.
- mCallback.onCustomCommand(mInstance, command, args, receiver);
- });
- }
-
- void onAllowedCommandsChanged(final SessionCommandGroup2 commands) {
- mCallbackExecutor.execute(() -> {
- mCallback.onAllowedCommandsChanged(mInstance, commands);
- });
- }
-
- void onCustomLayoutChanged(final List<CommandButton> layout) {
- mCallbackExecutor.execute(() -> {
- mCallback.onCustomLayoutChanged(mInstance, layout);
- });
- }
-
- // This will be called on the main thread.
- private class SessionServiceConnection implements ServiceConnection {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- // Note that it's always main-thread.
- if (DEBUG) {
- Log.d(TAG, "onServiceConnected " + name + " " + this);
- }
- // Sanity check
- if (!mToken.getPackageName().equals(name.getPackageName())) {
- Log.wtf(TAG, name + " was connected, but expected pkg="
- + mToken.getPackageName() + " with id=" + mToken.getId());
- return;
- }
- final IMediaSession2 sessionBinder = IMediaSession2.Stub.asInterface(service);
- connectToSession(sessionBinder);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- // Temporal lose of the binding because of the service crash. System will automatically
- // rebind, so just no-op.
- // TODO(jaewan): Really? Either disconnect cleanly or
- if (DEBUG) {
- Log.w(TAG, "Session service " + name + " is disconnected.");
- }
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- // Permanent lose of the binding because of the service package update or removed.
- // This SessionServiceRecord will be removed accordingly, but forget session binder here
- // for sure.
- mInstance.close();
- }
- }
-
- public static final class PlaybackInfoImpl implements PlaybackInfoProvider {
-
- private static final String KEY_PLAYBACK_TYPE =
- "android.media.playbackinfo_impl.playback_type";
- private static final String KEY_CONTROL_TYPE =
- "android.media.playbackinfo_impl.control_type";
- private static final String KEY_MAX_VOLUME =
- "android.media.playbackinfo_impl.max_volume";
- private static final String KEY_CURRENT_VOLUME =
- "android.media.playbackinfo_impl.current_volume";
- private static final String KEY_AUDIO_ATTRIBUTES =
- "android.media.playbackinfo_impl.audio_attrs";
-
- private final PlaybackInfo mInstance;
-
- private final int mPlaybackType;
- private final int mControlType;
- private final int mMaxVolume;
- private final int mCurrentVolume;
- private final AudioAttributes mAudioAttrs;
-
- private PlaybackInfoImpl(int playbackType, AudioAttributes attrs, int controlType,
- int max, int current) {
- mPlaybackType = playbackType;
- mAudioAttrs = attrs;
- mControlType = controlType;
- mMaxVolume = max;
- mCurrentVolume = current;
- mInstance = new PlaybackInfo(this);
- }
-
- @Override
- public int getPlaybackType_impl() {
- return mPlaybackType;
- }
-
- @Override
- public AudioAttributes getAudioAttributes_impl() {
- return mAudioAttrs;
- }
-
- @Override
- public int getControlType_impl() {
- return mControlType;
- }
-
- @Override
- public int getMaxVolume_impl() {
- return mMaxVolume;
- }
-
- @Override
- public int getCurrentVolume_impl() {
- return mCurrentVolume;
- }
-
- PlaybackInfo getInstance() {
- return mInstance;
- }
-
- Bundle toBundle() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_PLAYBACK_TYPE, mPlaybackType);
- bundle.putInt(KEY_CONTROL_TYPE, mControlType);
- bundle.putInt(KEY_MAX_VOLUME, mMaxVolume);
- bundle.putInt(KEY_CURRENT_VOLUME, mCurrentVolume);
- bundle.putParcelable(KEY_AUDIO_ATTRIBUTES, mAudioAttrs);
- return bundle;
- }
-
- static PlaybackInfo createPlaybackInfo(int playbackType, AudioAttributes attrs,
- int controlType, int max, int current) {
- return new PlaybackInfoImpl(playbackType, attrs, controlType, max, current)
- .getInstance();
- }
-
- static PlaybackInfo fromBundle(Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- final int volumeType = bundle.getInt(KEY_PLAYBACK_TYPE);
- final int volumeControl = bundle.getInt(KEY_CONTROL_TYPE);
- final int maxVolume = bundle.getInt(KEY_MAX_VOLUME);
- final int currentVolume = bundle.getInt(KEY_CURRENT_VOLUME);
- final AudioAttributes attrs = bundle.getParcelable(KEY_AUDIO_ATTRIBUTES);
-
- return createPlaybackInfo(volumeType, attrs, volumeControl, maxVolume, currentVolume);
- }
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Stub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
deleted file mode 100644
index 5b71e65..0000000
--- a/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
+++ /dev/null
@@ -1,322 +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 com.android.media;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.MediaController2;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaSession2.CommandButton;
-import android.media.SessionCommand2;
-import android.media.SessionCommandGroup2;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.media.MediaController2Impl.PlaybackInfoImpl;
-import com.android.media.MediaSession2Impl.CommandButtonImpl;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-public class MediaController2Stub extends IMediaController2.Stub {
- private static final String TAG = "MediaController2Stub";
- private static final boolean DEBUG = true; // TODO(jaewan): Change
-
- private final WeakReference<MediaController2Impl> mController;
-
- MediaController2Stub(MediaController2Impl controller) {
- mController = new WeakReference<>(controller);
- }
-
- private MediaController2Impl getController() throws IllegalStateException {
- final MediaController2Impl controller = mController.get();
- if (controller == null) {
- throw new IllegalStateException("Controller is released");
- }
- return controller;
- }
-
- public void destroy() {
- mController.clear();
- }
-
- @Override
- public void onPlayerStateChanged(int state) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- controller.pushPlayerStateChanges(state);
- }
-
- @Override
- public void onPositionChanged(long eventTimeMs, long positionMs) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- if (eventTimeMs < 0) {
- Log.w(TAG, "onPositionChanged(): Ignoring negative eventTimeMs");
- return;
- }
- if (positionMs < 0) {
- Log.w(TAG, "onPositionChanged(): Ignoring negative positionMs");
- return;
- }
- controller.pushPositionChanges(eventTimeMs, positionMs);
- }
-
- @Override
- public void onPlaybackSpeedChanged(float speed) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- controller.pushPlaybackSpeedChanges(speed);
- }
-
- @Override
- public void onBufferedPositionChanged(long bufferedPositionMs) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- if (bufferedPositionMs < 0) {
- Log.w(TAG, "onBufferedPositionChanged(): Ignoring negative bufferedPositionMs");
- return;
- }
- controller.pushBufferedPositionChanges(bufferedPositionMs);
- }
-
- @Override
- public void onPlaylistChanged(List<Bundle> playlistBundle, Bundle metadataBundle) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- if (playlistBundle == null) {
- Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist from " + controller);
- return;
- }
- List<MediaItem2> playlist = new ArrayList<>();
- for (Bundle bundle : playlistBundle) {
- MediaItem2 item = MediaItem2.fromBundle(bundle);
- if (item == null) {
- Log.w(TAG, "onPlaylistChanged(): Ignoring null item in playlist");
- } else {
- playlist.add(item);
- }
- }
- MediaMetadata2 metadata = MediaMetadata2.fromBundle(metadataBundle);
- controller.pushPlaylistChanges(playlist, metadata);
- }
-
- @Override
- public void onPlaylistMetadataChanged(Bundle metadataBundle) throws RuntimeException {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- MediaMetadata2 metadata = MediaMetadata2.fromBundle(metadataBundle);
- controller.pushPlaylistMetadataChanges(metadata);
- }
-
- @Override
- public void onRepeatModeChanged(int repeatMode) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- controller.pushRepeatModeChanges(repeatMode);
- }
-
- @Override
- public void onPlaybackInfoChanged(Bundle playbackInfo) throws RuntimeException {
- if (DEBUG) {
- Log.d(TAG, "onPlaybackInfoChanged");
- }
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- MediaController2.PlaybackInfo info = PlaybackInfoImpl.fromBundle(playbackInfo);
- if (info == null) {
- Log.w(TAG, "onPlaybackInfoChanged(): Ignoring null playbackInfo");
- return;
- }
- controller.pushPlaybackInfoChanges(info);
- }
-
- @Override
- public void onShuffleModeChanged(int shuffleMode) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- controller.pushShuffleModeChanges(shuffleMode);
- }
-
- @Override
- public void onError(int errorCode, Bundle extras) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- controller.pushError(errorCode, extras);
- }
-
- @Override
- public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
- int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
- long bufferedPositionMs, Bundle playbackInfo, int shuffleMode, int repeatMode,
- List<Bundle> itemBundleList, PendingIntent sessionActivity) {
- final MediaController2Impl controller = mController.get();
- if (controller == null) {
- if (DEBUG) {
- Log.d(TAG, "onConnected after MediaController2.close()");
- }
- return;
- }
- final Context context = controller.getContext();
- List<MediaItem2> itemList = null;
- if (itemBundleList != null) {
- itemList = new ArrayList<>();
- for (int i = 0; i < itemBundleList.size(); i++) {
- MediaItem2 item = MediaItem2.fromBundle(itemBundleList.get(i));
- if (item != null) {
- itemList.add(item);
- }
- }
- }
- controller.onConnectedNotLocked(sessionBinder,
- SessionCommandGroup2.fromBundle(commandGroup),
- playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
- PlaybackInfoImpl.fromBundle(playbackInfo), repeatMode, shuffleMode,
- itemList, sessionActivity);
- }
-
- @Override
- public void onDisconnected() {
- final MediaController2Impl controller = mController.get();
- if (controller == null) {
- if (DEBUG) {
- Log.d(TAG, "onDisconnected after MediaController2.close()");
- }
- return;
- }
- controller.getInstance().close();
- }
-
- @Override
- public void onCustomLayoutChanged(List<Bundle> commandButtonlist) {
- if (commandButtonlist == null) {
- Log.w(TAG, "onCustomLayoutChanged(): Ignoring null commandButtonlist");
- return;
- }
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- if (controller == null) {
- // TODO(jaewan): Revisit here. Could be a bug
- return;
- }
- List<CommandButton> layout = new ArrayList<>();
- for (int i = 0; i < commandButtonlist.size(); i++) {
- CommandButton button = CommandButtonImpl.fromBundle(commandButtonlist.get(i));
- if (button != null) {
- layout.add(button);
- }
- }
- controller.onCustomLayoutChanged(layout);
- }
-
- @Override
- public void onAllowedCommandsChanged(Bundle commandsBundle) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- if (controller == null) {
- // TODO(jaewan): Revisit here. Could be a bug
- return;
- }
- SessionCommandGroup2 commands = SessionCommandGroup2.fromBundle(commandsBundle);
- if (commands == null) {
- Log.w(TAG, "onAllowedCommandsChanged(): Ignoring null commands");
- return;
- }
- controller.onAllowedCommandsChanged(commands);
- }
-
- @Override
- public void onCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
- final MediaController2Impl controller;
- try {
- controller = getController();
- } catch (IllegalStateException e) {
- Log.w(TAG, "Don't fail silently here. Highly likely a bug");
- return;
- }
- SessionCommand2 command = SessionCommand2.fromBundle(commandBundle);
- if (command == null) {
- Log.w(TAG, "onCustomCommand(): Ignoring null command");
- return;
- }
- controller.onCustomCommand(command, args, receiver);
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
deleted file mode 100644
index 910a0f1..0000000
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ /dev/null
@@ -1,223 +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 com.android.media;
-
-import static android.media.MediaItem2.FLAG_BROWSABLE;
-import static android.media.MediaItem2.FLAG_PLAYABLE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaItem2.Builder;
-import android.media.MediaItem2.Flags;
-import android.media.MediaMetadata2;
-import android.media.update.MediaItem2Provider;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import java.util.UUID;
-
-public class MediaItem2Impl implements MediaItem2Provider {
- private static final String KEY_ID = "android.media.mediaitem2.id";
- private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
- private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
- private static final String KEY_UUID = "android.media.mediaitem2.uuid";
-
- private final MediaItem2 mInstance;
- private final String mId;
- private final int mFlags;
- private final UUID mUUID;
- private MediaMetadata2 mMetadata;
- private DataSourceDesc mDataSourceDesc;
-
- // From the public API
- public MediaItem2Impl(@NonNull String mediaId, @Nullable DataSourceDesc dsd,
- @Nullable MediaMetadata2 metadata, @Flags int flags) {
- this(mediaId, dsd, metadata, flags, null);
- }
-
- private MediaItem2Impl(@NonNull String mediaId, @Nullable DataSourceDesc dsd,
- @Nullable MediaMetadata2 metadata, @Flags int flags, @Nullable UUID uuid) {
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
- }
- if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
- throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
- }
-
- mId = mediaId;
- mDataSourceDesc = dsd;
- mMetadata = metadata;
- mFlags = flags;
- mUUID = (uuid == null) ? UUID.randomUUID() : uuid;
-
- mInstance = new MediaItem2(this);
- }
-
- @Override
- public boolean equals_impl(Object obj) {
- if (!(obj instanceof MediaItem2)) {
- return false;
- }
- MediaItem2 other = (MediaItem2) obj;
- return mUUID.equals(((MediaItem2Impl) other.getProvider()).mUUID);
- }
-
- /**
- * Return this object as a bundle to share between processes.
- *
- * @return a new bundle instance
- */
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putString(KEY_ID, mId);
- bundle.putInt(KEY_FLAGS, mFlags);
- if (mMetadata != null) {
- bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
- }
- bundle.putString(KEY_UUID, mUUID.toString());
- return bundle;
- }
-
- /**
- * Create a MediaItem2 from the {@link Bundle}.
- *
- * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
- * @return The newly created MediaItem2
- */
- public static MediaItem2 fromBundle_impl(@NonNull Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- final String uuidString = bundle.getString(KEY_UUID);
- return fromBundle(bundle, UUID.fromString(uuidString));
- }
-
- /**
- * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}.
- * If {@link UUID}
- * can be null for creating new.
- *
- * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
- * @param uuid A {@link UUID} to override. Can be {@link null} for override.
- * @return The newly created MediaItem2
- */
- static MediaItem2 fromBundle(@NonNull Bundle bundle, @Nullable UUID uuid) {
- if (bundle == null) {
- return null;
- }
- final String id = bundle.getString(KEY_ID);
- final Bundle metadataBundle = bundle.getBundle(KEY_METADATA);
- final MediaMetadata2 metadata = MediaMetadata2.fromBundle(metadataBundle);
- final int flags = bundle.getInt(KEY_FLAGS);
- return new MediaItem2Impl(id, null, metadata, flags, uuid).getInstance();
- }
-
- private MediaItem2 getInstance() {
- return mInstance;
- }
-
- @Override
- public String toString_impl() {
- final StringBuilder sb = new StringBuilder("MediaItem2{");
- sb.append("mFlags=").append(mFlags);
- sb.append(", mMetadata=").append(mMetadata);
- sb.append('}');
- return sb.toString();
- }
-
- @Override
- public @Flags int getFlags_impl() {
- return mFlags;
- }
-
- @Override
- public boolean isBrowsable_impl() {
- return (mFlags & FLAG_BROWSABLE) != 0;
- }
-
- @Override
- public boolean isPlayable_impl() {
- return (mFlags & FLAG_PLAYABLE) != 0;
- }
-
- @Override
- public void setMetadata_impl(@Nullable MediaMetadata2 metadata) {
- if (metadata != null && !TextUtils.equals(mId, metadata.getMediaId())) {
- throw new IllegalArgumentException("metadata's id should be matched with the mediaId");
- }
- mMetadata = metadata;
- }
-
- @Override
- public @Nullable MediaMetadata2 getMetadata_impl() {
- return mMetadata;
- }
-
- @Override
- public @NonNull String getMediaId_impl() {
- return mId;
- }
-
- @Override
- public @Nullable DataSourceDesc getDataSourceDesc_impl() {
- return mDataSourceDesc;
- }
-
- public static class BuilderImpl implements MediaItem2Provider.BuilderProvider {
- private Builder mInstance;
- private @Flags int mFlags;
- private String mMediaId;
- private MediaMetadata2 mMetadata;
- private DataSourceDesc mDataSourceDesc;
-
- public BuilderImpl(Builder instance, int flags) {
- mInstance = instance;
- mFlags = flags;
- }
-
- @Override
- public Builder setMediaId_impl(@Nullable String mediaId) {
- mMediaId = mediaId;
- return mInstance;
- }
-
- @Override
- public Builder setMetadata_impl(@Nullable MediaMetadata2 metadata) {
- mMetadata = metadata;
- return mInstance;
- }
-
- @Override
- public Builder setDataSourceDesc_impl(@Nullable DataSourceDesc dataSourceDesc) {
- mDataSourceDesc = dataSourceDesc;
- return mInstance;
- }
-
- @Override
- public MediaItem2 build_impl() {
- String id = (mMetadata != null)
- ? mMetadata.getString(MediaMetadata2.METADATA_KEY_MEDIA_ID) : null;
- if (id == null) {
- // TODO(jaewan): Double check if its sufficient (e.g. Use UUID instead?)
- id = (mMediaId != null) ? mMediaId : toString();
- }
- return new MediaItem2Impl(id, mDataSourceDesc, mMetadata, mFlags).getInstance();
- }
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
deleted file mode 100644
index cf1c532..0000000
--- a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
+++ /dev/null
@@ -1,373 +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 com.android.media;
-
-import static android.media.MediaMetadata2.*;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap;
-import android.media.MediaMetadata2;
-import android.media.MediaMetadata2.BitmapKey;
-import android.media.MediaMetadata2.Builder;
-import android.media.MediaMetadata2.LongKey;
-import android.media.MediaMetadata2.RatingKey;
-import android.media.MediaMetadata2.TextKey;
-import android.media.Rating2;
-import android.media.update.MediaMetadata2Provider;
-import android.os.Bundle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import java.util.Set;
-
-public class MediaMetadata2Impl implements MediaMetadata2Provider {
- private static final String TAG = "MediaMetadata2";
-
- static final int METADATA_TYPE_LONG = 0;
- static final int METADATA_TYPE_TEXT = 1;
- static final int METADATA_TYPE_BITMAP = 2;
- static final int METADATA_TYPE_RATING = 3;
- static final int METADATA_TYPE_FLOAT = 4;
- static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
-
- static {
- METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
- METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
- }
-
- private static final @TextKey
- String[] PREFERRED_DESCRIPTION_ORDER = {
- METADATA_KEY_TITLE,
- METADATA_KEY_ARTIST,
- METADATA_KEY_ALBUM,
- METADATA_KEY_ALBUM_ARTIST,
- METADATA_KEY_WRITER,
- METADATA_KEY_AUTHOR,
- METADATA_KEY_COMPOSER
- };
-
- private static final @BitmapKey
- String[] PREFERRED_BITMAP_ORDER = {
- METADATA_KEY_DISPLAY_ICON,
- METADATA_KEY_ART,
- METADATA_KEY_ALBUM_ART
- };
-
- private static final @TextKey
- String[] PREFERRED_URI_ORDER = {
- METADATA_KEY_DISPLAY_ICON_URI,
- METADATA_KEY_ART_URI,
- METADATA_KEY_ALBUM_ART_URI
- };
-
- private final MediaMetadata2 mInstance;
- private final Bundle mBundle;
-
- public MediaMetadata2Impl(Bundle bundle) {
- mInstance = new MediaMetadata2(this);
- mBundle = bundle;
- }
-
- public MediaMetadata2 getInstance() {
- return mInstance;
- }
-
- @Override
- public boolean containsKey_impl(String key) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- return mBundle.containsKey(key);
- }
-
- @Override
- public CharSequence getText_impl(@TextKey String key) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- return mBundle.getCharSequence(key);
- }
-
- @Override
- public @Nullable String getMediaId_impl() {
- return mInstance.getString(METADATA_KEY_MEDIA_ID);
- }
-
- @Override
- public String getString_impl(@TextKey String key) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- CharSequence text = mBundle.getCharSequence(key);
- if (text != null) {
- return text.toString();
- }
- return null;
- }
-
- @Override
- public long getLong_impl(@LongKey String key) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- return mBundle.getLong(key, 0);
- }
-
- @Override
- public Rating2 getRating_impl(@RatingKey String key) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- // TODO(jaewan): Add backward compatibility
- Rating2 rating = null;
- try {
- rating = Rating2.fromBundle(mBundle.getBundle(key));
- } catch (Exception e) {
- // ignore, value was not a rating
- Log.w(TAG, "Failed to retrieve a key as Rating.", e);
- }
- return rating;
- }
-
- @Override
- public float getFloat_impl(@FloatKey String key) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- return mBundle.getFloat(key);
- }
-
- @Override
- public Bitmap getBitmap_impl(@BitmapKey String key) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- Bitmap bmp = null;
- try {
- bmp = mBundle.getParcelable(key);
- } catch (Exception e) {
- // ignore, value was not a bitmap
- Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
- }
- return bmp;
- }
-
- @Override
- public Bundle getExtras_impl() {
- try {
- return mBundle.getBundle(METADATA_KEY_EXTRAS);
- } catch (Exception e) {
- // ignore, value was not an bundle
- Log.w(TAG, "Failed to retrieve an extra");
- }
- return null;
- }
-
- @Override
- public int size_impl() {
- return mBundle.size();
- }
-
- @Override
- public Set<String> keySet_impl() {
- return mBundle.keySet();
- }
-
- @Override
- public Bundle toBundle_impl() {
- return mBundle;
- }
-
- public static MediaMetadata2 fromBundle_impl(Bundle bundle) {
- return (bundle == null) ? null : new MediaMetadata2Impl(bundle).getInstance();
- }
-
- public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
- private final MediaMetadata2.Builder mInstance;
- private final Bundle mBundle;
-
- public BuilderImpl(MediaMetadata2.Builder instance) {
- mInstance = instance;
- mBundle = new Bundle();
- }
-
- public BuilderImpl(MediaMetadata2.Builder instance, MediaMetadata2 source) {
- if (source == null) {
- throw new IllegalArgumentException("source shouldn't be null");
- }
- mInstance = instance;
- mBundle = new Bundle(source.toBundle());
- }
-
- public BuilderImpl(int maxBitmapSize) {
- mInstance = new MediaMetadata2.Builder(this);
- mBundle = new Bundle();
-
- for (String key : mBundle.keySet()) {
- Object value = mBundle.get(key);
- if (value instanceof Bitmap) {
- Bitmap bmp = (Bitmap) value;
- if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
- mInstance.putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
- }
- }
- }
- }
-
- @Override
- public Builder putText_impl(@TextKey String key, CharSequence value) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- if (METADATA_KEYS_TYPE.containsKey(key)) {
- if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
- throw new IllegalArgumentException("The " + key
- + " key cannot be used to put a CharSequence");
- }
- }
- mBundle.putCharSequence(key, value);
- return mInstance;
- }
-
- @Override
- public Builder putString_impl(@TextKey String key, String value) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- if (METADATA_KEYS_TYPE.containsKey(key)) {
- if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
- throw new IllegalArgumentException("The " + key
- + " key cannot be used to put a String");
- }
- }
- mBundle.putCharSequence(key, value);
- return mInstance;
- }
-
- @Override
- public Builder putLong_impl(@LongKey String key, long value) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- if (METADATA_KEYS_TYPE.containsKey(key)) {
- if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
- throw new IllegalArgumentException("The " + key
- + " key cannot be used to put a long");
- }
- }
- mBundle.putLong(key, value);
- return mInstance;
- }
-
- @Override
- public Builder putRating_impl(@RatingKey String key, Rating2 value) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- if (METADATA_KEYS_TYPE.containsKey(key)) {
- if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
- throw new IllegalArgumentException("The " + key
- + " key cannot be used to put a Rating");
- }
- }
- mBundle.putBundle(key, value.toBundle());
- return mInstance;
- }
-
- @Override
- public Builder putBitmap_impl(@BitmapKey String key, Bitmap value) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- if (METADATA_KEYS_TYPE.containsKey(key)) {
- if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
- throw new IllegalArgumentException("The " + key
- + " key cannot be used to put a Bitmap");
- }
- }
- mBundle.putParcelable(key, value);
- return mInstance;
- }
-
- @Override
- public Builder putFloat_impl(@FloatKey String key, float value) {
- if (key == null) {
- throw new IllegalArgumentException("key shouldn't be null");
- }
- if (METADATA_KEYS_TYPE.containsKey(key)) {
- if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_FLOAT) {
- throw new IllegalArgumentException("The " + key
- + " key cannot be used to put a float");
- }
- }
- mBundle.putFloat(key, value);
- return mInstance;
- }
-
- @Override
- public Builder setExtras_impl(Bundle bundle) {
- mBundle.putBundle(METADATA_KEY_EXTRAS, bundle);
- return mInstance;
- }
-
- @Override
- public MediaMetadata2 build_impl() {
- return new MediaMetadata2Impl(mBundle).getInstance();
- }
-
- private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
- float maxSizeF = maxSize;
- float widthScale = maxSizeF / bmp.getWidth();
- float heightScale = maxSizeF / bmp.getHeight();
- float scale = Math.min(widthScale, heightScale);
- int height = (int) (bmp.getHeight() * scale);
- int width = (int) (bmp.getWidth() * scale);
- return Bitmap.createScaledBitmap(bmp, width, height, true);
- }
- }
-}
-
diff --git a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
deleted file mode 100644
index dfd4e1a..0000000
--- a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
+++ /dev/null
@@ -1,224 +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 com.android.media;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlaylistAgent;
-import android.media.MediaPlaylistAgent.PlaylistEventCallback;
-import android.media.update.MediaPlaylistAgentProvider;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-public class MediaPlaylistAgentImpl implements MediaPlaylistAgentProvider {
- private static final String TAG = "MediaPlaylistAgent";
-
- private final MediaPlaylistAgent mInstance;
-
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private final ArrayMap<PlaylistEventCallback, Executor> mCallbacks = new ArrayMap<>();
-
- public MediaPlaylistAgentImpl(MediaPlaylistAgent instance) {
- mInstance = instance;
- }
-
- @Override
- final public void registerPlaylistEventCallback_impl(
- @NonNull @CallbackExecutor Executor executor, @NonNull PlaylistEventCallback callback) {
- if (executor == null) {
- throw new IllegalArgumentException("executor shouldn't be null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
-
- synchronized (mLock) {
- if (mCallbacks.get(callback) != null) {
- Log.w(TAG, "callback is already added. Ignoring.");
- return;
- }
- mCallbacks.put(callback, executor);
- }
- }
-
- @Override
- final public void unregisterPlaylistEventCallback_impl(
- @NonNull PlaylistEventCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
- synchronized (mLock) {
- mCallbacks.remove(callback);
- }
- }
-
- @Override
- final public void notifyPlaylistChanged_impl() {
- ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
- List<MediaItem2> playlist= mInstance.getPlaylist();
- MediaMetadata2 metadata = mInstance.getPlaylistMetadata();
- for (int i = 0; i < callbacks.size(); i++) {
- final PlaylistEventCallback callback = callbacks.keyAt(i);
- final Executor executor = callbacks.valueAt(i);
- executor.execute(() -> callback.onPlaylistChanged(
- mInstance, playlist, metadata));
- }
- }
-
- @Override
- final public void notifyPlaylistMetadataChanged_impl() {
- ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
- for (int i = 0; i < callbacks.size(); i++) {
- final PlaylistEventCallback callback = callbacks.keyAt(i);
- final Executor executor = callbacks.valueAt(i);
- executor.execute(() -> callback.onPlaylistMetadataChanged(
- mInstance, mInstance.getPlaylistMetadata()));
- }
- }
-
- @Override
- final public void notifyShuffleModeChanged_impl() {
- ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
- for (int i = 0; i < callbacks.size(); i++) {
- final PlaylistEventCallback callback = callbacks.keyAt(i);
- final Executor executor = callbacks.valueAt(i);
- executor.execute(() -> callback.onShuffleModeChanged(
- mInstance, mInstance.getShuffleMode()));
- }
- }
-
- @Override
- final public void notifyRepeatModeChanged_impl() {
- ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
- for (int i = 0; i < callbacks.size(); i++) {
- final PlaylistEventCallback callback = callbacks.keyAt(i);
- final Executor executor = callbacks.valueAt(i);
- executor.execute(() -> callback.onRepeatModeChanged(
- mInstance, mInstance.getRepeatMode()));
- }
- }
-
- @Override
- public @Nullable List<MediaItem2> getPlaylist_impl() {
- // empty implementation
- return null;
- }
-
- @Override
- public void setPlaylist_impl(@NonNull List<MediaItem2> list,
- @Nullable MediaMetadata2 metadata) {
- // empty implementation
- }
-
- @Override
- public @Nullable MediaMetadata2 getPlaylistMetadata_impl() {
- // empty implementation
- return null;
- }
-
- @Override
- public void updatePlaylistMetadata_impl(@Nullable MediaMetadata2 metadata) {
- // empty implementation
- }
-
- @Override
- public void addPlaylistItem_impl(int index, @NonNull MediaItem2 item) {
- // empty implementation
- }
-
- @Override
- public void removePlaylistItem_impl(@NonNull MediaItem2 item) {
- // empty implementation
- }
-
- @Override
- public void replacePlaylistItem_impl(int index, @NonNull MediaItem2 item) {
- // empty implementation
- }
-
- @Override
- public void skipToPlaylistItem_impl(@NonNull MediaItem2 item) {
- // empty implementation
- }
-
- @Override
- public void skipToPreviousItem_impl() {
- // empty implementation
- }
-
- @Override
- public void skipToNextItem_impl() {
- // empty implementation
- }
-
- @Override
- public int getRepeatMode_impl() {
- return MediaPlaylistAgent.REPEAT_MODE_NONE;
- }
-
- @Override
- public void setRepeatMode_impl(int repeatMode) {
- // empty implementation
- }
-
- @Override
- public int getShuffleMode_impl() {
- // empty implementation
- return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
- }
-
- @Override
- public void setShuffleMode_impl(int shuffleMode) {
- // empty implementation
- }
-
- @Override
- public @Nullable MediaItem2 getMediaItem_impl(@NonNull DataSourceDesc dsd) {
- if (dsd == null) {
- throw new IllegalArgumentException("dsd shouldn't be null");
- }
- List<MediaItem2> itemList = mInstance.getPlaylist();
- if (itemList == null) {
- return null;
- }
- for (int i = 0; i < itemList.size(); i++) {
- MediaItem2 item = itemList.get(i);
- if (item != null && item.getDataSourceDesc() == dsd) {
- return item;
- }
- }
- return null;
- }
-
- private ArrayMap<PlaylistEventCallback, Executor> getCallbacks() {
- ArrayMap<PlaylistEventCallback, Executor> callbacks = new ArrayMap<>();
- synchronized (mLock) {
- callbacks.putAll(mCallbacks);
- }
- return callbacks;
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
deleted file mode 100644
index cd9edb8..0000000
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ /dev/null
@@ -1,1541 +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 com.android.media;
-
-import static android.media.SessionCommand2.COMMAND_CODE_CUSTOM;
-import static android.media.SessionToken2.TYPE_LIBRARY_SERVICE;
-import static android.media.SessionToken2.TYPE_SESSION;
-import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.media.AudioAttributes;
-import android.media.AudioFocusRequest;
-import android.media.AudioManager;
-import android.media.DataSourceDesc;
-import android.media.MediaController2;
-import android.media.MediaController2.PlaybackInfo;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
-import android.media.MediaPlayerBase.PlayerEventCallback;
-import android.media.MediaPlayerBase.PlayerState;
-import android.media.MediaPlaylistAgent;
-import android.media.MediaPlaylistAgent.PlaylistEventCallback;
-import android.media.MediaSession2;
-import android.media.MediaSession2.Builder;
-import android.media.MediaSession2.CommandButton;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.OnDataSourceMissingHelper;
-import android.media.MediaSession2.SessionCallback;
-import android.media.SessionCommand2;
-import android.media.SessionCommandGroup2;
-import android.media.SessionToken2;
-import android.media.VolumeProvider2;
-import android.media.session.MediaSessionManager;
-import android.media.update.MediaSession2Provider;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-public class MediaSession2Impl implements MediaSession2Provider {
- private static final String TAG = "MediaSession2";
- private static final boolean DEBUG = true;//Log.isLoggable(TAG, Log.DEBUG);
-
- private final Object mLock = new Object();
-
- private final MediaSession2 mInstance;
- private final Context mContext;
- private final String mId;
- private final Executor mCallbackExecutor;
- private final SessionCallback mCallback;
- private final MediaSession2Stub mSessionStub;
- private final SessionToken2 mSessionToken;
- private final AudioManager mAudioManager;
- private final PendingIntent mSessionActivity;
- private final PlayerEventCallback mPlayerEventCallback;
- private final PlaylistEventCallback mPlaylistEventCallback;
-
- // mPlayer is set to null when the session is closed, and we shouldn't throw an exception
- // nor leave log always for using mPlayer when it's null. Here's the reason.
- // When a MediaSession2 is closed, there could be a pended operation in the session callback
- // executor that may want to access the player. Here's the sample code snippet for that.
- //
- // public void onFoo() {
- // if (mPlayer == null) return; // first check
- // mSessionCallbackExecutor.executor(() -> {
- // // Error. Session may be closed and mPlayer can be null here.
- // mPlayer.foo();
- // });
- // }
- //
- // By adding protective code, we can also protect APIs from being called after the close()
- //
- // TODO(jaewan): Should we put volatile here?
- @GuardedBy("mLock")
- private MediaPlayerBase mPlayer;
- @GuardedBy("mLock")
- private MediaPlaylistAgent mPlaylistAgent;
- @GuardedBy("mLock")
- private SessionPlaylistAgent mSessionPlaylistAgent;
- @GuardedBy("mLock")
- private VolumeProvider2 mVolumeProvider;
- @GuardedBy("mLock")
- private PlaybackInfo mPlaybackInfo;
- @GuardedBy("mLock")
- private OnDataSourceMissingHelper mDsmHelper;
-
- /**
- * Can be only called by the {@link Builder#build()}.
- * @param context
- * @param player
- * @param id
- * @param playlistAgent
- * @param volumeProvider
- * @param sessionActivity
- * @param callbackExecutor
- * @param callback
- */
- public MediaSession2Impl(Context context, MediaPlayerBase player, String id,
- MediaPlaylistAgent playlistAgent, VolumeProvider2 volumeProvider,
- PendingIntent sessionActivity,
- Executor callbackExecutor, SessionCallback callback) {
- // TODO(jaewan): Keep other params.
- mInstance = createInstance();
-
- // Argument checks are done by builder already.
- // Initialize finals first.
- mContext = context;
- mId = id;
- mCallback = callback;
- mCallbackExecutor = callbackExecutor;
- mSessionActivity = sessionActivity;
- mSessionStub = new MediaSession2Stub(this);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mPlayerEventCallback = new MyPlayerEventCallback(this);
- mPlaylistEventCallback = new MyPlaylistEventCallback(this);
-
- // Infer type from the id and package name.
- mSessionToken = new SessionToken2Impl(Process.myUid(), TYPE_SESSION,
- mContext.getPackageName(), null, id, mSessionStub).getInstance();
-
- updatePlayer(player, playlistAgent, volumeProvider);
-
- // Ask server for the sanity check, and starts
- // Sanity check for making session ID unique 'per package' cannot be done in here.
- // Server can only know if the package has another process and has another session with the
- // same id. Note that 'ID is unique per package' is important for controller to distinguish
- // a session in another package.
- MediaSessionManager manager =
- (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
- if (!manager.createSession2(mSessionToken)) {
- throw new IllegalStateException("Session with the same id is already used by"
- + " another process. Use MediaController2 instead.");
- }
- }
-
- MediaSession2 createInstance() {
- return new MediaSession2(this);
- }
-
- private static String getServiceName(Context context, String serviceAction, String id) {
- PackageManager manager = context.getPackageManager();
- Intent serviceIntent = new Intent(serviceAction);
- serviceIntent.setPackage(context.getPackageName());
- List<ResolveInfo> services = manager.queryIntentServices(serviceIntent,
- PackageManager.GET_META_DATA);
- String serviceName = null;
- if (services != null) {
- for (int i = 0; i < services.size(); i++) {
- String serviceId = SessionToken2Impl.getSessionId(services.get(i));
- if (serviceId != null && TextUtils.equals(id, serviceId)) {
- if (services.get(i).serviceInfo == null) {
- continue;
- }
- if (serviceName != null) {
- throw new IllegalArgumentException("Ambiguous session type. Multiple"
- + " session services define the same id=" + id);
- }
- serviceName = services.get(i).serviceInfo.name;
- }
- }
- }
- return serviceName;
- }
-
- @Override
- public void updatePlayer_impl(@NonNull MediaPlayerBase player, MediaPlaylistAgent playlistAgent,
- VolumeProvider2 volumeProvider) throws IllegalArgumentException {
- ensureCallingThread();
- if (player == null) {
- throw new IllegalArgumentException("player shouldn't be null");
- }
- updatePlayer(player, playlistAgent, volumeProvider);
- }
-
- private void updatePlayer(MediaPlayerBase player, MediaPlaylistAgent agent,
- VolumeProvider2 volumeProvider) {
- final MediaPlayerBase oldPlayer;
- final MediaPlaylistAgent oldAgent;
- final PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
- synchronized (mLock) {
- oldPlayer = mPlayer;
- oldAgent = mPlaylistAgent;
- mPlayer = player;
- if (agent == null) {
- mSessionPlaylistAgent = new SessionPlaylistAgent(this, mPlayer);
- if (mDsmHelper != null) {
- mSessionPlaylistAgent.setOnDataSourceMissingHelper(mDsmHelper);
- }
- agent = mSessionPlaylistAgent;
- }
- mPlaylistAgent = agent;
- mVolumeProvider = volumeProvider;
- mPlaybackInfo = info;
- }
- if (player != oldPlayer) {
- player.registerPlayerEventCallback(mCallbackExecutor, mPlayerEventCallback);
- if (oldPlayer != null) {
- // Warning: Poorly implement player may ignore this
- oldPlayer.unregisterPlayerEventCallback(mPlayerEventCallback);
- }
- }
- if (agent != oldAgent) {
- agent.registerPlaylistEventCallback(mCallbackExecutor, mPlaylistEventCallback);
- if (oldAgent != null) {
- // Warning: Poorly implement player may ignore this
- oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
- }
- }
-
- if (oldPlayer != null) {
- mSessionStub.notifyPlaybackInfoChanged(info);
- notifyPlayerUpdatedNotLocked(oldPlayer);
- }
- // TODO(jaewan): Repeat the same thing for the playlist agent.
- }
-
- private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
- PlaybackInfo info;
- if (volumeProvider == null) {
- int stream;
- if (attrs == null) {
- stream = AudioManager.STREAM_MUSIC;
- } else {
- stream = attrs.getVolumeControlStream();
- if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- // It may happen if the AudioAttributes doesn't have usage.
- // Change it to the STREAM_MUSIC because it's not supported by audio manager
- // for querying volume level.
- stream = AudioManager.STREAM_MUSIC;
- }
- }
- info = MediaController2Impl.PlaybackInfoImpl.createPlaybackInfo(
- PlaybackInfo.PLAYBACK_TYPE_LOCAL,
- attrs,
- mAudioManager.isVolumeFixed()
- ? VolumeProvider2.VOLUME_CONTROL_FIXED
- : VolumeProvider2.VOLUME_CONTROL_ABSOLUTE,
- mAudioManager.getStreamMaxVolume(stream),
- mAudioManager.getStreamVolume(stream));
- } else {
- info = MediaController2Impl.PlaybackInfoImpl.createPlaybackInfo(
- PlaybackInfo.PLAYBACK_TYPE_REMOTE /* ControlType */,
- attrs,
- volumeProvider.getControlType(),
- volumeProvider.getMaxVolume(),
- volumeProvider.getCurrentVolume());
- }
- return info;
- }
-
- @Override
- public void close_impl() {
- // Stop system service from listening this session first.
- MediaSessionManager manager =
- (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
- manager.destroySession2(mSessionToken);
-
- if (mSessionStub != null) {
- if (DEBUG) {
- Log.d(TAG, "session is now unavailable, id=" + mId);
- }
- // Invalidate previously published session stub.
- mSessionStub.destroyNotLocked();
- }
- final MediaPlayerBase player;
- final MediaPlaylistAgent agent;
- synchronized (mLock) {
- player = mPlayer;
- mPlayer = null;
- agent = mPlaylistAgent;
- mPlaylistAgent = null;
- mSessionPlaylistAgent = null;
- }
- if (player != null) {
- player.unregisterPlayerEventCallback(mPlayerEventCallback);
- }
- if (agent != null) {
- agent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
- }
- }
-
- @Override
- public MediaPlayerBase getPlayer_impl() {
- return getPlayer();
- }
-
- @Override
- public MediaPlaylistAgent getPlaylistAgent_impl() {
- return mPlaylistAgent;
- }
-
- @Override
- public VolumeProvider2 getVolumeProvider_impl() {
- return mVolumeProvider;
- }
-
- @Override
- public SessionToken2 getToken_impl() {
- return mSessionToken;
- }
-
- @Override
- public List<ControllerInfo> getConnectedControllers_impl() {
- return mSessionStub.getControllers();
- }
-
- @Override
- public void setAudioFocusRequest_impl(AudioFocusRequest afr) {
- // implement
- }
-
- @Override
- public void play_impl() {
- ensureCallingThread();
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- player.play();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void pause_impl() {
- ensureCallingThread();
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- player.pause();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void stop_impl() {
- ensureCallingThread();
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- player.reset();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void skipToPlaylistItem_impl(@NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.skipToPlaylistItem(item);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void skipToPreviousItem_impl() {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.skipToPreviousItem();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void skipToNextItem_impl() {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.skipToNextItem();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void setCustomLayout_impl(@NonNull ControllerInfo controller,
- @NonNull List<CommandButton> layout) {
- ensureCallingThread();
- if (controller == null) {
- throw new IllegalArgumentException("controller shouldn't be null");
- }
- if (layout == null) {
- throw new IllegalArgumentException("layout shouldn't be null");
- }
- mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
- }
-
- //////////////////////////////////////////////////////////////////////////////////////
- // TODO(jaewan): Implement follows
- //////////////////////////////////////////////////////////////////////////////////////
-
- @Override
- public void setAllowedCommands_impl(@NonNull ControllerInfo controller,
- @NonNull SessionCommandGroup2 commands) {
- if (controller == null) {
- throw new IllegalArgumentException("controller shouldn't be null");
- }
- if (commands == null) {
- throw new IllegalArgumentException("commands shouldn't be null");
- }
- mSessionStub.setAllowedCommands(controller, commands);
- }
-
- @Override
- public void sendCustomCommand_impl(@NonNull ControllerInfo controller,
- @NonNull SessionCommand2 command, Bundle args, ResultReceiver receiver) {
- if (controller == null) {
- throw new IllegalArgumentException("controller shouldn't be null");
- }
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- mSessionStub.sendCustomCommand(controller, command, args, receiver);
- }
-
- @Override
- public void sendCustomCommand_impl(@NonNull SessionCommand2 command, Bundle args) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- mSessionStub.sendCustomCommand(command, args);
- }
-
- @Override
- public void setPlaylist_impl(@NonNull List<MediaItem2> list, MediaMetadata2 metadata) {
- if (list == null) {
- throw new IllegalArgumentException("list shouldn't be null");
- }
- ensureCallingThread();
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.setPlaylist(list, metadata);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.updatePlaylistMetadata(metadata);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void addPlaylistItem_impl(int index, @NonNull MediaItem2 item) {
- if (index < 0) {
- throw new IllegalArgumentException("index shouldn't be negative");
- }
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.addPlaylistItem(index, item);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void removePlaylistItem_impl(@NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.removePlaylistItem(item);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void replacePlaylistItem_impl(int index, @NonNull MediaItem2 item) {
- if (index < 0) {
- throw new IllegalArgumentException("index shouldn't be negative");
- }
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.replacePlaylistItem(index, item);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public List<MediaItem2> getPlaylist_impl() {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- return agent.getPlaylist();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- return null;
- }
-
- @Override
- public MediaMetadata2 getPlaylistMetadata_impl() {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- return agent.getPlaylistMetadata();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- return null;
- }
-
- @Override
- public MediaItem2 getCurrentPlaylistItem_impl() {
- // TODO(jaewan): Implement
- return null;
- }
-
- @Override
- public int getRepeatMode_impl() {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- return agent.getRepeatMode();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- return MediaPlaylistAgent.REPEAT_MODE_NONE;
- }
-
- @Override
- public void setRepeatMode_impl(int repeatMode) {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.setRepeatMode(repeatMode);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public int getShuffleMode_impl() {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- return agent.getShuffleMode();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
- }
-
- @Override
- public void setShuffleMode_impl(int shuffleMode) {
- final MediaPlaylistAgent agent = mPlaylistAgent;
- if (agent != null) {
- agent.setShuffleMode(shuffleMode);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void prepare_impl() {
- ensureCallingThread();
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- player.prepare();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public void seekTo_impl(long pos) {
- ensureCallingThread();
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- player.seekTo(pos);
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- }
-
- @Override
- public @PlayerState int getPlayerState_impl() {
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- return mPlayer.getPlayerState();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- return MediaPlayerBase.PLAYER_STATE_ERROR;
- }
-
- @Override
- public long getCurrentPosition_impl() {
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- return mPlayer.getCurrentPosition();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- return MediaPlayerBase.UNKNOWN_TIME;
- }
-
- @Override
- public long getBufferedPosition_impl() {
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- return mPlayer.getBufferedPosition();
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- return MediaPlayerBase.UNKNOWN_TIME;
- }
-
- @Override
- public void notifyError_impl(int errorCode, Bundle extras) {
- mSessionStub.notifyError(errorCode, extras);
- }
-
- @Override
- public void setOnDataSourceMissingHelper_impl(@NonNull OnDataSourceMissingHelper helper) {
- if (helper == null) {
- throw new IllegalArgumentException("helper shouldn't be null");
- }
- synchronized (mLock) {
- mDsmHelper = helper;
- if (mSessionPlaylistAgent != null) {
- mSessionPlaylistAgent.setOnDataSourceMissingHelper(helper);
- }
- }
- }
-
- @Override
- public void clearOnDataSourceMissingHelper_impl() {
- synchronized (mLock) {
- mDsmHelper = null;
- if (mSessionPlaylistAgent != null) {
- mSessionPlaylistAgent.clearOnDataSourceMissingHelper();
- }
- }
- }
-
- ///////////////////////////////////////////////////
- // Protected or private methods
- ///////////////////////////////////////////////////
-
- // Enforces developers to call all the methods on the initially given thread
- // because calls from the MediaController2 will be run on the thread.
- // TODO(jaewan): Should we allow calls from the multiple thread?
- // I prefer this way because allowing multiple thread may case tricky issue like
- // b/63446360. If the {@link #setPlayer()} with {@code null} can be called from
- // another thread, transport controls can be called after that.
- // That's basically the developer's mistake, but they cannot understand what's
- // happening behind until we tell them so.
- // If enforcing callling thread doesn't look good, we can alternatively pick
- // 1. Allow calls from random threads for all methods.
- // 2. Allow calls from random threads for all methods, except for the
- // {@link #setPlayer()}.
- void ensureCallingThread() {
- // TODO(jaewan): Uncomment or remove
- /*
- if (mHandler.getLooper() != Looper.myLooper()) {
- throw new IllegalStateException("Run this on the given thread");
- }*/
- }
-
- private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
- List<MediaItem2> list, MediaMetadata2 metadata) {
- if (playlistAgent != mPlaylistAgent) {
- // Ignore calls from the old agent.
- return;
- }
- mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
- mSessionStub.notifyPlaylistChangedNotLocked(list, metadata);
- }
-
- private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
- MediaMetadata2 metadata) {
- if (playlistAgent != mPlaylistAgent) {
- // Ignore calls from the old agent.
- return;
- }
- mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata);
- mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
- }
-
- private void notifyRepeatModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
- int repeatMode) {
- if (playlistAgent != mPlaylistAgent) {
- // Ignore calls from the old agent.
- return;
- }
- mCallback.onRepeatModeChanged(mInstance, playlistAgent, repeatMode);
- mSessionStub.notifyRepeatModeChangedNotLocked(repeatMode);
- }
-
- private void notifyShuffleModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
- int shuffleMode) {
- if (playlistAgent != mPlaylistAgent) {
- // Ignore calls from the old agent.
- return;
- }
- mCallback.onShuffleModeChanged(mInstance, playlistAgent, shuffleMode);
- mSessionStub.notifyShuffleModeChangedNotLocked(shuffleMode);
- }
-
- private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
- final MediaPlayerBase player = mPlayer;
- // TODO(jaewan): (Can be post-P) Find better way for player.getPlayerState() //
- // In theory, Session.getXXX() may not be the same as Player.getXXX()
- // and we should notify information of the session.getXXX() instead of
- // player.getXXX()
- // Notify to controllers as well.
- final int state = player.getPlayerState();
- if (state != oldPlayer.getPlayerState()) {
- mSessionStub.notifyPlayerStateChangedNotLocked(state);
- }
-
- final long currentTimeMs = System.currentTimeMillis();
- final long position = player.getCurrentPosition();
- if (position != oldPlayer.getCurrentPosition()) {
- mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
- }
-
- final float speed = player.getPlaybackSpeed();
- if (speed != oldPlayer.getPlaybackSpeed()) {
- mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
- }
-
- final long bufferedPosition = player.getBufferedPosition();
- if (bufferedPosition != oldPlayer.getBufferedPosition()) {
- mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
- }
- }
-
- Context getContext() {
- return mContext;
- }
-
- MediaSession2 getInstance() {
- return mInstance;
- }
-
- MediaPlayerBase getPlayer() {
- return mPlayer;
- }
-
- MediaPlaylistAgent getPlaylistAgent() {
- return mPlaylistAgent;
- }
-
- Executor getCallbackExecutor() {
- return mCallbackExecutor;
- }
-
- SessionCallback getCallback() {
- return mCallback;
- }
-
- MediaSession2Stub getSessionStub() {
- return mSessionStub;
- }
-
- VolumeProvider2 getVolumeProvider() {
- return mVolumeProvider;
- }
-
- PlaybackInfo getPlaybackInfo() {
- synchronized (mLock) {
- return mPlaybackInfo;
- }
- }
-
- PendingIntent getSessionActivity() {
- return mSessionActivity;
- }
-
- private static class MyPlayerEventCallback extends PlayerEventCallback {
- private final WeakReference<MediaSession2Impl> mSession;
-
- private MyPlayerEventCallback(MediaSession2Impl session) {
- mSession = new WeakReference<>(session);
- }
-
- @Override
- public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
- MediaSession2Impl session = getSession();
- if (session == null || dsd == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- MediaItem2 item = getMediaItem(session, dsd);
- if (item == null) {
- return;
- }
- session.getCallback().onCurrentMediaItemChanged(session.getInstance(), mpb, item);
- // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
- });
- }
-
- @Override
- public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
- MediaSession2Impl session = getSession();
- if (session == null || dsd == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- MediaItem2 item = getMediaItem(session, dsd);
- if (item == null) {
- return;
- }
- session.getCallback().onMediaPrepared(session.getInstance(), mpb, item);
- // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
- });
- }
-
- @Override
- public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
- MediaSession2Impl session = getSession();
- if (session == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
- session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
- });
- }
-
- @Override
- public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
- MediaSession2Impl session = getSession();
- if (session == null || dsd == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- MediaItem2 item = getMediaItem(session, dsd);
- if (item == null) {
- return;
- }
- session.getCallback().onBufferingStateChanged(
- session.getInstance(), mpb, item, state);
- // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
- });
- }
-
- private MediaSession2Impl getSession() {
- final MediaSession2Impl session = mSession.get();
- if (session == null && DEBUG) {
- Log.d(TAG, "Session is closed", new IllegalStateException());
- }
- return session;
- }
-
- private MediaItem2 getMediaItem(MediaSession2Impl session, DataSourceDesc dsd) {
- MediaPlaylistAgent agent = session.getPlaylistAgent();
- if (agent == null) {
- if (DEBUG) {
- Log.d(TAG, "Session is closed", new IllegalStateException());
- }
- return null;
- }
- MediaItem2 item = agent.getMediaItem(dsd);
- if (item == null) {
- if (DEBUG) {
- Log.d(TAG, "Could not find matching item for dsd=" + dsd,
- new NoSuchElementException());
- }
- }
- return item;
- }
- }
-
- private static class MyPlaylistEventCallback extends PlaylistEventCallback {
- private final WeakReference<MediaSession2Impl> mSession;
-
- private MyPlaylistEventCallback(MediaSession2Impl session) {
- mSession = new WeakReference<>(session);
- }
-
- @Override
- public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
- MediaMetadata2 metadata) {
- final MediaSession2Impl session = mSession.get();
- if (session == null) {
- return;
- }
- session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
- }
-
- @Override
- public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
- MediaMetadata2 metadata) {
- final MediaSession2Impl session = mSession.get();
- if (session == null) {
- return;
- }
- session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
- }
-
- @Override
- public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
- final MediaSession2Impl session = mSession.get();
- if (session == null) {
- return;
- }
- session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode);
- }
-
- @Override
- public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
- final MediaSession2Impl session = mSession.get();
- if (session == null) {
- return;
- }
- session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode);
- }
- }
-
- public static final class CommandImpl implements CommandProvider {
- private static final String KEY_COMMAND_CODE
- = "android.media.media_session2.command.command_code";
- private static final String KEY_COMMAND_CUSTOM_COMMAND
- = "android.media.media_session2.command.custom_command";
- private static final String KEY_COMMAND_EXTRAS
- = "android.media.media_session2.command.extras";
-
- private final SessionCommand2 mInstance;
- private final int mCommandCode;
- // Nonnull if it's custom command
- private final String mCustomCommand;
- private final Bundle mExtras;
-
- public CommandImpl(SessionCommand2 instance, int commandCode) {
- mInstance = instance;
- mCommandCode = commandCode;
- mCustomCommand = null;
- mExtras = null;
- }
-
- public CommandImpl(SessionCommand2 instance, @NonNull String action,
- @Nullable Bundle extras) {
- if (action == null) {
- throw new IllegalArgumentException("action shouldn't be null");
- }
- mInstance = instance;
- mCommandCode = COMMAND_CODE_CUSTOM;
- mCustomCommand = action;
- mExtras = extras;
- }
-
- @Override
- public int getCommandCode_impl() {
- return mCommandCode;
- }
-
- @Override
- public @Nullable String getCustomCommand_impl() {
- return mCustomCommand;
- }
-
- @Override
- public @Nullable Bundle getExtras_impl() {
- return mExtras;
- }
-
- /**
- * @return a new Bundle instance from the Command
- */
- @Override
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
- bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand);
- bundle.putBundle(KEY_COMMAND_EXTRAS, mExtras);
- return bundle;
- }
-
- /**
- * @return a new Command instance from the Bundle
- */
- public static SessionCommand2 fromBundle_impl(@NonNull Bundle command) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- int code = command.getInt(KEY_COMMAND_CODE);
- if (code != COMMAND_CODE_CUSTOM) {
- return new SessionCommand2(code);
- } else {
- String customCommand = command.getString(KEY_COMMAND_CUSTOM_COMMAND);
- if (customCommand == null) {
- return null;
- }
- return new SessionCommand2(customCommand, command.getBundle(KEY_COMMAND_EXTRAS));
- }
- }
-
- @Override
- public boolean equals_impl(Object obj) {
- if (!(obj instanceof CommandImpl)) {
- return false;
- }
- CommandImpl other = (CommandImpl) obj;
- // TODO(jaewan): Compare Commands with the generated UUID, as we're doing for the MI2.
- return mCommandCode == other.mCommandCode
- && TextUtils.equals(mCustomCommand, other.mCustomCommand);
- }
-
- @Override
- public int hashCode_impl() {
- final int prime = 31;
- return ((mCustomCommand != null)
- ? mCustomCommand.hashCode() : 0) * prime + mCommandCode;
- }
- }
-
- /**
- * Represent set of {@link SessionCommand2}.
- */
- public static class CommandGroupImpl implements CommandGroupProvider {
- private static final String KEY_COMMANDS =
- "android.media.mediasession2.commandgroup.commands";
-
- // Prefix for all command codes
- private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
-
- // Prefix for command codes that will be sent directly to the MediaPlayerBase
- private static final String PREFIX_COMMAND_CODE_PLAYBACK = "COMMAND_CODE_PLAYBACK_";
-
- // Prefix for command codes that will be sent directly to the MediaPlaylistAgent
- private static final String PREFIX_COMMAND_CODE_PLAYLIST = "COMMAND_CODE_PLAYLIST_";
-
- private Set<SessionCommand2> mCommands = new HashSet<>();
- private final SessionCommandGroup2 mInstance;
-
- public CommandGroupImpl(SessionCommandGroup2 instance, Object other) {
- mInstance = instance;
- if (other != null && other instanceof CommandGroupImpl) {
- mCommands.addAll(((CommandGroupImpl) other).mCommands);
- }
- }
-
- public CommandGroupImpl() {
- mInstance = new SessionCommandGroup2(this);
- }
-
- @Override
- public void addCommand_impl(@NonNull SessionCommand2 command) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- mCommands.add(command);
- }
-
- @Override
- public void addAllPredefinedCommands_impl() {
- addCommandsWithPrefix(PREFIX_COMMAND_CODE);
- }
-
- void addAllPlaybackCommands() {
- addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYBACK);
- }
-
- void addAllPlaylistCommands() {
- addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYLIST);
- }
-
- private void addCommandsWithPrefix(String prefix) {
- // TODO(jaewan): (Can be post-P): Don't use reflection for this purpose.
- final Field[] fields = MediaSession2.class.getFields();
- if (fields != null) {
- for (int i = 0; i < fields.length; i++) {
- if (fields[i].getName().startsWith(prefix)) {
- try {
- mCommands.add(new SessionCommand2(fields[i].getInt(null)));
- } catch (IllegalAccessException e) {
- Log.w(TAG, "Unexpected " + fields[i] + " in MediaSession2");
- }
- }
- }
- }
- }
-
- @Override
- public void removeCommand_impl(@NonNull SessionCommand2 command) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- mCommands.remove(command);
- }
-
- @Override
- public boolean hasCommand_impl(@NonNull SessionCommand2 command) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- return mCommands.contains(command);
- }
-
- @Override
- public boolean hasCommand_impl(int code) {
- if (code == COMMAND_CODE_CUSTOM) {
- throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
- }
- for (SessionCommand2 command : mCommands) {
- if (command.getCommandCode() == code) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public Set<SessionCommand2> getCommands_impl() {
- return getCommands();
- }
-
- public Set<SessionCommand2> getCommands() {
- return Collections.unmodifiableSet(mCommands);
- }
-
- /**
- * @return new bundle from the CommandGroup
- * @hide
- */
- @Override
- public Bundle toBundle_impl() {
- ArrayList<Bundle> list = new ArrayList<>();
- for (SessionCommand2 command : mCommands) {
- list.add(command.toBundle());
- }
- Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(KEY_COMMANDS, list);
- return bundle;
- }
-
- /**
- * @return new instance of CommandGroup from the bundle
- * @hide
- */
- public static @Nullable SessionCommandGroup2 fromBundle_impl(Bundle commands) {
- if (commands == null) {
- return null;
- }
- List<Parcelable> list = commands.getParcelableArrayList(KEY_COMMANDS);
- if (list == null) {
- return null;
- }
- SessionCommandGroup2 commandGroup = new SessionCommandGroup2();
- for (int i = 0; i < list.size(); i++) {
- Parcelable parcelable = list.get(i);
- if (!(parcelable instanceof Bundle)) {
- continue;
- }
- Bundle commandBundle = (Bundle) parcelable;
- SessionCommand2 command = SessionCommand2.fromBundle(commandBundle);
- if (command != null) {
- commandGroup.addCommand(command);
- }
- }
- return commandGroup;
- }
- }
-
- public static class ControllerInfoImpl implements ControllerInfoProvider {
- private final ControllerInfo mInstance;
- private final int mUid;
- private final String mPackageName;
- private final boolean mIsTrusted;
- private final IMediaController2 mControllerBinder;
-
- public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
- int pid, @NonNull String packageName, @NonNull IMediaController2 callback) {
- if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("packageName shouldn't be empty");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
-
- mInstance = instance;
- mUid = uid;
- mPackageName = packageName;
- mControllerBinder = callback;
- MediaSessionManager manager =
- (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
- // Ask server whether the controller is trusted.
- // App cannot know this because apps cannot query enabled notification listener for
- // another package, but system server can do.
- mIsTrusted = manager.isTrustedForMediaControl(
- new MediaSessionManager.RemoteUserInfo(packageName, pid, uid));
- }
-
- @Override
- public String getPackageName_impl() {
- return mPackageName;
- }
-
- @Override
- public int getUid_impl() {
- return mUid;
- }
-
- @Override
- public boolean isTrusted_impl() {
- return mIsTrusted;
- }
-
- @Override
- public int hashCode_impl() {
- return mControllerBinder.hashCode();
- }
-
- @Override
- public boolean equals_impl(Object obj) {
- if (!(obj instanceof ControllerInfo)) {
- return false;
- }
- return equals(((ControllerInfo) obj).getProvider());
- }
-
- @Override
- public String toString_impl() {
- return "ControllerInfo {pkg=" + mPackageName + ", uid=" + mUid + ", trusted="
- + mIsTrusted + "}";
- }
-
- @Override
- public int hashCode() {
- return mControllerBinder.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof ControllerInfoImpl)) {
- return false;
- }
- ControllerInfoImpl other = (ControllerInfoImpl) obj;
- return mControllerBinder.asBinder().equals(other.mControllerBinder.asBinder());
- }
-
- ControllerInfo getInstance() {
- return mInstance;
- }
-
- IBinder getId() {
- return mControllerBinder.asBinder();
- }
-
- IMediaController2 getControllerBinder() {
- return mControllerBinder;
- }
-
- static ControllerInfoImpl from(ControllerInfo controller) {
- return (ControllerInfoImpl) controller.getProvider();
- }
- }
-
- public static class CommandButtonImpl implements CommandButtonProvider {
- private static final String KEY_COMMAND
- = "android.media.media_session2.command_button.command";
- private static final String KEY_ICON_RES_ID
- = "android.media.media_session2.command_button.icon_res_id";
- private static final String KEY_DISPLAY_NAME
- = "android.media.media_session2.command_button.display_name";
- private static final String KEY_EXTRAS
- = "android.media.media_session2.command_button.extras";
- private static final String KEY_ENABLED
- = "android.media.media_session2.command_button.enabled";
-
- private final CommandButton mInstance;
- private SessionCommand2 mCommand;
- private int mIconResId;
- private String mDisplayName;
- private Bundle mExtras;
- private boolean mEnabled;
-
- public CommandButtonImpl(@Nullable SessionCommand2 command, int iconResId,
- @Nullable String displayName, Bundle extras, boolean enabled) {
- mCommand = command;
- mIconResId = iconResId;
- mDisplayName = displayName;
- mExtras = extras;
- mEnabled = enabled;
- mInstance = new CommandButton(this);
- }
-
- @Override
- public @Nullable
- SessionCommand2 getCommand_impl() {
- return mCommand;
- }
-
- @Override
- public int getIconResId_impl() {
- return mIconResId;
- }
-
- @Override
- public @Nullable String getDisplayName_impl() {
- return mDisplayName;
- }
-
- @Override
- public @Nullable Bundle getExtras_impl() {
- return mExtras;
- }
-
- @Override
- public boolean isEnabled_impl() {
- return mEnabled;
- }
-
- @NonNull Bundle toBundle() {
- Bundle bundle = new Bundle();
- bundle.putBundle(KEY_COMMAND, mCommand.toBundle());
- bundle.putInt(KEY_ICON_RES_ID, mIconResId);
- bundle.putString(KEY_DISPLAY_NAME, mDisplayName);
- bundle.putBundle(KEY_EXTRAS, mExtras);
- bundle.putBoolean(KEY_ENABLED, mEnabled);
- return bundle;
- }
-
- static @Nullable CommandButton fromBundle(Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- CommandButton.Builder builder = new CommandButton.Builder();
- builder.setCommand(SessionCommand2.fromBundle(bundle.getBundle(KEY_COMMAND)));
- builder.setIconResId(bundle.getInt(KEY_ICON_RES_ID, 0));
- builder.setDisplayName(bundle.getString(KEY_DISPLAY_NAME));
- builder.setExtras(bundle.getBundle(KEY_EXTRAS));
- builder.setEnabled(bundle.getBoolean(KEY_ENABLED));
- try {
- return builder.build();
- } catch (IllegalStateException e) {
- // Malformed or version mismatch. Return null for now.
- return null;
- }
- }
-
- /**
- * Builder for {@link CommandButton}.
- */
- public static class BuilderImpl implements CommandButtonProvider.BuilderProvider {
- private final CommandButton.Builder mInstance;
- private SessionCommand2 mCommand;
- private int mIconResId;
- private String mDisplayName;
- private Bundle mExtras;
- private boolean mEnabled;
-
- public BuilderImpl(CommandButton.Builder instance) {
- mInstance = instance;
- mEnabled = true;
- }
-
- @Override
- public CommandButton.Builder setCommand_impl(SessionCommand2 command) {
- mCommand = command;
- return mInstance;
- }
-
- @Override
- public CommandButton.Builder setIconResId_impl(int resId) {
- mIconResId = resId;
- return mInstance;
- }
-
- @Override
- public CommandButton.Builder setDisplayName_impl(String displayName) {
- mDisplayName = displayName;
- return mInstance;
- }
-
- @Override
- public CommandButton.Builder setEnabled_impl(boolean enabled) {
- mEnabled = enabled;
- return mInstance;
- }
-
- @Override
- public CommandButton.Builder setExtras_impl(Bundle extras) {
- mExtras = extras;
- return mInstance;
- }
-
- @Override
- public CommandButton build_impl() {
- if (mEnabled && mCommand == null) {
- throw new IllegalStateException("Enabled button needs Command"
- + " for controller to invoke the command");
- }
- if (mCommand != null && mCommand.getCommandCode() == COMMAND_CODE_CUSTOM
- && (mIconResId == 0 || TextUtils.isEmpty(mDisplayName))) {
- throw new IllegalStateException("Custom commands needs icon and"
- + " and name to display");
- }
- return new CommandButtonImpl(mCommand, mIconResId, mDisplayName, mExtras, mEnabled)
- .mInstance;
- }
- }
- }
-
- public static abstract class BuilderBaseImpl<T extends MediaSession2, C extends SessionCallback>
- implements BuilderBaseProvider<T, C> {
- final Context mContext;
- MediaPlayerBase mPlayer;
- String mId;
- Executor mCallbackExecutor;
- C mCallback;
- MediaPlaylistAgent mPlaylistAgent;
- VolumeProvider2 mVolumeProvider;
- PendingIntent mSessionActivity;
-
- /**
- * Constructor.
- *
- * @param context a context
- * @throws IllegalArgumentException if any parameter is null, or the player is a
- * {@link MediaSession2} or {@link MediaController2}.
- */
- // TODO(jaewan): Also need executor
- public BuilderBaseImpl(@NonNull Context context) {
- if (context == null) {
- throw new IllegalArgumentException("context shouldn't be null");
- }
- mContext = context;
- // Ensure non-null
- mId = "";
- }
-
- @Override
- public void setPlayer_impl(@NonNull MediaPlayerBase player) {
- if (player == null) {
- throw new IllegalArgumentException("player shouldn't be null");
- }
- mPlayer = player;
- }
-
- @Override
- public void setPlaylistAgent_impl(@NonNull MediaPlaylistAgent playlistAgent) {
- if (playlistAgent == null) {
- throw new IllegalArgumentException("playlistAgent shouldn't be null");
- }
- mPlaylistAgent = playlistAgent;
- }
-
- @Override
- public void setVolumeProvider_impl(VolumeProvider2 volumeProvider) {
- mVolumeProvider = volumeProvider;
- }
-
- @Override
- public void setSessionActivity_impl(PendingIntent pi) {
- mSessionActivity = pi;
- }
-
- @Override
- public void setId_impl(@NonNull String id) {
- if (id == null) {
- throw new IllegalArgumentException("id shouldn't be null");
- }
- mId = id;
- }
-
- @Override
- public void setSessionCallback_impl(@NonNull Executor executor, @NonNull C callback) {
- if (executor == null) {
- throw new IllegalArgumentException("executor shouldn't be null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
- mCallbackExecutor = executor;
- mCallback = callback;
- }
-
- @Override
- public abstract T build_impl();
- }
-
- public static class BuilderImpl extends BuilderBaseImpl<MediaSession2, SessionCallback> {
- public BuilderImpl(Context context, Builder instance) {
- super(context);
- }
-
- @Override
- public MediaSession2 build_impl() {
- if (mCallbackExecutor == null) {
- mCallbackExecutor = mContext.getMainExecutor();
- }
- if (mCallback == null) {
- mCallback = new SessionCallback() {};
- }
- return new MediaSession2Impl(mContext, mPlayer, mId, mPlaylistAgent,
- mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback).getInstance();
- }
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
deleted file mode 100644
index 53a5986..0000000
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ /dev/null
@@ -1,888 +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 com.android.media;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.MediaController2;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaSession2.CommandButton;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.Rating2;
-import android.media.SessionCommand2;
-import android.media.SessionCommandGroup2;
-import android.media.VolumeProvider2;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.DeadObjectException;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-
-import com.android.media.MediaSession2Impl.CommandButtonImpl;
-import com.android.media.MediaSession2Impl.CommandGroupImpl;
-import com.android.media.MediaSession2Impl.ControllerInfoImpl;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class MediaSession2Stub extends IMediaSession2.Stub {
-
- static final String ARGUMENT_KEY_POSITION = "android.media.media_session2.key_position";
- static final String ARGUMENT_KEY_ITEM_INDEX = "android.media.media_session2.key_item_index";
- static final String ARGUMENT_KEY_PLAYLIST_PARAMS =
- "android.media.media_session2.key_playlist_params";
-
- private static final String TAG = "MediaSession2Stub";
- private static final boolean DEBUG = true; // TODO(jaewan): Rename.
-
- private static final SparseArray<SessionCommand2> sCommandsForOnCommandRequest =
- new SparseArray<>();
-
- private final Object mLock = new Object();
- private final WeakReference<MediaSession2Impl> mSession;
-
- @GuardedBy("mLock")
- private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
- @GuardedBy("mLock")
- private final Set<IBinder> mConnectingControllers = new HashSet<>();
- @GuardedBy("mLock")
- private final ArrayMap<ControllerInfo, SessionCommandGroup2> mAllowedCommandGroupMap =
- new ArrayMap<>();
- @GuardedBy("mLock")
- private final ArrayMap<ControllerInfo, Set<String>> mSubscriptions = new ArrayMap<>();
-
- public MediaSession2Stub(MediaSession2Impl session) {
- mSession = new WeakReference<>(session);
-
- synchronized (sCommandsForOnCommandRequest) {
- if (sCommandsForOnCommandRequest.size() == 0) {
- CommandGroupImpl group = new CommandGroupImpl();
- group.addAllPlaybackCommands();
- group.addAllPlaylistCommands();
- Set<SessionCommand2> commands = group.getCommands();
- for (SessionCommand2 command : commands) {
- sCommandsForOnCommandRequest.append(command.getCommandCode(), command);
- }
- }
- }
- }
-
- public void destroyNotLocked() {
- final List<ControllerInfo> list;
- synchronized (mLock) {
- mSession.clear();
- list = getControllers();
- mControllers.clear();
- }
- for (int i = 0; i < list.size(); i++) {
- IMediaController2 controllerBinder =
- ((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
- try {
- // Should be used without a lock hold to prevent potential deadlock.
- controllerBinder.onDisconnected();
- } catch (RemoteException e) {
- // Controller is gone. Should be fine because we're destroying.
- }
- }
- }
-
- private MediaSession2Impl getSession() {
- final MediaSession2Impl session = mSession.get();
- if (session == null && DEBUG) {
- Log.d(TAG, "Session is closed", new IllegalStateException());
- }
- return session;
- }
-
- // Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaController2 caller) {
- synchronized (mLock) {
- final ControllerInfo controllerInfo = mControllers.get(caller.asBinder());
- if (controllerInfo == null && DEBUG) {
- Log.d(TAG, "Controller is disconnected", new IllegalStateException());
- }
- return controllerInfo;
- }
- }
-
- // Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaController2 caller, int commandCode) {
- synchronized (mLock) {
- final ControllerInfo controllerInfo = getControllerIfAble(caller);
- if (controllerInfo == null) {
- return null;
- }
- SessionCommandGroup2 allowedCommands = mAllowedCommandGroupMap.get(controllerInfo);
- if (allowedCommands == null) {
- Log.w(TAG, "Controller with null allowed commands. Ignoring",
- new IllegalStateException());
- return null;
- }
- if (!allowedCommands.hasCommand(commandCode)) {
- if (DEBUG) {
- Log.d(TAG, "Controller isn't allowed for command " + commandCode);
- }
- return null;
- }
- return controllerInfo;
- }
- }
-
- // Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaController2 caller, SessionCommand2 command) {
- synchronized (mLock) {
- final ControllerInfo controllerInfo = getControllerIfAble(caller);
- if (controllerInfo == null) {
- return null;
- }
- SessionCommandGroup2 allowedCommands = mAllowedCommandGroupMap.get(controllerInfo);
- if (allowedCommands == null) {
- Log.w(TAG, "Controller with null allowed commands. Ignoring",
- new IllegalStateException());
- return null;
- }
- if (!allowedCommands.hasCommand(command)) {
- if (DEBUG) {
- Log.d(TAG, "Controller isn't allowed for command " + command);
- }
- return null;
- }
- return controllerInfo;
- }
- }
-
- // Return binder if the session is able to send a command to the controller.
- private IMediaController2 getControllerBinderIfAble(ControllerInfo controller) {
- if (getSession() == null) {
- // getSession() already logged if session is closed.
- return null;
- }
- final ControllerInfoImpl impl = ControllerInfoImpl.from(controller);
- synchronized (mLock) {
- if (mControllers.get(impl.getId()) != null
- || mConnectingControllers.contains(impl.getId())) {
- return impl.getControllerBinder();
- }
- if (DEBUG) {
- Log.d(TAG, controller + " isn't connected nor connecting",
- new IllegalArgumentException());
- }
- return null;
- }
- }
-
- // Return binder if the session is able to send a command to the controller.
- private IMediaController2 getControllerBinderIfAble(ControllerInfo controller,
- int commandCode) {
- synchronized (mLock) {
- SessionCommandGroup2 allowedCommands = mAllowedCommandGroupMap.get(controller);
- if (allowedCommands == null) {
- Log.w(TAG, "Controller with null allowed commands. Ignoring");
- return null;
- }
- if (!allowedCommands.hasCommand(commandCode)) {
- if (DEBUG) {
- Log.d(TAG, "Controller isn't allowed for command " + commandCode);
- }
- return null;
- }
- return getControllerBinderIfAble(controller);
- }
- }
-
- private void onCommand(@NonNull IMediaController2 caller, int commandCode,
- @NonNull SessionRunnable runnable) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(caller, commandCode);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, commandCode) == null) {
- return;
- }
- SessionCommand2 command = sCommandsForOnCommandRequest.get(commandCode);
- if (command != null) {
- boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
- controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command (code=" + commandCode + ") from "
- + controller + " was rejected by " + session);
- }
- return;
- }
- }
- runnable.run(session, controller);
- });
- }
-
- private void notifyAll(int commandCode, @NonNull NotifyRunnable runnable) {
- List<ControllerInfo> controllers = getControllers();
- for (int i = 0; i < controllers.size(); i++) {
- notifyInternal(controllers.get(i),
- getControllerBinderIfAble(controllers.get(i), commandCode), runnable);
- }
- }
-
- private void notifyAll(@NonNull NotifyRunnable runnable) {
- List<ControllerInfo> controllers = getControllers();
- for (int i = 0; i < controllers.size(); i++) {
- notifyInternal(controllers.get(i),
- getControllerBinderIfAble(controllers.get(i)), runnable);
- }
- }
-
- private void notify(@NonNull ControllerInfo controller, @NonNull NotifyRunnable runnable) {
- notifyInternal(controller, getControllerBinderIfAble(controller), runnable);
- }
-
- private void notify(@NonNull ControllerInfo controller, int commandCode,
- @NonNull NotifyRunnable runnable) {
- notifyInternal(controller, getControllerBinderIfAble(controller, commandCode), runnable);
- }
-
- // Do not call this API directly. Use notify() instead.
- private void notifyInternal(@NonNull ControllerInfo controller,
- @NonNull IMediaController2 iController, @NonNull NotifyRunnable runnable) {
- if (controller == null || iController == null) {
- return;
- }
- try {
- runnable.run(controller, iController);
- } catch (DeadObjectException e) {
- if (DEBUG) {
- Log.d(TAG, controller.toString() + " is gone", e);
- }
- onControllerClosed(iController);
- } 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);
- }
- }
-
- private void onControllerClosed(IMediaController2 iController) {
- ControllerInfo controller;
- synchronized (mLock) {
- controller = mControllers.remove(iController.asBinder());
- if (DEBUG) {
- Log.d(TAG, "releasing " + controller);
- }
- mSubscriptions.remove(controller);
- }
- final MediaSession2Impl session = getSession();
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- session.getCallback().onDisconnected(session.getInstance(), controller);
- });
- }
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- // AIDL methods for session overrides
- //////////////////////////////////////////////////////////////////////////////////////////////
- @Override
- public void connect(final IMediaController2 caller, final String callingPackage)
- throws RuntimeException {
- final MediaSession2Impl session = getSession();
- if (session == null) {
- return;
- }
- final Context context = session.getContext();
- final ControllerInfo controllerInfo = new ControllerInfo(context,
- Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, caller);
- session.getCallbackExecutor().execute(() -> {
- if (getSession() == null) {
- return;
- }
- synchronized (mLock) {
- // Keep connecting controllers.
- // This helps sessions to call APIs in the onConnect() (e.g. setCustomLayout())
- // instead of pending them.
- mConnectingControllers.add(ControllerInfoImpl.from(controllerInfo).getId());
- }
- SessionCommandGroup2 allowedCommands = session.getCallback().onConnect(
- session.getInstance(), controllerInfo);
- // Don't reject connection for the request from trusted app.
- // Otherwise server will fail to retrieve session's information to dispatch
- // media keys to.
- boolean accept = allowedCommands != null || controllerInfo.isTrusted();
- if (accept) {
- ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controllerInfo);
- if (DEBUG) {
- Log.d(TAG, "Accepting connection, controllerInfo=" + controllerInfo
- + " allowedCommands=" + allowedCommands);
- }
- if (allowedCommands == null) {
- // For trusted apps, send non-null allowed commands to keep connection.
- allowedCommands = new SessionCommandGroup2();
- }
- synchronized (mLock) {
- mConnectingControllers.remove(controllerImpl.getId());
- mControllers.put(controllerImpl.getId(), controllerInfo);
- mAllowedCommandGroupMap.put(controllerInfo, allowedCommands);
- }
- // If connection is accepted, notify the current state to the controller.
- // It's needed because we cannot call synchronous calls between session/controller.
- // Note: We're doing this after the onConnectionChanged(), but there's no guarantee
- // that events here are notified after the onConnected() because
- // IMediaController2 is oneway (i.e. async call) and Stub will
- // use thread poll for incoming calls.
- final int playerState = session.getInstance().getPlayerState();
- final long positionEventTimeMs = System.currentTimeMillis();
- final long positionMs = session.getInstance().getCurrentPosition();
- final float playbackSpeed = session.getInstance().getPlaybackSpeed();
- final long bufferedPositionMs = session.getInstance().getBufferedPosition();
- final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
- session.getPlaybackInfo().getProvider()).toBundle();
- final int repeatMode = session.getInstance().getRepeatMode();
- final int shuffleMode = session.getInstance().getShuffleMode();
- final PendingIntent sessionActivity = session.getSessionActivity();
- final List<MediaItem2> playlist =
- allowedCommands.hasCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST)
- ? session.getInstance().getPlaylist() : null;
- final List<Bundle> playlistBundle;
- if (playlist != null) {
- playlistBundle = new ArrayList<>();
- // TODO(jaewan): Find a way to avoid concurrent modification exception.
- for (int i = 0; i < playlist.size(); i++) {
- final MediaItem2 item = playlist.get(i);
- if (item != null) {
- final Bundle itemBundle = item.toBundle();
- if (itemBundle != null) {
- playlistBundle.add(itemBundle);
- }
- }
- }
- } else {
- playlistBundle = null;
- }
-
- // Double check if session is still there, because close() can be called in another
- // thread.
- if (getSession() == null) {
- return;
- }
- try {
- caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
- playerState, positionEventTimeMs, positionMs, playbackSpeed,
- bufferedPositionMs, playbackInfoBundle, repeatMode, shuffleMode,
- playlistBundle, sessionActivity);
- } catch (RemoteException e) {
- // Controller may be died prematurely.
- // TODO(jaewan): Handle here.
- }
- } else {
- synchronized (mLock) {
- mConnectingControllers.remove(ControllerInfoImpl.from(controllerInfo).getId());
- }
- if (DEBUG) {
- Log.d(TAG, "Rejecting connection, controllerInfo=" + controllerInfo);
- }
- try {
- caller.onDisconnected();
- } catch (RemoteException e) {
- // Controller may be died prematurely.
- // Not an issue because we'll ignore it anyway.
- }
- }
- });
- }
-
- @Override
- public void release(final IMediaController2 caller) throws RemoteException {
- onControllerClosed(caller);
- }
-
- @Override
- public void setVolumeTo(final IMediaController2 caller, final int value, final int flags)
- throws RuntimeException {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SET_VOLUME,
- (session, controller) -> {
- VolumeProvider2 volumeProvider = session.getVolumeProvider();
- if (volumeProvider == null) {
- // TODO(jaewan): Set local stream volume
- } else {
- volumeProvider.onSetVolumeTo(value);
- }
- });
- }
-
- @Override
- public void adjustVolume(IMediaController2 caller, int direction, int flags)
- throws RuntimeException {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SET_VOLUME,
- (session, controller) -> {
- VolumeProvider2 volumeProvider = session.getVolumeProvider();
- if (volumeProvider == null) {
- // TODO(jaewan): Adjust local stream volume
- } else {
- volumeProvider.onAdjustVolume(direction);
- }
- });
- }
-
- @Override
- public void sendTransportControlCommand(IMediaController2 caller,
- int commandCode, Bundle args) throws RuntimeException {
- onCommand(caller, commandCode, (session, controller) -> {
- switch (commandCode) {
- case SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY:
- session.getInstance().play();
- break;
- case SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE:
- session.getInstance().pause();
- break;
- case SessionCommand2.COMMAND_CODE_PLAYBACK_STOP:
- session.getInstance().stop();
- break;
- case SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE:
- session.getInstance().prepare();
- break;
- case SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO:
- session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
- break;
- default:
- // TODO(jaewan): Resend unknown (new) commands through the custom command.
- }
- });
- }
-
- @Override
- public void sendCustomCommand(final IMediaController2 caller, final Bundle commandBundle,
- final Bundle args, final ResultReceiver receiver) {
- final MediaSession2Impl session = getSession();
- if (session == null) {
- return;
- }
- final SessionCommand2 command = SessionCommand2.fromBundle(commandBundle);
- if (command == null) {
- Log.w(TAG, "sendCustomCommand(): Ignoring null command from "
- + getControllerIfAble(caller));
- return;
- }
- final ControllerInfo controller = getControllerIfAble(caller, command);
- if (controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, command) == null) {
- return;
- }
- session.getCallback().onCustomCommand(session.getInstance(),
- controller, command, args, receiver);
- });
- }
-
- @Override
- public void prepareFromUri(final IMediaController2 caller, final Uri uri,
- final Bundle extras) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI,
- (session, controller) -> {
- if (uri == null) {
- Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
- return;
- }
- session.getCallback().onPrepareFromUri(session.getInstance(), controller, uri,
- extras);
- });
- }
-
- @Override
- public void prepareFromSearch(final IMediaController2 caller, final String query,
- final Bundle extras) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH,
- (session, controller) -> {
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallback().onPrepareFromSearch(session.getInstance(),
- controller, query, extras);
- });
- }
-
- @Override
- public void prepareFromMediaId(final IMediaController2 caller, final String mediaId,
- final Bundle extras) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID,
- (session, controller) -> {
- if (mediaId == null) {
- Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
- return;
- }
- session.getCallback().onPrepareFromMediaId(session.getInstance(),
- controller, mediaId, extras);
- });
- }
-
- @Override
- public void playFromUri(final IMediaController2 caller, final Uri uri,
- final Bundle extras) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI,
- (session, controller) -> {
- if (uri == null) {
- Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
- return;
- }
- session.getCallback().onPlayFromUri(session.getInstance(), controller, uri,
- extras);
- });
- }
-
- @Override
- public void playFromSearch(final IMediaController2 caller, final String query,
- final Bundle extras) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH,
- (session, controller) -> {
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallback().onPlayFromSearch(session.getInstance(),
- controller, query, extras);
- });
- }
-
- @Override
- public void playFromMediaId(final IMediaController2 caller, final String mediaId,
- final Bundle extras) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID,
- (session, controller) -> {
- if (mediaId == null) {
- Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
- return;
- }
- session.getCallback().onPlayFromMediaId(session.getInstance(), controller,
- mediaId, extras);
- });
- }
-
- @Override
- public void setRating(final IMediaController2 caller, final String mediaId,
- final Bundle ratingBundle) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_SESSION_SET_RATING,
- (session, controller) -> {
- if (mediaId == null) {
- Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
- return;
- }
- if (ratingBundle == null) {
- Log.w(TAG, "setRating(): Ignoring null ratingBundle from " + controller);
- return;
- }
- Rating2 rating = Rating2.fromBundle(ratingBundle);
- if (rating == null) {
- if (ratingBundle == null) {
- Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
- return;
- }
- return;
- }
- session.getCallback().onSetRating(session.getInstance(), controller, mediaId,
- rating);
- });
- }
-
- @Override
- public void setPlaylist(final IMediaController2 caller, final List<Bundle> playlist,
- final Bundle metadata) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST, (session, controller) -> {
- if (playlist == null) {
- Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
- return;
- }
- List<MediaItem2> list = new ArrayList<>();
- for (int i = 0; i < playlist.size(); i++) {
- // Recreates UUID in the playlist
- MediaItem2 item = MediaItem2Impl.fromBundle(playlist.get(i), null);
- if (item != null) {
- list.add(item);
- }
- }
- session.getInstance().setPlaylist(list, MediaMetadata2.fromBundle(metadata));
- });
- }
-
- @Override
- public void updatePlaylistMetadata(final IMediaController2 caller, final Bundle metadata) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA,
- (session, controller) -> {
- session.getInstance().updatePlaylistMetadata(MediaMetadata2.fromBundle(metadata));
- });
- }
-
- @Override
- public void addPlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM,
- (session, controller) -> {
- // Resets the UUID from the incoming media id, so controller may reuse a media
- // item multiple times for addPlaylistItem.
- session.getInstance().addPlaylistItem(index,
- MediaItem2Impl.fromBundle(mediaItem, null));
- });
- }
-
- @Override
- public void removePlaylistItem(IMediaController2 caller, Bundle mediaItem) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM,
- (session, controller) -> {
- MediaItem2 item = MediaItem2.fromBundle(mediaItem);
- // Note: MediaItem2 has hidden UUID to identify it across the processes.
- session.getInstance().removePlaylistItem(item);
- });
- }
-
- @Override
- public void replacePlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM,
- (session, controller) -> {
- // Resets the UUID from the incoming media id, so controller may reuse a media
- // item multiple times for replacePlaylistItem.
- session.getInstance().replacePlaylistItem(index,
- MediaItem2Impl.fromBundle(mediaItem, null));
- });
- }
-
- @Override
- public void skipToPlaylistItem(IMediaController2 caller, Bundle mediaItem) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM,
- (session, controller) -> {
- if (mediaItem == null) {
- Log.w(TAG, "skipToPlaylistItem(): Ignoring null mediaItem from "
- + controller);
- }
- // Note: MediaItem2 has hidden UUID to identify it across the processes.
- session.getInstance().skipToPlaylistItem(MediaItem2.fromBundle(mediaItem));
- });
- }
-
- @Override
- public void skipToPreviousItem(IMediaController2 caller) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_PREV_ITEM,
- (session, controller) -> {
- session.getInstance().skipToPreviousItem();
- });
- }
-
- @Override
- public void skipToNextItem(IMediaController2 caller) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_NEXT_ITEM,
- (session, controller) -> {
- session.getInstance().skipToNextItem();
- });
- }
-
- @Override
- public void setRepeatMode(IMediaController2 caller, int repeatMode) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE,
- (session, controller) -> {
- session.getInstance().setRepeatMode(repeatMode);
- });
- }
-
- @Override
- public void setShuffleMode(IMediaController2 caller, int shuffleMode) {
- onCommand(caller, SessionCommand2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE,
- (session, controller) -> {
- session.getInstance().setShuffleMode(shuffleMode);
- });
- }
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- // APIs for MediaSession2Impl
- //////////////////////////////////////////////////////////////////////////////////////////////
-
- // TODO(jaewan): (Can be Post-P) Need a way to get controller with permissions
- public List<ControllerInfo> getControllers() {
- ArrayList<ControllerInfo> controllers = new ArrayList<>();
- synchronized (mLock) {
- for (int i = 0; i < mControllers.size(); i++) {
- controllers.add(mControllers.valueAt(i));
- }
- }
- return controllers;
- }
-
- // Should be used without a lock to prevent potential deadlock.
- public void notifyPlayerStateChangedNotLocked(int state) {
- notifyAll((controller, iController) -> {
- iController.onPlayerStateChanged(state);
- });
- }
-
- // TODO(jaewan): Rename
- public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
- notifyAll((controller, iController) -> {
- iController.onPositionChanged(eventTimeMs, positionMs);
- });
- }
-
- public void notifyPlaybackSpeedChangedNotLocked(float speed) {
- notifyAll((controller, iController) -> {
- iController.onPlaybackSpeedChanged(speed);
- });
- }
-
- public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
- notifyAll((controller, iController) -> {
- iController.onBufferedPositionChanged(bufferedPositionMs);
- });
- }
-
- public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
- notify(controller, (unused, iController) -> {
- List<Bundle> layoutBundles = new ArrayList<>();
- for (int i = 0; i < layout.size(); i++) {
- Bundle bundle = ((CommandButtonImpl) layout.get(i).getProvider()).toBundle();
- if (bundle != null) {
- layoutBundles.add(bundle);
- }
- }
- iController.onCustomLayoutChanged(layoutBundles);
- });
- }
-
- public void notifyPlaylistChangedNotLocked(List<MediaItem2> playlist, MediaMetadata2 metadata) {
- final List<Bundle> bundleList;
- if (playlist != null) {
- bundleList = new ArrayList<>();
- for (int i = 0; i < playlist.size(); i++) {
- if (playlist.get(i) != null) {
- Bundle bundle = playlist.get(i).toBundle();
- if (bundle != null) {
- bundleList.add(bundle);
- }
- }
- }
- } else {
- bundleList = null;
- }
- final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
- notifyAll((controller, iController) -> {
- if (getControllerBinderIfAble(controller,
- SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST) != null) {
- iController.onPlaylistChanged(bundleList, metadataBundle);
- } else if (getControllerBinderIfAble(controller,
- SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA) != null) {
- iController.onPlaylistMetadataChanged(metadataBundle);
- }
- });
- }
-
- public void notifyPlaylistMetadataChangedNotLocked(MediaMetadata2 metadata) {
- final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
- notifyAll(SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA,
- (unused, iController) -> {
- iController.onPlaylistMetadataChanged(metadataBundle);
- });
- }
-
- public void notifyRepeatModeChangedNotLocked(int repeatMode) {
- notifyAll((unused, iController) -> {
- iController.onRepeatModeChanged(repeatMode);
- });
- }
-
- public void notifyShuffleModeChangedNotLocked(int shuffleMode) {
- notifyAll((unused, iController) -> {
- iController.onShuffleModeChanged(shuffleMode);
- });
- }
-
- public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
- final Bundle playbackInfoBundle =
- ((MediaController2Impl.PlaybackInfoImpl) playbackInfo.getProvider()).toBundle();
- notifyAll((unused, iController) -> {
- iController.onPlaybackInfoChanged(playbackInfoBundle);
- });
- }
-
- public void setAllowedCommands(ControllerInfo controller, SessionCommandGroup2 commands) {
- synchronized (mLock) {
- mAllowedCommandGroupMap.put(controller, commands);
- }
- notify(controller, (unused, iController) -> {
- iController.onAllowedCommandsChanged(commands.toBundle());
- });
- }
-
- public void sendCustomCommand(ControllerInfo controller, SessionCommand2 command, Bundle args,
- ResultReceiver receiver) {
- if (receiver != null && controller == null) {
- throw new IllegalArgumentException("Controller shouldn't be null if result receiver is"
- + " specified");
- }
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- notify(controller, (unused, iController) -> {
- Bundle commandBundle = command.toBundle();
- iController.onCustomCommand(commandBundle, args, null);
- });
- }
-
- public void sendCustomCommand(SessionCommand2 command, Bundle args) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- Bundle commandBundle = command.toBundle();
- notifyAll((unused, iController) -> {
- iController.onCustomCommand(commandBundle, args, null);
- });
- }
-
- public void notifyError(int errorCode, Bundle extras) {
- notifyAll((unused, iController) -> {
- iController.onError(errorCode, extras);
- });
- }
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- // Misc
- //////////////////////////////////////////////////////////////////////////////////////////////
-
- @FunctionalInterface
- private interface SessionRunnable {
- void run(final MediaSession2Impl session, final ControllerInfo controller);
- }
-
- @FunctionalInterface
- private interface NotifyRunnable {
- void run(final ControllerInfo controller,
- final IMediaController2 iController) throws RemoteException;
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/Rating2Impl.java b/packages/MediaComponents/src/com/android/media/Rating2Impl.java
deleted file mode 100644
index e2b9f0a..0000000
--- a/packages/MediaComponents/src/com/android/media/Rating2Impl.java
+++ /dev/null
@@ -1,190 +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 com.android.media;
-
-import static android.media.Rating2.*;
-
-import android.media.Rating2;
-import android.media.Rating2.Style;
-import android.media.update.Rating2Provider;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.util.Objects;
-
-public final class Rating2Impl implements Rating2Provider {
- private static final String TAG = "Rating2";
-
- private static final String KEY_STYLE = "android.media.rating2.style";
- private static final String KEY_VALUE = "android.media.rating2.value";
-
- private final static float RATING_NOT_RATED = -1.0f;
-
- private final Rating2 mInstance;
- private final int mRatingStyle;
- private final float mRatingValue;
-
- private Rating2Impl(@Style int ratingStyle, float rating) {
- mRatingStyle = ratingStyle;
- mRatingValue = rating;
- mInstance = new Rating2(this);
- }
-
- @Override
- public String toString_impl() {
- return "Rating2:style=" + mRatingStyle + " rating="
- + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
- }
-
- @Override
- public boolean equals_impl(Object obj) {
- if (!(obj instanceof Rating2)) {
- return false;
- }
- Rating2Impl other = (Rating2Impl) ((Rating2) obj).getProvider();
- return mRatingStyle == other.mRatingStyle
- && mRatingValue == other.mRatingValue;
- }
-
- @Override
- public int hashCode_impl() {
- return Objects.hash(mRatingStyle, mRatingValue);
- }
-
- Rating2 getInstance() {
- return mInstance;
- }
-
- public static Rating2 fromBundle_impl(Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- return new Rating2Impl(bundle.getInt(KEY_STYLE), bundle.getFloat(KEY_VALUE)).getInstance();
- }
-
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_STYLE, mRatingStyle);
- bundle.putFloat(KEY_VALUE, mRatingValue);
- return bundle;
- }
-
- public static Rating2 newUnratedRating_impl(@Style int ratingStyle) {
- switch(ratingStyle) {
- case RATING_HEART:
- case RATING_THUMB_UP_DOWN:
- case RATING_3_STARS:
- case RATING_4_STARS:
- case RATING_5_STARS:
- case RATING_PERCENTAGE:
- return new Rating2Impl(ratingStyle, RATING_NOT_RATED).getInstance();
- default:
- return null;
- }
- }
-
- public static Rating2 newHeartRating_impl(boolean hasHeart) {
- return new Rating2Impl(RATING_HEART, hasHeart ? 1.0f : 0.0f).getInstance();
- }
-
- public static Rating2 newThumbRating_impl(boolean thumbIsUp) {
- return new Rating2Impl(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f).getInstance();
- }
-
- public static Rating2 newStarRating_impl(int starRatingStyle, float starRating) {
- float maxRating = RATING_NOT_RATED;
- switch(starRatingStyle) {
- case RATING_3_STARS:
- maxRating = 3.0f;
- break;
- case RATING_4_STARS:
- maxRating = 4.0f;
- break;
- case RATING_5_STARS:
- maxRating = 5.0f;
- break;
- default:
- Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
- return null;
- }
- if ((starRating < 0.0f) || (starRating > maxRating)) {
- Log.e(TAG, "Trying to set out of range star-based rating");
- return null;
- }
- return new Rating2Impl(starRatingStyle, starRating).getInstance();
- }
-
- public static Rating2 newPercentageRating_impl(float percent) {
- if ((percent < 0.0f) || (percent > 100.0f)) {
- Log.e(TAG, "Invalid percentage-based rating value");
- return null;
- } else {
- return new Rating2Impl(RATING_PERCENTAGE, percent).getInstance();
- }
- }
-
- @Override
- public boolean isRated_impl() {
- return mRatingValue >= 0.0f;
- }
-
- @Override
- public int getRatingStyle_impl() {
- return mRatingStyle;
- }
-
- @Override
- public boolean hasHeart_impl() {
- if (mRatingStyle != RATING_HEART) {
- return false;
- } else {
- return (mRatingValue == 1.0f);
- }
- }
-
- @Override
- public boolean isThumbUp_impl() {
- if (mRatingStyle != RATING_THUMB_UP_DOWN) {
- return false;
- } else {
- return (mRatingValue == 1.0f);
- }
- }
-
- @Override
- public float getStarRating_impl() {
- switch (mRatingStyle) {
- case RATING_3_STARS:
- case RATING_4_STARS:
- case RATING_5_STARS:
- if (mInstance.isRated()) {
- return mRatingValue;
- }
- default:
- return -1.0f;
- }
- }
-
- @Override
- public float getPercentRating_impl() {
- if ((mRatingStyle != RATING_PERCENTAGE) || !mInstance.isRated()) {
- return -1.0f;
- } else {
- return mRatingValue;
- }
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java b/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java
deleted file mode 100644
index 1c570aa..0000000
--- a/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java
+++ /dev/null
@@ -1,494 +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 com.android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
-import android.media.MediaPlayerBase.PlayerEventCallback;
-import android.media.MediaPlaylistAgent;
-import android.media.MediaSession2.OnDataSourceMissingHelper;
-import android.util.ArrayMap;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ThreadLocalRandom;
-
-public class SessionPlaylistAgent extends MediaPlaylistAgent {
- private static final String TAG = "SessionPlaylistAgent";
- @VisibleForTesting
- static final int END_OF_PLAYLIST = -1;
- @VisibleForTesting
- static final int NO_VALID_ITEMS = -2;
-
- private final PlayItem mEopPlayItem = new PlayItem(END_OF_PLAYLIST, null);
-
- private final Object mLock = new Object();
- private final MediaSession2Impl mSessionImpl;
- private final MyPlayerEventCallback mPlayerCallback;
-
- @GuardedBy("mLock")
- private MediaPlayerBase mPlayer;
- @GuardedBy("mLock")
- private OnDataSourceMissingHelper mDsmHelper;
- // TODO: Check if having the same item is okay (b/74090741)
- @GuardedBy("mLock")
- private ArrayList<MediaItem2> mPlaylist = new ArrayList<>();
- @GuardedBy("mLock")
- private ArrayList<MediaItem2> mShuffledList = new ArrayList<>();
- @GuardedBy("mLock")
- private Map<MediaItem2, DataSourceDesc> mItemDsdMap = new ArrayMap<>();
- @GuardedBy("mLock")
- private MediaMetadata2 mMetadata;
- @GuardedBy("mLock")
- private int mRepeatMode;
- @GuardedBy("mLock")
- private int mShuffleMode;
- @GuardedBy("mLock")
- private PlayItem mCurrent;
-
- // Called on session callback executor.
- private class MyPlayerEventCallback extends PlayerEventCallback {
- public void onCurrentDataSourceChanged(@NonNull MediaPlayerBase mpb,
- @Nullable DataSourceDesc dsd) {
- if (mPlayer != mpb) {
- return;
- }
- synchronized (mLock) {
- if (dsd == null && mCurrent != null) {
- mCurrent = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
- updateCurrentIfNeededLocked();
- }
- }
- }
- }
-
- private class PlayItem {
- int shuffledIdx;
- DataSourceDesc dsd;
- MediaItem2 mediaItem;
-
- PlayItem(int shuffledIdx) {
- this(shuffledIdx, null);
- }
-
- PlayItem(int shuffledIdx, DataSourceDesc dsd) {
- this.shuffledIdx = shuffledIdx;
- if (shuffledIdx >= 0) {
- this.mediaItem = mShuffledList.get(shuffledIdx);
- if (dsd == null) {
- synchronized (mLock) {
- this.dsd = retrieveDataSourceDescLocked(this.mediaItem);
- }
- } else {
- this.dsd = dsd;
- }
- }
- }
-
- boolean isValid() {
- if (this == mEopPlayItem) {
- return true;
- }
- if (mediaItem == null) {
- return false;
- }
- if (dsd == null) {
- return false;
- }
- if (shuffledIdx >= mShuffledList.size()) {
- return false;
- }
- if (mediaItem != mShuffledList.get(shuffledIdx)) {
- return false;
- }
- if (mediaItem.getDataSourceDesc() != null
- && !mediaItem.getDataSourceDesc().equals(dsd)) {
- return false;
- }
- return true;
- }
- }
-
- public SessionPlaylistAgent(@NonNull MediaSession2Impl sessionImpl,
- @NonNull MediaPlayerBase player) {
- if (sessionImpl == null) {
- throw new IllegalArgumentException("sessionImpl shouldn't be null");
- }
- if (player == null) {
- throw new IllegalArgumentException("player shouldn't be null");
- }
- mSessionImpl = sessionImpl;
- mPlayer = player;
- mPlayerCallback = new MyPlayerEventCallback();
- mPlayer.registerPlayerEventCallback(mSessionImpl.getCallbackExecutor(), mPlayerCallback);
- }
-
- public void setPlayer(@NonNull MediaPlayerBase player) {
- if (player == null) {
- throw new IllegalArgumentException("player shouldn't be null");
- }
- synchronized (mLock) {
- if (player == mPlayer) {
- return;
- }
- mPlayer.unregisterPlayerEventCallback(mPlayerCallback);
- mPlayer = player;
- mPlayer.registerPlayerEventCallback(
- mSessionImpl.getCallbackExecutor(), mPlayerCallback);
- updatePlayerDataSourceLocked();
- }
- }
-
- public void setOnDataSourceMissingHelper(OnDataSourceMissingHelper helper) {
- synchronized (mLock) {
- mDsmHelper = helper;
- }
- }
-
- public void clearOnDataSourceMissingHelper() {
- synchronized (mLock) {
- mDsmHelper = null;
- }
- }
-
- @Override
- public @Nullable List<MediaItem2> getPlaylist() {
- synchronized (mLock) {
- return Collections.unmodifiableList(mPlaylist);
- }
- }
-
- @Override
- public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
- if (list == null) {
- throw new IllegalArgumentException("list shouldn't be null");
- }
-
- synchronized (mLock) {
- mItemDsdMap.clear();
-
- mPlaylist.clear();
- mPlaylist.addAll(list);
- applyShuffleModeLocked();
-
- mMetadata = metadata;
- mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
- updatePlayerDataSourceLocked();
- }
- notifyPlaylistChanged();
- }
-
- @Override
- public @Nullable MediaMetadata2 getPlaylistMetadata() {
- return mMetadata;
- }
-
- @Override
- public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
- synchronized (mLock) {
- if (metadata == mMetadata) {
- return;
- }
- mMetadata = metadata;
- }
- notifyPlaylistMetadataChanged();
- }
-
- @Override
- public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- synchronized (mLock) {
- index = clamp(index, mPlaylist.size());
- int shuffledIdx = index;
- mPlaylist.add(index, item);
- if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
- mShuffledList.add(index, item);
- } else {
- // Add the item in random position of mShuffledList.
- shuffledIdx = ThreadLocalRandom.current().nextInt(mShuffledList.size() + 1);
- mShuffledList.add(shuffledIdx, item);
- }
- if (!hasValidItem()) {
- mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
- updatePlayerDataSourceLocked();
- } else {
- updateCurrentIfNeededLocked();
- }
- }
- notifyPlaylistChanged();
- }
-
- @Override
- public void removePlaylistItem(@NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- synchronized (mLock) {
- if (!mPlaylist.remove(item)) {
- return;
- }
- mShuffledList.remove(item);
- mItemDsdMap.remove(item);
- updateCurrentIfNeededLocked();
- }
- notifyPlaylistChanged();
- }
-
- @Override
- public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- synchronized (mLock) {
- if (mPlaylist.size() <= 0) {
- return;
- }
- index = clamp(index, mPlaylist.size() - 1);
- int shuffledIdx = mShuffledList.indexOf(mPlaylist.get(index));
- mItemDsdMap.remove(mShuffledList.get(shuffledIdx));
- mShuffledList.set(shuffledIdx, item);
- mPlaylist.set(index, item);
- if (!hasValidItem()) {
- mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
- updatePlayerDataSourceLocked();
- } else {
- updateCurrentIfNeededLocked();
- }
- }
- notifyPlaylistChanged();
- }
-
- @Override
- public void skipToPlaylistItem(@NonNull MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- synchronized (mLock) {
- if (!hasValidItem() || item.equals(mCurrent.mediaItem)) {
- return;
- }
- int shuffledIdx = mShuffledList.indexOf(item);
- if (shuffledIdx < 0) {
- return;
- }
- mCurrent = new PlayItem(shuffledIdx);
- updateCurrentIfNeededLocked();
- }
- }
-
- @Override
- public void skipToPreviousItem() {
- synchronized (mLock) {
- if (!hasValidItem()) {
- return;
- }
- PlayItem prev = getNextValidPlayItemLocked(mCurrent.shuffledIdx, -1);
- if (prev != mEopPlayItem) {
- mCurrent = prev;
- }
- updateCurrentIfNeededLocked();
- }
- }
-
- @Override
- public void skipToNextItem() {
- synchronized (mLock) {
- if (!hasValidItem() || mCurrent == mEopPlayItem) {
- return;
- }
- PlayItem next = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
- if (next != mEopPlayItem) {
- mCurrent = next;
- }
- updateCurrentIfNeededLocked();
- }
- }
-
- @Override
- public int getRepeatMode() {
- return mRepeatMode;
- }
-
- @Override
- public void setRepeatMode(int repeatMode) {
- if (repeatMode < MediaPlaylistAgent.REPEAT_MODE_NONE
- || repeatMode > MediaPlaylistAgent.REPEAT_MODE_GROUP) {
- return;
- }
- synchronized (mLock) {
- if (mRepeatMode == repeatMode) {
- return;
- }
- mRepeatMode = repeatMode;
- switch (repeatMode) {
- case MediaPlaylistAgent.REPEAT_MODE_ONE:
- if (mCurrent != null && mCurrent != mEopPlayItem) {
- mPlayer.loopCurrent(true);
- }
- break;
- case MediaPlaylistAgent.REPEAT_MODE_ALL:
- case MediaPlaylistAgent.REPEAT_MODE_GROUP:
- if (mCurrent == mEopPlayItem) {
- mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
- updatePlayerDataSourceLocked();
- }
- // pass through
- case MediaPlaylistAgent.REPEAT_MODE_NONE:
- mPlayer.loopCurrent(false);
- break;
- }
- }
- notifyRepeatModeChanged();
- }
-
- @Override
- public int getShuffleMode() {
- return mShuffleMode;
- }
-
- @Override
- public void setShuffleMode(int shuffleMode) {
- if (shuffleMode < MediaPlaylistAgent.SHUFFLE_MODE_NONE
- || shuffleMode > MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
- return;
- }
- synchronized (mLock) {
- if (mShuffleMode == shuffleMode) {
- return;
- }
- mShuffleMode = shuffleMode;
- applyShuffleModeLocked();
- updateCurrentIfNeededLocked();
- }
- notifyShuffleModeChanged();
- }
-
- @VisibleForTesting
- int getCurShuffledIndex() {
- return hasValidItem() ? mCurrent.shuffledIdx : NO_VALID_ITEMS;
- }
-
- private boolean hasValidItem() {
- return mCurrent != null;
- }
-
- private DataSourceDesc retrieveDataSourceDescLocked(MediaItem2 item) {
- DataSourceDesc dsd = item.getDataSourceDesc();
- if (dsd != null) {
- mItemDsdMap.put(item, dsd);
- return dsd;
- }
- dsd = mItemDsdMap.get(item);
- if (dsd != null) {
- return dsd;
- }
- OnDataSourceMissingHelper helper = mDsmHelper;
- if (helper != null) {
- // TODO: Do not call onDataSourceMissing with the lock (b/74090741).
- dsd = helper.onDataSourceMissing(mSessionImpl.getInstance(), item);
- if (dsd != null) {
- mItemDsdMap.put(item, dsd);
- }
- }
- return dsd;
- }
-
- // TODO: consider to call updateCurrentIfNeededLocked inside (b/74090741)
- private PlayItem getNextValidPlayItemLocked(int curShuffledIdx, int direction) {
- int size = mPlaylist.size();
- if (curShuffledIdx == END_OF_PLAYLIST) {
- curShuffledIdx = (direction > 0) ? -1 : size;
- }
- for (int i = 0; i < size; i++) {
- curShuffledIdx += direction;
- if (curShuffledIdx < 0 || curShuffledIdx >= mPlaylist.size()) {
- if (mRepeatMode == REPEAT_MODE_NONE) {
- return (i == size - 1) ? null : mEopPlayItem;
- } else {
- curShuffledIdx = curShuffledIdx < 0 ? mPlaylist.size() - 1 : 0;
- }
- }
- DataSourceDesc dsd = retrieveDataSourceDescLocked(mShuffledList.get(curShuffledIdx));
- if (dsd != null) {
- return new PlayItem(curShuffledIdx, dsd);
- }
- }
- return null;
- }
-
- private void updateCurrentIfNeededLocked() {
- if (!hasValidItem() || mCurrent.isValid()) {
- return;
- }
- int shuffledIdx = mShuffledList.indexOf(mCurrent.mediaItem);
- if (shuffledIdx >= 0) {
- // Added an item.
- mCurrent.shuffledIdx = shuffledIdx;
- return;
- }
-
- if (mCurrent.shuffledIdx >= mShuffledList.size()) {
- mCurrent = getNextValidPlayItemLocked(mShuffledList.size() - 1, 1);
- } else {
- mCurrent.mediaItem = mShuffledList.get(mCurrent.shuffledIdx);
- if (retrieveDataSourceDescLocked(mCurrent.mediaItem) == null) {
- mCurrent = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
- }
- }
- updatePlayerDataSourceLocked();
- return;
- }
-
- private void updatePlayerDataSourceLocked() {
- if (mCurrent == null || mCurrent == mEopPlayItem) {
- return;
- }
- if (mPlayer.getCurrentDataSource() != mCurrent.dsd) {
- mPlayer.setDataSource(mCurrent.dsd);
- mPlayer.loopCurrent(mRepeatMode == MediaPlaylistAgent.REPEAT_MODE_ONE);
- }
- // TODO: Call setNextDataSource (b/74090741)
- }
-
- private void applyShuffleModeLocked() {
- mShuffledList.clear();
- mShuffledList.addAll(mPlaylist);
- if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_ALL
- || mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
- Collections.shuffle(mShuffledList);
- }
- }
-
- // Clamps value to [0, size]
- private static int clamp(int value, int size) {
- if (value < 0) {
- return 0;
- }
- return (value > size) ? size : value;
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
deleted file mode 100644
index 396ebbb..0000000
--- a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
+++ /dev/null
@@ -1,217 +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 com.android.media;
-
-import static android.media.SessionToken2.TYPE_LIBRARY_SERVICE;
-import static android.media.SessionToken2.TYPE_SESSION;
-import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.media.SessionToken2;
-import android.media.SessionToken2.TokenType;
-import android.media.update.SessionToken2Provider;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.text.TextUtils;
-
-import java.util.List;
-
-public class SessionToken2Impl implements SessionToken2Provider {
- private static final String KEY_UID = "android.media.token.uid";
- private static final String KEY_TYPE = "android.media.token.type";
- private static final String KEY_PACKAGE_NAME = "android.media.token.package_name";
- private static final String KEY_SERVICE_NAME = "android.media.token.service_name";
- private static final String KEY_ID = "android.media.token.id";
- private static final String KEY_SESSION_BINDER = "android.media.token.session_binder";
-
- private final SessionToken2 mInstance;
- private final int mUid;
- private final @TokenType int mType;
- private final String mPackageName;
- private final String mServiceName;
- private final String mId;
- private final IMediaSession2 mSessionBinder;
-
- /**
- * Public constructor for the legacy support (i.e. browser can try connecting to any browser
- * service if it knows the service name)
- */
- public SessionToken2Impl(Context context, SessionToken2 instance,
- String packageName, String serviceName, int uid) {
- if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("packageName shouldn't be empty");
- }
- if (TextUtils.isEmpty(serviceName)) {
- throw new IllegalArgumentException("serviceName shouldn't be empty");
- }
- mInstance = instance;
- // Calculate uid if it's not specified.
- final PackageManager manager = context.getPackageManager();
- if (uid < 0) {
- try {
- uid = manager.getPackageUid(packageName, 0);
- } catch (NameNotFoundException e) {
- throw new IllegalArgumentException("Cannot find package " + packageName);
- }
- }
- mUid = uid;
- mId = null;
- mType = -1;
- mPackageName = packageName;
- mServiceName = serviceName;
- mSessionBinder = null;
- }
-
- SessionToken2Impl(int uid, int type, String packageName, String serviceName, String id,
- IMediaSession2 sessionBinder) {
- // TODO(jaewan): Add sanity check (b/73863865)
- mUid = uid;
- mType = type;
- mPackageName = packageName;
- mServiceName = serviceName;
- mId = id;
- mSessionBinder = sessionBinder;
- mInstance = new SessionToken2(this);
- }
-
- public static String getSessionId(ResolveInfo resolveInfo) {
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- return null;
- } else if (resolveInfo.serviceInfo.metaData == null) {
- return "";
- }
- return null;
- }
-
- public SessionToken2 getInstance() {
- return mInstance;
- }
-
- @Override
- public String getPackageName_impl() {
- return mPackageName;
- }
-
- @Override
- public int getUid_impl() {
- return mUid;
- }
-
- @Override
- public String getId_imp() {
- return mId;
- }
-
- @Override
- public int getType_impl() {
- return mType;
- }
-
- String getServiceName() {
- return mServiceName;
- }
-
- IMediaSession2 getSessionBinder() {
- return mSessionBinder;
- }
-
- public static SessionToken2 fromBundle_impl(Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- final int uid = bundle.getInt(KEY_UID);
- final @TokenType int type = bundle.getInt(KEY_TYPE, -1);
- final String packageName = bundle.getString(KEY_PACKAGE_NAME);
- final String serviceName = bundle.getString(KEY_SERVICE_NAME);
- final String id = bundle.getString(KEY_ID);
- final IBinder sessionBinder = bundle.getBinder(KEY_SESSION_BINDER);
-
- // Sanity check.
- switch (type) {
- case TYPE_SESSION:
- if (sessionBinder == null) {
- throw new IllegalArgumentException("Unexpected sessionBinder for session,"
- + " binder=" + sessionBinder);
- }
- break;
- case TYPE_SESSION_SERVICE:
- case TYPE_LIBRARY_SERVICE:
- if (TextUtils.isEmpty(serviceName)) {
- throw new IllegalArgumentException("Session service needs service name");
- }
- break;
- default:
- throw new IllegalArgumentException("Invalid type");
- }
- if (TextUtils.isEmpty(packageName) || id == null) {
- throw new IllegalArgumentException("Package name nor ID cannot be null.");
- }
- return new SessionToken2Impl(uid, type, packageName, serviceName, id,
- sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null)
- .getInstance();
- }
-
- @Override
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_UID, mUid);
- bundle.putString(KEY_PACKAGE_NAME, mPackageName);
- bundle.putString(KEY_SERVICE_NAME, mServiceName);
- bundle.putString(KEY_ID, mId);
- bundle.putInt(KEY_TYPE, mType);
- bundle.putBinder(KEY_SESSION_BINDER,
- mSessionBinder != null ? mSessionBinder.asBinder() : null);
- return bundle;
- }
-
- @Override
- public int hashCode_impl() {
- final int prime = 31;
- return mType
- + prime * (mUid
- + prime * (mPackageName.hashCode()
- + prime * (mId.hashCode()
- + prime * (mServiceName != null ? mServiceName.hashCode() : 0))));
- }
-
- @Override
- public boolean equals_impl(Object obj) {
- if (!(obj instanceof SessionToken2)) {
- return false;
- }
- SessionToken2Impl other = from((SessionToken2) obj);
- return mUid == other.mUid
- && TextUtils.equals(mPackageName, other.mPackageName)
- && TextUtils.equals(mServiceName, other.mServiceName)
- && TextUtils.equals(mId, other.mId)
- && mType == other.mType;
- }
-
- @Override
- public String toString_impl() {
- return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType
- + " service=" + mServiceName + " binder=" + mSessionBinder + "}";
- }
-
- static SessionToken2Impl from(SessionToken2 token) {
- return ((SessionToken2Impl) token.getProvider());
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/VolumeProvider2Impl.java b/packages/MediaComponents/src/com/android/media/VolumeProvider2Impl.java
deleted file mode 100644
index bf22e1b..0000000
--- a/packages/MediaComponents/src/com/android/media/VolumeProvider2Impl.java
+++ /dev/null
@@ -1,93 +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 com.android.media;
-
-import static android.media.VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
-import static android.media.VolumeProvider2.VOLUME_CONTROL_FIXED;
-import static android.media.VolumeProvider2.VOLUME_CONTROL_RELATIVE;
-
-import android.media.VolumeProvider2;
-import android.media.update.VolumeProvider2Provider;
-
-public class VolumeProvider2Impl implements VolumeProvider2Provider {
- private final VolumeProvider2 mInstance;
- private final int mControlType;
- private final int mMaxVolume;
-
- private int mCurrentVolume;
- private Callback mCallback;
-
- public VolumeProvider2Impl(VolumeProvider2 instance,
- @VolumeProvider2.ControlType int controlType, int maxVolume, int currentVolume) {
- if (controlType != VOLUME_CONTROL_FIXED && controlType != VOLUME_CONTROL_RELATIVE
- && controlType != VOLUME_CONTROL_ABSOLUTE) {
- throw new IllegalArgumentException("wrong controlType " + controlType);
- }
- if (maxVolume < 0 || currentVolume < 0) {
- throw new IllegalArgumentException("volume shouldn't be negative"
- + ", maxVolume=" + maxVolume + ", currentVolume=" + currentVolume);
- }
- if (currentVolume > maxVolume) {
- throw new IllegalArgumentException("currentVolume shouldn't be greater than maxVolume"
- + ", maxVolume=" + maxVolume + ", currentVolume=" + currentVolume);
- }
- mInstance = instance;
- mControlType = controlType;
- mMaxVolume = maxVolume;
- mCurrentVolume = currentVolume;
- }
-
- @Override
- public int getControlType_impl() {
- return mControlType;
- }
-
- @Override
- public int getMaxVolume_impl() {
- return mMaxVolume;
- }
-
- @Override
- public int getCurrentVolume_impl() {
- return mCurrentVolume;
- }
-
- @Override
- public void setCurrentVolume_impl(int currentVolume) {
- if (currentVolume < 0) {
- throw new IllegalArgumentException("currentVolume shouldn't be negative"
- + ", currentVolume=" + currentVolume);
- }
- mCurrentVolume = currentVolume;
- if (mCallback != null) {
- mCallback.onVolumeChanged(mInstance);
- }
- }
-
- /**
- * Sets a callback to receive volume changes.
- */
- public void setCallback(Callback callback) {
- mCallback = callback;
- }
-
- /**
- * Listens for changes to the volume.
- */
- public static abstract class Callback {
- public abstract void onVolumeChanged(VolumeProvider2 volumeProvider);
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java b/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java
deleted file mode 100644
index af36d7f..0000000
--- a/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java
+++ /dev/null
@@ -1,89 +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 com.android.media.subtitle;
-
-// Note: This is just copied from android.media.MediaTimeProvider.
-public interface MediaTimeProvider {
- // we do not allow negative media time
- /**
- * Presentation time value if no timed event notification is requested.
- */
- public final static long NO_TIME = -1;
-
- /**
- * Cancels all previous notification request from this listener if any. It
- * registers the listener to get seek and stop notifications. If timeUs is
- * not negative, it also registers the listener for a timed event
- * notification when the presentation time reaches (becomes greater) than
- * the value specified. This happens immediately if the current media time
- * is larger than or equal to timeUs.
- *
- * @param timeUs presentation time to get timed event callback at (or
- * {@link #NO_TIME})
- */
- public void notifyAt(long timeUs, OnMediaTimeListener listener);
-
- /**
- * Cancels all previous notification request from this listener if any. It
- * registers the listener to get seek and stop notifications. If the media
- * is stopped, the listener will immediately receive a stop notification.
- * Otherwise, it will receive a timed event notificaton.
- */
- public void scheduleUpdate(OnMediaTimeListener listener);
-
- /**
- * Cancels all previous notification request from this listener if any.
- */
- public void cancelNotifications(OnMediaTimeListener listener);
-
- /**
- * Get the current presentation time.
- *
- * @param precise Whether getting a precise time is important. This is
- * more costly.
- * @param monotonic Whether returned time should be monotonic: that is,
- * greater than or equal to the last returned time. Don't
- * always set this to true. E.g. this has undesired
- * consequences if the media is seeked between calls.
- * @throws IllegalStateException if the media is not initialized
- */
- public long getCurrentTimeUs(boolean precise, boolean monotonic)
- throws IllegalStateException;
-
- public static interface OnMediaTimeListener {
- /**
- * Called when the registered time was reached naturally.
- *
- * @param timeUs current media time
- */
- void onTimedEvent(long timeUs);
-
- /**
- * Called when the media time changed due to seeking.
- *
- * @param timeUs current media time
- */
- void onSeek(long timeUs);
-
- /**
- * Called when the playback stopped. This is not called on pause, only
- * on full stop, at which point there is no further current media time.
- */
- void onStop();
- }
-}
-
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java
deleted file mode 100644
index 97d3927..0000000
--- a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java
+++ /dev/null
@@ -1,507 +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 com.android.media.subtitle;
-
-import android.content.Context;
-import android.media.MediaFormat;
-import android.media.MediaPlayer2.TrackInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.view.accessibility.CaptioningManager;
-
-import com.android.media.subtitle.SubtitleTrack.RenderingWidget;
-
-import java.util.Locale;
-import java.util.Vector;
-
-// Note: This is forked from android.media.SubtitleController since P
-/**
- * The subtitle controller provides the architecture to display subtitles for a
- * media source. It allows specifying which tracks to display, on which anchor
- * to display them, and also allows adding external, out-of-band subtitle tracks.
- */
-public class SubtitleController {
- private MediaTimeProvider mTimeProvider;
- private Vector<Renderer> mRenderers;
- private Vector<SubtitleTrack> mTracks;
- private SubtitleTrack mSelectedTrack;
- private boolean mShowing;
- private CaptioningManager mCaptioningManager;
- private Handler mHandler;
-
- private static final int WHAT_SHOW = 1;
- private static final int WHAT_HIDE = 2;
- private static final int WHAT_SELECT_TRACK = 3;
- private static final int WHAT_SELECT_DEFAULT_TRACK = 4;
-
- private final Handler.Callback mCallback = new Handler.Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case WHAT_SHOW:
- doShow();
- return true;
- case WHAT_HIDE:
- doHide();
- return true;
- case WHAT_SELECT_TRACK:
- doSelectTrack((SubtitleTrack)msg.obj);
- return true;
- case WHAT_SELECT_DEFAULT_TRACK:
- doSelectDefaultTrack();
- return true;
- default:
- return false;
- }
- }
- };
-
- private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener =
- new CaptioningManager.CaptioningChangeListener() {
- @Override
- public void onEnabledChanged(boolean enabled) {
- selectDefaultTrack();
- }
-
- @Override
- public void onLocaleChanged(Locale locale) {
- selectDefaultTrack();
- }
- };
-
- public SubtitleController(Context context) {
- this(context, null, null);
- }
-
- /**
- * Creates a subtitle controller for a media playback object that implements
- * the MediaTimeProvider interface.
- *
- * @param timeProvider
- */
- public SubtitleController(
- Context context,
- MediaTimeProvider timeProvider,
- Listener listener) {
- mTimeProvider = timeProvider;
- mListener = listener;
-
- mRenderers = new Vector<Renderer>();
- mShowing = false;
- mTracks = new Vector<SubtitleTrack>();
- mCaptioningManager =
- (CaptioningManager)context.getSystemService(Context.CAPTIONING_SERVICE);
- }
-
- @Override
- protected void finalize() throws Throwable {
- mCaptioningManager.removeCaptioningChangeListener(
- mCaptioningChangeListener);
- super.finalize();
- }
-
- /**
- * @return the available subtitle tracks for this media. These include
- * the tracks found by {@link MediaPlayer} as well as any tracks added
- * manually via {@link #addTrack}.
- */
- public SubtitleTrack[] getTracks() {
- synchronized(mTracks) {
- SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()];
- mTracks.toArray(tracks);
- return tracks;
- }
- }
-
- /**
- * @return the currently selected subtitle track
- */
- public SubtitleTrack getSelectedTrack() {
- return mSelectedTrack;
- }
-
- private RenderingWidget getRenderingWidget() {
- if (mSelectedTrack == null) {
- return null;
- }
- return mSelectedTrack.getRenderingWidget();
- }
-
- /**
- * Selects a subtitle track. As a result, this track will receive
- * in-band data from the {@link MediaPlayer}. However, this does
- * not change the subtitle visibility.
- *
- * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
- *
- * @param track The subtitle track to select. This must be one of the
- * tracks in {@link #getTracks}.
- * @return true if the track was successfully selected.
- */
- public boolean selectTrack(SubtitleTrack track) {
- if (track != null && !mTracks.contains(track)) {
- return false;
- }
-
- processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track));
- return true;
- }
-
- private void doSelectTrack(SubtitleTrack track) {
- mTrackIsExplicit = true;
- if (mSelectedTrack == track) {
- return;
- }
-
- if (mSelectedTrack != null) {
- mSelectedTrack.hide();
- mSelectedTrack.setTimeProvider(null);
- }
-
- mSelectedTrack = track;
- if (mAnchor != null) {
- mAnchor.setSubtitleWidget(getRenderingWidget());
- }
-
- if (mSelectedTrack != null) {
- mSelectedTrack.setTimeProvider(mTimeProvider);
- mSelectedTrack.show();
- }
-
- if (mListener != null) {
- mListener.onSubtitleTrackSelected(track);
- }
- }
-
- /**
- * @return the default subtitle track based on system preferences, or null,
- * if no such track exists in this manager.
- *
- * Supports HLS-flags: AUTOSELECT, FORCED & DEFAULT.
- *
- * 1. If captioning is disabled, only consider FORCED tracks. Otherwise,
- * consider all tracks, but prefer non-FORCED ones.
- * 2. If user selected "Default" caption language:
- * a. If there is a considered track with DEFAULT=yes, returns that track
- * (favor the first one in the current language if there are more than
- * one default tracks, or the first in general if none of them are in
- * the current language).
- * b. Otherwise, if there is a track with AUTOSELECT=yes in the current
- * language, return that one.
- * c. If there are no default tracks, and no autoselectable tracks in the
- * current language, return null.
- * 3. If there is a track with the caption language, select that one. Prefer
- * the one with AUTOSELECT=no.
- *
- * The default values for these flags are DEFAULT=no, AUTOSELECT=yes
- * and FORCED=no.
- */
- public SubtitleTrack getDefaultTrack() {
- SubtitleTrack bestTrack = null;
- int bestScore = -1;
-
- Locale selectedLocale = mCaptioningManager.getLocale();
- Locale locale = selectedLocale;
- if (locale == null) {
- locale = Locale.getDefault();
- }
- boolean selectForced = !mCaptioningManager.isEnabled();
-
- synchronized(mTracks) {
- for (SubtitleTrack track: mTracks) {
- MediaFormat format = track.getFormat();
- String language = format.getString(MediaFormat.KEY_LANGUAGE);
- boolean forced =
- format.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0;
- boolean autoselect =
- format.getInteger(MediaFormat.KEY_IS_AUTOSELECT, 1) != 0;
- boolean is_default =
- format.getInteger(MediaFormat.KEY_IS_DEFAULT, 0) != 0;
-
- boolean languageMatches =
- (locale == null ||
- locale.getLanguage().equals("") ||
- locale.getISO3Language().equals(language) ||
- locale.getLanguage().equals(language));
- // is_default is meaningless unless caption language is 'default'
- int score = (forced ? 0 : 8) +
- (((selectedLocale == null) && is_default) ? 4 : 0) +
- (autoselect ? 0 : 2) + (languageMatches ? 1 : 0);
-
- if (selectForced && !forced) {
- continue;
- }
-
- // we treat null locale/language as matching any language
- if ((selectedLocale == null && is_default) ||
- (languageMatches &&
- (autoselect || forced || selectedLocale != null))) {
- if (score > bestScore) {
- bestScore = score;
- bestTrack = track;
- }
- }
- }
- }
- return bestTrack;
- }
-
- private boolean mTrackIsExplicit = false;
- private boolean mVisibilityIsExplicit = false;
-
- /** should be called from anchor thread */
- public void selectDefaultTrack() {
- processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK));
- }
-
- private void doSelectDefaultTrack() {
- if (mTrackIsExplicit) {
- // If track selection is explicit, but visibility
- // is not, it falls back to the captioning setting
- if (!mVisibilityIsExplicit) {
- if (mCaptioningManager.isEnabled() ||
- (mSelectedTrack != null &&
- mSelectedTrack.getFormat().getInteger(
- MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) {
- show();
- } else if (mSelectedTrack != null
- && mSelectedTrack.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
- hide();
- }
- mVisibilityIsExplicit = false;
- }
- return;
- }
-
- // We can have a default (forced) track even if captioning
- // is not enabled. This is handled by getDefaultTrack().
- // Show this track unless subtitles were explicitly hidden.
- SubtitleTrack track = getDefaultTrack();
- if (track != null) {
- selectTrack(track);
- mTrackIsExplicit = false;
- if (!mVisibilityIsExplicit) {
- show();
- mVisibilityIsExplicit = false;
- }
- }
- }
-
- /** must be called from anchor thread */
- public void reset() {
- checkAnchorLooper();
- hide();
- selectTrack(null);
- mTracks.clear();
- mTrackIsExplicit = false;
- mVisibilityIsExplicit = false;
- mCaptioningManager.removeCaptioningChangeListener(
- mCaptioningChangeListener);
- }
-
- /**
- * Adds a new, external subtitle track to the manager.
- *
- * @param format the format of the track that will include at least
- * the MIME type {@link MediaFormat@KEY_MIME}.
- * @return the created {@link SubtitleTrack} object
- */
- public SubtitleTrack addTrack(MediaFormat format) {
- synchronized(mRenderers) {
- for (Renderer renderer: mRenderers) {
- if (renderer.supports(format)) {
- SubtitleTrack track = renderer.createTrack(format);
- if (track != null) {
- synchronized(mTracks) {
- if (mTracks.size() == 0) {
- mCaptioningManager.addCaptioningChangeListener(
- mCaptioningChangeListener);
- }
- mTracks.add(track);
- }
- return track;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Show the selected (or default) subtitle track.
- *
- * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
- */
- public void show() {
- processOnAnchor(mHandler.obtainMessage(WHAT_SHOW));
- }
-
- private void doShow() {
- mShowing = true;
- mVisibilityIsExplicit = true;
- if (mSelectedTrack != null) {
- mSelectedTrack.show();
- }
- }
-
- /**
- * Hide the selected (or default) subtitle track.
- *
- * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
- */
- public void hide() {
- processOnAnchor(mHandler.obtainMessage(WHAT_HIDE));
- }
-
- private void doHide() {
- mVisibilityIsExplicit = true;
- if (mSelectedTrack != null) {
- mSelectedTrack.hide();
- }
- mShowing = false;
- }
-
- /**
- * Interface for supporting a single or multiple subtitle types in {@link
- * MediaPlayer}.
- */
- public abstract static class Renderer {
- /**
- * Called by {@link MediaPlayer}'s {@link SubtitleController} when a new
- * subtitle track is detected, to see if it should use this object to
- * parse and display this subtitle track.
- *
- * @param format the format of the track that will include at least
- * the MIME type {@link MediaFormat@KEY_MIME}.
- *
- * @return true if and only if the track format is supported by this
- * renderer
- */
- public abstract boolean supports(MediaFormat format);
-
- /**
- * Called by {@link MediaPlayer}'s {@link SubtitleController} for each
- * subtitle track that was detected and is supported by this object to
- * create a {@link SubtitleTrack} object. This object will be created
- * for each track that was found. If the track is selected for display,
- * this object will be used to parse and display the track data.
- *
- * @param format the format of the track that will include at least
- * the MIME type {@link MediaFormat@KEY_MIME}.
- * @return a {@link SubtitleTrack} object that will be used to parse
- * and render the subtitle track.
- */
- public abstract SubtitleTrack createTrack(MediaFormat format);
- }
-
- /**
- * Add support for a subtitle format in {@link MediaPlayer}.
- *
- * @param renderer a {@link SubtitleController.Renderer} object that adds
- * support for a subtitle format.
- */
- public void registerRenderer(Renderer renderer) {
- synchronized(mRenderers) {
- // TODO how to get available renderers in the system
- if (!mRenderers.contains(renderer)) {
- // TODO should added renderers override existing ones (to allow replacing?)
- mRenderers.add(renderer);
- }
- }
- }
-
- public boolean hasRendererFor(MediaFormat format) {
- synchronized(mRenderers) {
- // TODO how to get available renderers in the system
- for (Renderer renderer: mRenderers) {
- if (renderer.supports(format)) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Subtitle anchor, an object that is able to display a subtitle renderer,
- * e.g. a VideoView.
- */
- public interface Anchor {
- /**
- * Anchor should use the supplied subtitle rendering widget, or
- * none if it is null.
- */
- public void setSubtitleWidget(RenderingWidget subtitleWidget);
-
- /**
- * Anchors provide the looper on which all track visibility changes
- * (track.show/hide, setSubtitleWidget) will take place.
- */
- public Looper getSubtitleLooper();
- }
-
- private Anchor mAnchor;
-
- /**
- * called from anchor's looper (if any, both when unsetting and
- * setting)
- */
- public void setAnchor(Anchor anchor) {
- if (mAnchor == anchor) {
- return;
- }
-
- if (mAnchor != null) {
- checkAnchorLooper();
- mAnchor.setSubtitleWidget(null);
- }
- mAnchor = anchor;
- mHandler = null;
- if (mAnchor != null) {
- mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback);
- checkAnchorLooper();
- mAnchor.setSubtitleWidget(getRenderingWidget());
- }
- }
-
- private void checkAnchorLooper() {
- assert mHandler != null : "Should have a looper already";
- assert Looper.myLooper() == mHandler.getLooper()
- : "Must be called from the anchor's looper";
- }
-
- private void processOnAnchor(Message m) {
- assert mHandler != null : "Should have a looper already";
- if (Looper.myLooper() == mHandler.getLooper()) {
- mHandler.dispatchMessage(m);
- } else {
- mHandler.sendMessage(m);
- }
- }
-
- public interface Listener {
- /**
- * Called when a subtitle track has been selected.
- *
- * @param track selected subtitle track or null
- */
- public void onSubtitleTrackSelected(SubtitleTrack track);
- }
-
- private Listener mListener;
-}
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java
deleted file mode 100644
index 6b9064a..0000000
--- a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java
+++ /dev/null
@@ -1,696 +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 com.android.media.subtitle;
-
-import android.graphics.Canvas;
-import android.media.MediaFormat;
-import android.media.MediaPlayer2.TrackInfo;
-import android.media.SubtitleData;
-import android.os.Handler;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.Pair;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.Vector;
-
-// Note: This is forked from android.media.SubtitleTrack since P
-/**
- * A subtitle track abstract base class that is responsible for parsing and displaying
- * an instance of a particular type of subtitle.
- */
-public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeListener {
- private static final String TAG = "SubtitleTrack";
- private long mLastUpdateTimeMs;
- private long mLastTimeMs;
-
- private Runnable mRunnable;
-
- final private LongSparseArray<Run> mRunsByEndTime = new LongSparseArray<Run>();
- final private LongSparseArray<Run> mRunsByID = new LongSparseArray<Run>();
-
- private CueList mCues;
- final private Vector<Cue> mActiveCues = new Vector<Cue>();
- protected boolean mVisible;
-
- public boolean DEBUG = false;
-
- protected Handler mHandler = new Handler();
-
- private MediaFormat mFormat;
-
- public SubtitleTrack(MediaFormat format) {
- mFormat = format;
- mCues = new CueList();
- clearActiveCues();
- mLastTimeMs = -1;
- }
-
- public final MediaFormat getFormat() {
- return mFormat;
- }
-
- private long mNextScheduledTimeMs = -1;
-
- public void onData(SubtitleData data) {
- long runID = data.getStartTimeUs() + 1;
- onData(data.getData(), true /* eos */, runID);
- setRunDiscardTimeMs(
- runID,
- (data.getStartTimeUs() + data.getDurationUs()) / 1000);
- }
-
- /**
- * Called when there is input data for the subtitle track. The
- * complete subtitle for a track can include multiple whole units
- * (runs). Each of these units can have multiple sections. The
- * contents of a run are submitted in sequential order, with eos
- * indicating the last section of the run. Calls from different
- * runs must not be intermixed.
- *
- * @param data subtitle data byte buffer
- * @param eos true if this is the last section of the run.
- * @param runID mostly-unique ID for this run of data. Subtitle cues
- * with runID of 0 are discarded immediately after
- * display. Cues with runID of ~0 are discarded
- * only at the deletion of the track object. Cues
- * with other runID-s are discarded at the end of the
- * run, which defaults to the latest timestamp of
- * any of its cues (with this runID).
- */
- protected abstract void onData(byte[] data, boolean eos, long runID);
-
- /**
- * Called when adding the subtitle rendering widget to the view hierarchy,
- * as well as when showing or hiding the subtitle track, or when the video
- * surface position has changed.
- *
- * @return the widget that renders this subtitle track. For most renderers
- * there should be a single shared instance that is used for all
- * tracks supported by that renderer, as at most one subtitle track
- * is visible at one time.
- */
- public abstract RenderingWidget getRenderingWidget();
-
- /**
- * Called when the active cues have changed, and the contents of the subtitle
- * view should be updated.
- */
- public abstract void updateView(Vector<Cue> activeCues);
-
- protected synchronized void updateActiveCues(boolean rebuild, long timeMs) {
- // out-of-order times mean seeking or new active cues being added
- // (during their own timespan)
- if (rebuild || mLastUpdateTimeMs > timeMs) {
- clearActiveCues();
- }
-
- for(Iterator<Pair<Long, Cue> > it =
- mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) {
- Pair<Long, Cue> event = it.next();
- Cue cue = event.second;
-
- if (cue.mEndTimeMs == event.first) {
- // remove past cues
- if (DEBUG) Log.v(TAG, "Removing " + cue);
- mActiveCues.remove(cue);
- if (cue.mRunID == 0) {
- it.remove();
- }
- } else if (cue.mStartTimeMs == event.first) {
- // add new cues
- // TRICKY: this will happen in start order
- if (DEBUG) Log.v(TAG, "Adding " + cue);
- if (cue.mInnerTimesMs != null) {
- cue.onTime(timeMs);
- }
- mActiveCues.add(cue);
- } else if (cue.mInnerTimesMs != null) {
- // cue is modified
- cue.onTime(timeMs);
- }
- }
-
- /* complete any runs */
- while (mRunsByEndTime.size() > 0 &&
- mRunsByEndTime.keyAt(0) <= timeMs) {
- removeRunsByEndTimeIndex(0); // removes element
- }
- mLastUpdateTimeMs = timeMs;
- }
-
- private void removeRunsByEndTimeIndex(int ix) {
- Run run = mRunsByEndTime.valueAt(ix);
- while (run != null) {
- Cue cue = run.mFirstCue;
- while (cue != null) {
- mCues.remove(cue);
- Cue nextCue = cue.mNextInRun;
- cue.mNextInRun = null;
- cue = nextCue;
- }
- mRunsByID.remove(run.mRunID);
- Run nextRun = run.mNextRunAtEndTimeMs;
- run.mPrevRunAtEndTimeMs = null;
- run.mNextRunAtEndTimeMs = null;
- run = nextRun;
- }
- mRunsByEndTime.removeAt(ix);
- }
-
- @Override
- protected void finalize() throws Throwable {
- /* remove all cues (untangle all cross-links) */
- int size = mRunsByEndTime.size();
- for(int ix = size - 1; ix >= 0; ix--) {
- removeRunsByEndTimeIndex(ix);
- }
-
- super.finalize();
- }
-
- private synchronized void takeTime(long timeMs) {
- mLastTimeMs = timeMs;
- }
-
- protected synchronized void clearActiveCues() {
- if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues");
- mActiveCues.clear();
- mLastUpdateTimeMs = -1;
- }
-
- protected void scheduleTimedEvents() {
- /* get times for the next event */
- if (mTimeProvider != null) {
- mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs);
- if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs);
- mTimeProvider.notifyAt(
- mNextScheduledTimeMs >= 0 ?
- (mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME,
- this);
- }
- }
-
- @Override
- public void onTimedEvent(long timeUs) {
- if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs);
- synchronized (this) {
- long timeMs = timeUs / 1000;
- updateActiveCues(false, timeMs);
- takeTime(timeMs);
- }
- updateView(mActiveCues);
- scheduleTimedEvents();
- }
-
- @Override
- public void onSeek(long timeUs) {
- if (DEBUG) Log.d(TAG, "onSeek " + timeUs);
- synchronized (this) {
- long timeMs = timeUs / 1000;
- updateActiveCues(true, timeMs);
- takeTime(timeMs);
- }
- updateView(mActiveCues);
- scheduleTimedEvents();
- }
-
- @Override
- public void onStop() {
- synchronized (this) {
- if (DEBUG) Log.d(TAG, "onStop");
- clearActiveCues();
- mLastTimeMs = -1;
- }
- updateView(mActiveCues);
- mNextScheduledTimeMs = -1;
- mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this);
- }
-
- protected MediaTimeProvider mTimeProvider;
-
- public void show() {
- if (mVisible) {
- return;
- }
-
- mVisible = true;
- RenderingWidget renderingWidget = getRenderingWidget();
- if (renderingWidget != null) {
- renderingWidget.setVisible(true);
- }
- if (mTimeProvider != null) {
- mTimeProvider.scheduleUpdate(this);
- }
- }
-
- public void hide() {
- if (!mVisible) {
- return;
- }
-
- if (mTimeProvider != null) {
- mTimeProvider.cancelNotifications(this);
- }
- RenderingWidget renderingWidget = getRenderingWidget();
- if (renderingWidget != null) {
- renderingWidget.setVisible(false);
- }
- mVisible = false;
- }
-
- protected synchronized boolean addCue(Cue cue) {
- mCues.add(cue);
-
- if (cue.mRunID != 0) {
- Run run = mRunsByID.get(cue.mRunID);
- if (run == null) {
- run = new Run();
- mRunsByID.put(cue.mRunID, run);
- run.mEndTimeMs = cue.mEndTimeMs;
- } else if (run.mEndTimeMs < cue.mEndTimeMs) {
- run.mEndTimeMs = cue.mEndTimeMs;
- }
-
- // link-up cues in the same run
- cue.mNextInRun = run.mFirstCue;
- run.mFirstCue = cue;
- }
-
- // if a cue is added that should be visible, need to refresh view
- long nowMs = -1;
- if (mTimeProvider != null) {
- try {
- nowMs = mTimeProvider.getCurrentTimeUs(
- false /* precise */, true /* monotonic */) / 1000;
- } catch (IllegalStateException e) {
- // handle as it we are not playing
- }
- }
-
- if (DEBUG) Log.v(TAG, "mVisible=" + mVisible + ", " +
- cue.mStartTimeMs + " <= " + nowMs + ", " +
- cue.mEndTimeMs + " >= " + mLastTimeMs);
-
- if (mVisible &&
- cue.mStartTimeMs <= nowMs &&
- // we don't trust nowMs, so check any cue since last callback
- cue.mEndTimeMs >= mLastTimeMs) {
- if (mRunnable != null) {
- mHandler.removeCallbacks(mRunnable);
- }
- final SubtitleTrack track = this;
- final long thenMs = nowMs;
- mRunnable = new Runnable() {
- @Override
- public void run() {
- // even with synchronized, it is possible that we are going
- // to do multiple updates as the runnable could be already
- // running.
- synchronized (track) {
- mRunnable = null;
- updateActiveCues(true, thenMs);
- updateView(mActiveCues);
- }
- }
- };
- // delay update so we don't update view on every cue. TODO why 10?
- if (mHandler.postDelayed(mRunnable, 10 /* delay */)) {
- if (DEBUG) Log.v(TAG, "scheduling update");
- } else {
- if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update");
- }
- return true;
- }
-
- if (mVisible &&
- cue.mEndTimeMs >= mLastTimeMs &&
- (cue.mStartTimeMs < mNextScheduledTimeMs ||
- mNextScheduledTimeMs < 0)) {
- scheduleTimedEvents();
- }
-
- return false;
- }
-
- public synchronized void setTimeProvider(MediaTimeProvider timeProvider) {
- if (mTimeProvider == timeProvider) {
- return;
- }
- if (mTimeProvider != null) {
- mTimeProvider.cancelNotifications(this);
- }
- mTimeProvider = timeProvider;
- if (mTimeProvider != null) {
- mTimeProvider.scheduleUpdate(this);
- }
- }
-
-
- static class CueList {
- private static final String TAG = "CueList";
- // simplistic, inefficient implementation
- private SortedMap<Long, Vector<Cue> > mCues;
- public boolean DEBUG = false;
-
- private boolean addEvent(Cue cue, long timeMs) {
- Vector<Cue> cues = mCues.get(timeMs);
- if (cues == null) {
- cues = new Vector<Cue>(2);
- mCues.put(timeMs, cues);
- } else if (cues.contains(cue)) {
- // do not duplicate cues
- return false;
- }
-
- cues.add(cue);
- return true;
- }
-
- private void removeEvent(Cue cue, long timeMs) {
- Vector<Cue> cues = mCues.get(timeMs);
- if (cues != null) {
- cues.remove(cue);
- if (cues.size() == 0) {
- mCues.remove(timeMs);
- }
- }
- }
-
- public void add(Cue cue) {
- // ignore non-positive-duration cues
- if (cue.mStartTimeMs >= cue.mEndTimeMs)
- return;
-
- if (!addEvent(cue, cue.mStartTimeMs)) {
- return;
- }
-
- long lastTimeMs = cue.mStartTimeMs;
- if (cue.mInnerTimesMs != null) {
- for (long timeMs: cue.mInnerTimesMs) {
- if (timeMs > lastTimeMs && timeMs < cue.mEndTimeMs) {
- addEvent(cue, timeMs);
- lastTimeMs = timeMs;
- }
- }
- }
-
- addEvent(cue, cue.mEndTimeMs);
- }
-
- public void remove(Cue cue) {
- removeEvent(cue, cue.mStartTimeMs);
- if (cue.mInnerTimesMs != null) {
- for (long timeMs: cue.mInnerTimesMs) {
- removeEvent(cue, timeMs);
- }
- }
- removeEvent(cue, cue.mEndTimeMs);
- }
-
- public Iterable<Pair<Long, Cue>> entriesBetween(
- final long lastTimeMs, final long timeMs) {
- return new Iterable<Pair<Long, Cue> >() {
- @Override
- public Iterator<Pair<Long, Cue> > iterator() {
- if (DEBUG) Log.d(TAG, "slice (" + lastTimeMs + ", " + timeMs + "]=");
- try {
- return new EntryIterator(
- mCues.subMap(lastTimeMs + 1, timeMs + 1));
- } catch(IllegalArgumentException e) {
- return new EntryIterator(null);
- }
- }
- };
- }
-
- public long nextTimeAfter(long timeMs) {
- SortedMap<Long, Vector<Cue>> tail = null;
- try {
- tail = mCues.tailMap(timeMs + 1);
- if (tail != null) {
- return tail.firstKey();
- } else {
- return -1;
- }
- } catch(IllegalArgumentException e) {
- return -1;
- } catch(NoSuchElementException e) {
- return -1;
- }
- }
-
- class EntryIterator implements Iterator<Pair<Long, Cue> > {
- @Override
- public boolean hasNext() {
- return !mDone;
- }
-
- @Override
- public Pair<Long, Cue> next() {
- if (mDone) {
- throw new NoSuchElementException("");
- }
- mLastEntry = new Pair<Long, Cue>(
- mCurrentTimeMs, mListIterator.next());
- mLastListIterator = mListIterator;
- if (!mListIterator.hasNext()) {
- nextKey();
- }
- return mLastEntry;
- }
-
- @Override
- public void remove() {
- // only allow removing end tags
- if (mLastListIterator == null ||
- mLastEntry.second.mEndTimeMs != mLastEntry.first) {
- throw new IllegalStateException("");
- }
-
- // remove end-cue
- mLastListIterator.remove();
- mLastListIterator = null;
- if (mCues.get(mLastEntry.first).size() == 0) {
- mCues.remove(mLastEntry.first);
- }
-
- // remove rest of the cues
- Cue cue = mLastEntry.second;
- removeEvent(cue, cue.mStartTimeMs);
- if (cue.mInnerTimesMs != null) {
- for (long timeMs: cue.mInnerTimesMs) {
- removeEvent(cue, timeMs);
- }
- }
- }
-
- public EntryIterator(SortedMap<Long, Vector<Cue> > cues) {
- if (DEBUG) Log.v(TAG, cues + "");
- mRemainingCues = cues;
- mLastListIterator = null;
- nextKey();
- }
-
- private void nextKey() {
- do {
- try {
- if (mRemainingCues == null) {
- throw new NoSuchElementException("");
- }
- mCurrentTimeMs = mRemainingCues.firstKey();
- mListIterator =
- mRemainingCues.get(mCurrentTimeMs).iterator();
- try {
- mRemainingCues =
- mRemainingCues.tailMap(mCurrentTimeMs + 1);
- } catch (IllegalArgumentException e) {
- mRemainingCues = null;
- }
- mDone = false;
- } catch (NoSuchElementException e) {
- mDone = true;
- mRemainingCues = null;
- mListIterator = null;
- return;
- }
- } while (!mListIterator.hasNext());
- }
-
- private long mCurrentTimeMs;
- private Iterator<Cue> mListIterator;
- private boolean mDone;
- private SortedMap<Long, Vector<Cue> > mRemainingCues;
- private Iterator<Cue> mLastListIterator;
- private Pair<Long,Cue> mLastEntry;
- }
-
- CueList() {
- mCues = new TreeMap<Long, Vector<Cue>>();
- }
- }
-
- public static class Cue {
- public long mStartTimeMs;
- public long mEndTimeMs;
- public long[] mInnerTimesMs;
- public long mRunID;
-
- public Cue mNextInRun;
-
- public void onTime(long timeMs) { }
- }
-
- /** update mRunsByEndTime (with default end time) */
- protected void finishedRun(long runID) {
- if (runID != 0 && runID != ~0) {
- Run run = mRunsByID.get(runID);
- if (run != null) {
- run.storeByEndTimeMs(mRunsByEndTime);
- }
- }
- }
-
- /** update mRunsByEndTime with given end time */
- public void setRunDiscardTimeMs(long runID, long timeMs) {
- if (runID != 0 && runID != ~0) {
- Run run = mRunsByID.get(runID);
- if (run != null) {
- run.mEndTimeMs = timeMs;
- run.storeByEndTimeMs(mRunsByEndTime);
- }
- }
- }
-
- /** whether this is a text track who fires events instead getting rendered */
- public int getTrackType() {
- return getRenderingWidget() == null
- ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
- : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
- }
-
-
- private static class Run {
- public Cue mFirstCue;
- public Run mNextRunAtEndTimeMs;
- public Run mPrevRunAtEndTimeMs;
- public long mEndTimeMs = -1;
- public long mRunID = 0;
- private long mStoredEndTimeMs = -1;
-
- public void storeByEndTimeMs(LongSparseArray<Run> runsByEndTime) {
- // remove old value if any
- int ix = runsByEndTime.indexOfKey(mStoredEndTimeMs);
- if (ix >= 0) {
- if (mPrevRunAtEndTimeMs == null) {
- assert(this == runsByEndTime.valueAt(ix));
- if (mNextRunAtEndTimeMs == null) {
- runsByEndTime.removeAt(ix);
- } else {
- runsByEndTime.setValueAt(ix, mNextRunAtEndTimeMs);
- }
- }
- removeAtEndTimeMs();
- }
-
- // add new value
- if (mEndTimeMs >= 0) {
- mPrevRunAtEndTimeMs = null;
- mNextRunAtEndTimeMs = runsByEndTime.get(mEndTimeMs);
- if (mNextRunAtEndTimeMs != null) {
- mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = this;
- }
- runsByEndTime.put(mEndTimeMs, this);
- mStoredEndTimeMs = mEndTimeMs;
- }
- }
-
- public void removeAtEndTimeMs() {
- Run prev = mPrevRunAtEndTimeMs;
-
- if (mPrevRunAtEndTimeMs != null) {
- mPrevRunAtEndTimeMs.mNextRunAtEndTimeMs = mNextRunAtEndTimeMs;
- mPrevRunAtEndTimeMs = null;
- }
- if (mNextRunAtEndTimeMs != null) {
- mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = prev;
- mNextRunAtEndTimeMs = null;
- }
- }
- }
-
- /**
- * Interface for rendering subtitles onto a Canvas.
- */
- public interface RenderingWidget {
- /**
- * Sets the widget's callback, which is used to send updates when the
- * rendered data has changed.
- *
- * @param callback update callback
- */
- public void setOnChangedListener(OnChangedListener callback);
-
- /**
- * Sets the widget's size.
- *
- * @param width width in pixels
- * @param height height in pixels
- */
- public void setSize(int width, int height);
-
- /**
- * Sets whether the widget should draw subtitles.
- *
- * @param visible true if subtitles should be drawn, false otherwise
- */
- public void setVisible(boolean visible);
-
- /**
- * Renders subtitles onto a {@link Canvas}.
- *
- * @param c canvas on which to render subtitles
- */
- public void draw(Canvas c);
-
- /**
- * Called when the widget is attached to a window.
- */
- public void onAttachedToWindow();
-
- /**
- * Called when the widget is detached from a window.
- */
- public void onDetachedFromWindow();
-
- /**
- * Callback used to send updates about changes to rendering data.
- */
- public interface OnChangedListener {
- /**
- * Called when the rendering data has changed.
- *
- * @param renderingWidget the widget whose data has changed
- */
- public void onChanged(RenderingWidget renderingWidget);
- }
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
deleted file mode 100644
index 663b813..0000000
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media.update;
-
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.media.MediaController2;
-import android.media.MediaController2.ControllerCallback;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlaylistAgent;
-import android.media.MediaSession2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.SessionCallback;
-import android.media.Rating2;
-import android.media.SessionCommand2;
-import android.media.SessionCommandGroup2;
-import android.media.SessionToken2;
-import android.media.VolumeProvider2;
-import android.media.update.MediaController2Provider;
-import android.media.update.MediaItem2Provider;
-import android.media.update.MediaMetadata2Provider;
-import android.media.update.MediaPlaylistAgentProvider;
-import android.media.update.MediaSession2Provider;
-import android.media.update.MediaSession2Provider.BuilderBaseProvider;
-import android.media.update.MediaSession2Provider.CommandButtonProvider.BuilderProvider;
-import android.media.update.SessionToken2Provider;
-import android.media.update.StaticProvider;
-import android.media.update.VolumeProvider2Provider;
-import android.os.Bundle;
-import android.os.IInterface;
-
-import com.android.media.IMediaController2;
-import com.android.media.MediaController2Impl;
-import com.android.media.MediaItem2Impl;
-import com.android.media.MediaMetadata2Impl;
-import com.android.media.MediaPlaylistAgentImpl;
-import com.android.media.MediaSession2Impl;
-import com.android.media.Rating2Impl;
-import com.android.media.SessionToken2Impl;
-import com.android.media.VolumeProvider2Impl;
-
-import java.util.concurrent.Executor;
-
-public final class ApiFactory implements StaticProvider {
- private ApiFactory() { }
-
- public static StaticProvider initialize(ApplicationInfo updatableInfo) {
- ApiHelper.initialize(updatableInfo);
- return new ApiFactory();
- }
-
- @Override
- public MediaController2Provider createMediaController2(
- Context context, MediaController2 instance, SessionToken2 token,
- Executor executor, ControllerCallback callback) {
- return new MediaController2Impl(context, instance, token, executor, callback);
- }
-
- @Override
- public MediaSession2Provider.CommandProvider createMediaSession2Command(
- SessionCommand2 instance, int commandCode, String action, Bundle extra) {
- if (action == null && extra == null) {
- return new MediaSession2Impl.CommandImpl(instance, commandCode);
- }
- return new MediaSession2Impl.CommandImpl(instance, action, extra);
- }
-
- @Override
- public SessionCommand2 fromBundle_MediaSession2Command(Bundle command) {
- return MediaSession2Impl.CommandImpl.fromBundle_impl(command);
- }
-
- @Override
- public MediaSession2Provider.CommandGroupProvider createMediaSession2CommandGroup(
- SessionCommandGroup2 instance, SessionCommandGroup2 other) {
- return new MediaSession2Impl.CommandGroupImpl(instance,
- (other == null) ? null : other.getProvider());
- }
-
- @Override
- public SessionCommandGroup2 fromBundle_MediaSession2CommandGroup(Bundle commands) {
- return MediaSession2Impl.CommandGroupImpl.fromBundle_impl(commands);
- }
-
- @Override
- public MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfo(
- Context context, ControllerInfo instance, int uid, int pid, String packageName,
- IInterface callback) {
- return new MediaSession2Impl.ControllerInfoImpl(context,
- instance, uid, pid, packageName, (IMediaController2) callback);
- }
-
- @Override
- public BuilderProvider createMediaSession2CommandButtonBuilder(
- MediaSession2.CommandButton.Builder instance) {
- return new MediaSession2Impl.CommandButtonImpl.BuilderImpl(instance);
- }
-
- public BuilderBaseProvider<MediaSession2, SessionCallback> createMediaSession2Builder(
- Context context, MediaSession2.Builder instance) {
- return new MediaSession2Impl.BuilderImpl(context, instance);
- }
-
- @Override
- public SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance,
- String packageName, String serviceName, int uid) {
- return new SessionToken2Impl(context, instance, packageName, serviceName, uid);
- }
-
- @Override
- public SessionToken2 fromBundle_SessionToken2(Bundle bundle) {
- return SessionToken2Impl.fromBundle_impl(bundle);
- }
-
- @Override
- public MediaItem2Provider.BuilderProvider createMediaItem2Builder(MediaItem2.Builder instance,
- int flags) {
- return new MediaItem2Impl.BuilderImpl(instance, flags);
- }
-
- @Override
- public MediaItem2 fromBundle_MediaItem2(Bundle bundle) {
- return MediaItem2Impl.fromBundle_impl(bundle);
- }
-
- @Override
- public VolumeProvider2Provider createVolumeProvider2(VolumeProvider2 instance, int controlType,
- int maxVolume, int currentVolume) {
- return new VolumeProvider2Impl(instance, controlType, maxVolume, currentVolume);
- }
-
- @Override
- public MediaMetadata2 fromBundle_MediaMetadata2(Bundle bundle) {
- return MediaMetadata2Impl.fromBundle_impl(bundle);
- }
-
- @Override
- public MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder(
- MediaMetadata2.Builder instance) {
- return new MediaMetadata2Impl.BuilderImpl(instance);
- }
-
- @Override
- public MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder(
- MediaMetadata2.Builder instance, MediaMetadata2 source) {
- return new MediaMetadata2Impl.BuilderImpl(instance, source);
- }
-
- @Override
- public Rating2 fromBundle_Rating2(Bundle bundle) {
- return Rating2Impl.fromBundle_impl(bundle);
- }
-
- @Override
- public Rating2 newUnratedRating_Rating2(int ratingStyle) {
- return Rating2Impl.newUnratedRating_impl(ratingStyle);
- }
-
- @Override
- public Rating2 newHeartRating_Rating2(boolean hasHeart) {
- return Rating2Impl.newHeartRating_impl(hasHeart);
- }
-
- @Override
- public Rating2 newThumbRating_Rating2(boolean thumbIsUp) {
- return Rating2Impl.newThumbRating_impl(thumbIsUp);
- }
-
- @Override
- public Rating2 newStarRating_Rating2(int starRatingStyle, float starRating) {
- return Rating2Impl.newStarRating_impl(starRatingStyle, starRating);
- }
-
- @Override
- public Rating2 newPercentageRating_Rating2(float percent) {
- return Rating2Impl.newPercentageRating_impl(percent);
- }
-
- @Override
- public MediaPlaylistAgentProvider createMediaPlaylistAgent(MediaPlaylistAgent instance) {
- return new MediaPlaylistAgentImpl(instance);
- }
-}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
deleted file mode 100644
index f918ed3..0000000
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ /dev/null
@@ -1,33 +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 com.android.media.update;
-
-import android.content.pm.ApplicationInfo;
-
-public final class ApiHelper {
- private static ApplicationInfo sUpdatableInfo;
-
- private ApiHelper() { }
-
- static void initialize(ApplicationInfo updatableInfo) {
- if (sUpdatableInfo != null) {
- throw new IllegalStateException("initialize should only be called once");
- }
-
- sUpdatableInfo = updatableInfo;
- }
-}
diff --git a/packages/MediaComponents/src/com/android/widget/SubtitleView.java b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
deleted file mode 100644
index db0ae33..0000000
--- a/packages/MediaComponents/src/com/android/widget/SubtitleView.java
+++ /dev/null
@@ -1,144 +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 com.android.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-import androidx.annotation.Nullable;
-
-import com.android.media.subtitle.SubtitleController.Anchor;
-import com.android.media.subtitle.SubtitleTrack.RenderingWidget;
-
-class SubtitleView extends FrameLayout implements Anchor {
- private static final String TAG = "SubtitleView";
-
- private RenderingWidget mSubtitleWidget;
- private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
-
- public SubtitleView(Context context) {
- this(context, null);
- }
-
- public SubtitleView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SubtitleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public SubtitleView(
- Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- public void setSubtitleWidget(RenderingWidget subtitleWidget) {
- if (mSubtitleWidget == subtitleWidget) {
- return;
- }
-
- final boolean attachedToWindow = isAttachedToWindow();
- if (mSubtitleWidget != null) {
- if (attachedToWindow) {
- mSubtitleWidget.onDetachedFromWindow();
- }
-
- mSubtitleWidget.setOnChangedListener(null);
- }
- mSubtitleWidget = subtitleWidget;
-
- if (subtitleWidget != null) {
- if (mSubtitlesChangedListener == null) {
- mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
- @Override
- public void onChanged(RenderingWidget renderingWidget) {
- invalidate();
- }
- };
- }
-
- setWillNotDraw(false);
- subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
-
- if (attachedToWindow) {
- subtitleWidget.onAttachedToWindow();
- requestLayout();
- }
- } else {
- setWillNotDraw(true);
- }
-
- invalidate();
- }
-
- @Override
- public Looper getSubtitleLooper() {
- return Looper.getMainLooper();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (mSubtitleWidget != null) {
- mSubtitleWidget.onAttachedToWindow();
- }
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (mSubtitleWidget != null) {
- mSubtitleWidget.onDetachedFromWindow();
- }
- }
-
- @Override
- public void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (mSubtitleWidget != null) {
- final int width = getWidth() - getPaddingLeft() - getPaddingRight();
- final int height = getHeight() - getPaddingTop() - getPaddingBottom();
-
- mSubtitleWidget.setSize(width, height);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- if (mSubtitleWidget != null) {
- final int saveCount = canvas.save();
- canvas.translate(getPaddingLeft(), getPaddingTop());
- mSubtitleWidget.draw(canvas);
- canvas.restoreToCount(saveCount);
- }
- }
-
- @Override
- public CharSequence getAccessibilityClassName() {
- return SubtitleView.class.getName();
- }
-}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java b/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
deleted file mode 100644
index 854d47e..0000000
--- a/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
+++ /dev/null
@@ -1,65 +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 com.android.widget;
-
-import android.annotation.NonNull;
-import android.media.MediaPlayer2;
-import android.view.View;
-
-interface VideoViewInterface {
- /**
- * Assigns the view's surface to the given MediaPlayer2 instance.
- *
- * @param mp MediaPlayer2
- * @return true if the surface is successfully assigned, false if not. It will fail to assign
- * if any of MediaPlayer2 or surface is unavailable.
- */
- boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp);
- void setSurfaceListener(SurfaceListener l);
- int getViewType();
- void setMediaPlayer(MediaPlayer2 mp);
-
- /**
- * Takes over oldView. It means that the MediaPlayer2 will start rendering on this view.
- * The visibility of oldView will be set as {@link View.GONE}. If the view doesn't have a
- * MediaPlayer2 instance or its surface is not available, the actual execution is deferred until
- * a MediaPlayer2 instance is set by {@link #setMediaPlayer} or its surface becomes available.
- * {@link SurfaceListener.onSurfaceTakeOverDone} will be called when the actual execution is
- * done.
- *
- * @param oldView The view that MediaPlayer2 is currently rendering on.
- */
- void takeOver(@NonNull VideoViewInterface oldView);
-
- /**
- * Indicates if the view's surface is available.
- *
- * @return true if the surface is available.
- */
- boolean hasAvailableSurface();
-
- /**
- * An instance of VideoViewInterface calls these surface notification methods accordingly if
- * a listener has been registered via {@link #setSurfaceListener(SurfaceListener)}.
- */
- interface SurfaceListener {
- void onSurfaceCreated(View view, int width, int height);
- void onSurfaceDestroyed(View view);
- void onSurfaceChanged(View view, int width, int height);
- void onSurfaceTakeOverDone(VideoViewInterface view);
- }
-}
diff --git a/packages/MediaComponents/tests/Android.mk b/packages/MediaComponents/tests/Android.mk
deleted file mode 100644
index dddfd2a..0000000
--- a/packages/MediaComponents/tests/Android.mk
+++ /dev/null
@@ -1,36 +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.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android.test.runner.stubs \
- android.test.base.stubs \
- mockito-target-minus-junit4 \
- junit
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := MediaComponentsTest
-
-LOCAL_INSTRUMENTATION_FOR := MediaComponents
-
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-
-include $(BUILD_PACKAGE)
diff --git a/packages/MediaComponents/tests/AndroidManifest.xml b/packages/MediaComponents/tests/AndroidManifest.xml
deleted file mode 100644
index 7255265..0000000
--- a/packages/MediaComponents/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.media.tests">
-
- <application android:label="Media API Test">
- <uses-library android:name="android.test.runner" />
- </application>
-
- <!--
- To run the tests use the command:
- "adb shell am instrument -w com.android.media.tests/android.test.InstrumentationTestRunner"
- -->
- <instrumentation
- android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.media.update"
- android:label="Media API test" />
-
-</manifest>
diff --git a/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java b/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java
deleted file mode 100644
index 0807600..0000000
--- a/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java
+++ /dev/null
@@ -1,643 +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 com.android.media;
-
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.DataSourceDesc;
-import android.media.UriDataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
-import android.media.MediaPlayerBase.PlayerEventCallback;
-import android.media.MediaPlaylistAgent;
-import android.media.MediaSession2;
-import android.media.MediaSession2.OnDataSourceMissingHelper;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.test.AndroidTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Matchers;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Tests {@link SessionPlaylistAgent}.
- */
-public class SessionPlaylistAgentTest extends AndroidTestCase {
- private static final String TAG = "SessionPlaylistAgentTest";
- private static final int WAIT_TIME_MS = 1000;
- private static final int INVALID_REPEAT_MODE = -100;
- private static final int INVALID_SHUFFLE_MODE = -100;
-
- private Handler mHandler;
- private Executor mHandlerExecutor;
-
- private Object mWaitLock = new Object();
- private Context mContext;
- private MediaSession2Impl mSessionImpl;
- private MediaPlayerBase mPlayer;
- private PlayerEventCallback mPlayerEventCallback;
- private SessionPlaylistAgent mAgent;
- private OnDataSourceMissingHelper mDataSourceHelper;
- private MyPlaylistEventCallback mEventCallback;
-
- public class MyPlaylistEventCallback extends MediaPlaylistAgent.PlaylistEventCallback {
- boolean onPlaylistChangedCalled;
- boolean onPlaylistMetadataChangedCalled;
- boolean onRepeatModeChangedCalled;
- boolean onShuffleModeChangedCalled;
-
- private Object mWaitLock;
-
- public MyPlaylistEventCallback(Object waitLock) {
- mWaitLock = waitLock;
- }
-
- public void clear() {
- onPlaylistChangedCalled = false;
- onPlaylistMetadataChangedCalled = false;
- onRepeatModeChangedCalled = false;
- onShuffleModeChangedCalled = false;
- }
-
- public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
- MediaMetadata2 metadata) {
- synchronized (mWaitLock) {
- onPlaylistChangedCalled = true;
- mWaitLock.notify();
- }
- }
-
- public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
- MediaMetadata2 metadata) {
- synchronized (mWaitLock) {
- onPlaylistMetadataChangedCalled = true;
- mWaitLock.notify();
- }
- }
-
- public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
- synchronized (mWaitLock) {
- onRepeatModeChangedCalled = true;
- mWaitLock.notify();
- }
- }
-
- public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
- synchronized (mWaitLock) {
- onShuffleModeChangedCalled = true;
- mWaitLock.notify();
- }
- }
- }
-
- public class MyDataSourceHelper implements OnDataSourceMissingHelper {
- @Override
- public DataSourceDesc onDataSourceMissing(MediaSession2 session, MediaItem2 item) {
- if (item.getMediaId().contains("WITHOUT_DSD")) {
- return null;
- }
- return new UriDataSourceDesc.Builder()
- .setDataSource(getContext(), Uri.parse("dsd://test"))
- .setMediaId(item.getMediaId())
- .build();
- }
- }
-
- public class MockPlayer extends MediaPlayerBase {
- @Override
- public void play() {
- }
-
- @Override
- public void prepare() {
- }
-
- @Override
- public void pause() {
- }
-
- @Override
- public void reset() {
- }
-
- @Override
- public void skipToNext() {
- }
-
- @Override
- public void seekTo(long pos) {
- }
-
- @Override
- public int getPlayerState() {
- return 0;
- }
-
- @Override
- public int getBufferingState() {
- return 0;
- }
-
- @Override
- public void setAudioAttributes(AudioAttributes attributes) {
- }
-
- @Override
- public AudioAttributes getAudioAttributes() {
- return null;
- }
-
- @Override
- public void setDataSource(DataSourceDesc dsd) {
- }
-
- @Override
- public void setNextDataSource(DataSourceDesc dsd) {
- }
-
- @Override
- public void setNextDataSources(List<DataSourceDesc> dsds) {
- }
-
- @Override
- public DataSourceDesc getCurrentDataSource() {
- return null;
- }
-
- @Override
- public void loopCurrent(boolean loop) {
- }
-
- @Override
- public void setPlaybackSpeed(float speed) {
- }
-
- @Override
- public void setPlayerVolume(float volume) {
- }
-
- @Override
- public float getPlayerVolume() {
- return 0;
- }
-
- @Override
- public void registerPlayerEventCallback(Executor e, PlayerEventCallback cb) {
- }
-
- @Override
- public void unregisterPlayerEventCallback(PlayerEventCallback cb) {
- }
-
- @Override
- public void close() throws Exception {
- }
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = getContext();
- // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
- // Dexmaker is used by mockito.
- System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
-
- HandlerThread handlerThread = new HandlerThread("SessionPlaylistAgent");
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
- mHandlerExecutor = (runnable) -> {
- mHandler.post(runnable);
- };
-
- mPlayer = mock(MockPlayer.class);
- doAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- mPlayerEventCallback = (PlayerEventCallback) args[1];
- return null;
- }).when(mPlayer).registerPlayerEventCallback(Matchers.any(), Matchers.any());
-
- mSessionImpl = mock(MediaSession2Impl.class);
- mDataSourceHelper = new MyDataSourceHelper();
- mAgent = new SessionPlaylistAgent(mSessionImpl, mPlayer);
- mAgent.setOnDataSourceMissingHelper(mDataSourceHelper);
- mEventCallback = new MyPlaylistEventCallback(mWaitLock);
- mAgent.registerPlaylistEventCallback(mHandlerExecutor, mEventCallback);
- }
-
- @After
- public void tearDown() throws Exception {
- mHandler.getLooper().quitSafely();
- mHandler = null;
- mHandlerExecutor = null;
- }
-
- @Test
- public void testSetAndGetShuflleMode() throws Exception {
- int shuffleMode = mAgent.getShuffleMode();
- if (shuffleMode != MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_NONE);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onShuffleModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_NONE, mAgent.getShuffleMode());
- }
-
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_ALL);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onShuffleModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_ALL, mAgent.getShuffleMode());
-
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_GROUP);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onShuffleModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
-
- // INVALID_SHUFFLE_MODE will not change the shuffle mode.
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setShuffleMode(INVALID_SHUFFLE_MODE);
- mWaitLock.wait(WAIT_TIME_MS);
- assertFalse(mEventCallback.onShuffleModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
- }
-
- @Test
- public void testSetAndGetRepeatMode() throws Exception {
- int repeatMode = mAgent.getRepeatMode();
- if (repeatMode != MediaPlaylistAgent.REPEAT_MODE_NONE) {
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onRepeatModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.REPEAT_MODE_NONE, mAgent.getRepeatMode());
- }
-
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ONE);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onRepeatModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.REPEAT_MODE_ONE, mAgent.getRepeatMode());
-
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onRepeatModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.REPEAT_MODE_ALL, mAgent.getRepeatMode());
-
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_GROUP);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onRepeatModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
-
- // INVALID_SHUFFLE_MODE will not change the shuffle mode.
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setRepeatMode(INVALID_REPEAT_MODE);
- mWaitLock.wait(WAIT_TIME_MS);
- assertFalse(mEventCallback.onRepeatModeChangedCalled);
- }
- assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
- }
-
- @Test
- public void testSetPlaylist() throws Exception {
- int listSize = 10;
- createAndSetPlaylist(10);
- assertEquals(listSize, mAgent.getPlaylist().size());
- assertEquals(0, mAgent.getCurShuffledIndex());
- }
-
- @Test
- public void testSkipItems() throws Exception {
- int listSize = 5;
- List<MediaItem2> playlist = createAndSetPlaylist(listSize);
-
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
- // Test skipToPlaylistItem
- for (int i = listSize - 1; i >= 0; --i) {
- mAgent.skipToPlaylistItem(playlist.get(i));
- assertEquals(i, mAgent.getCurShuffledIndex());
- }
-
- // Test skipToNextItem
- // curPlayPos = 0
- for (int curPlayPos = 0; curPlayPos < listSize - 1; ++curPlayPos) {
- mAgent.skipToNextItem();
- assertEquals(curPlayPos + 1, mAgent.getCurShuffledIndex());
- }
- mAgent.skipToNextItem();
- assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
-
- // Test skipToPrevious
- // curPlayPos = listSize - 1
- for (int curPlayPos = listSize - 1; curPlayPos > 0; --curPlayPos) {
- mAgent.skipToPreviousItem();
- assertEquals(curPlayPos - 1, mAgent.getCurShuffledIndex());
- }
- mAgent.skipToPreviousItem();
- assertEquals(0, mAgent.getCurShuffledIndex());
-
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
- // Test skipToPrevious with repeat mode all
- // curPlayPos = 0
- mAgent.skipToPreviousItem();
- assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
-
- // Test skipToNext with repeat mode all
- // curPlayPos = listSize - 1
- mAgent.skipToNextItem();
- assertEquals(0, mAgent.getCurShuffledIndex());
-
- mAgent.skipToPreviousItem();
- // curPlayPos = listSize - 1, nextPlayPos = 0
- // Test next play pos after setting repeat mode none.
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
- assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
- }
-
- @Test
- public void testEditPlaylist() throws Exception {
- int listSize = 5;
- List<MediaItem2> playlist = createAndSetPlaylist(listSize);
-
- // Test add item: [0 (cur), 1, 2, 3, 4] -> [0 (cur), 1, 5, 2, 3, 4]
- mEventCallback.clear();
- MediaItem2 item_5 = generateMediaItem(5);
- synchronized (mWaitLock) {
- playlist.add(2, item_5);
- mAgent.addPlaylistItem(2, item_5);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
-
- mEventCallback.clear();
- // Move current: [0 (cur), 1, 5, 2, 3, 4] -> [0, 1, 5 (cur), 2, 3, 4]
- mAgent.skipToPlaylistItem(item_5);
- // Remove current item: [0, 1, 5 (cur), 2, 3, 4] -> [0, 1, 2 (cur), 3, 4]
- synchronized (mWaitLock) {
- playlist.remove(item_5);
- mAgent.removePlaylistItem(item_5);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(2, mAgent.getCurShuffledIndex());
-
- // Remove previous item: [0, 1, 2 (cur), 3, 4] -> [0, 2 (cur), 3, 4]
- mEventCallback.clear();
- MediaItem2 previousItem = playlist.get(1);
- synchronized (mWaitLock) {
- playlist.remove(previousItem);
- mAgent.removePlaylistItem(previousItem);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(1, mAgent.getCurShuffledIndex());
-
- // Remove next item: [0, 2 (cur), 3, 4] -> [0, 2 (cur), 4]
- mEventCallback.clear();
- MediaItem2 nextItem = playlist.get(2);
- synchronized (mWaitLock) {
- playlist.remove(nextItem);
- mAgent.removePlaylistItem(nextItem);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(1, mAgent.getCurShuffledIndex());
-
- // Replace item: [0, 2 (cur), 4] -> [0, 2 (cur), 5]
- mEventCallback.clear();
- synchronized (mWaitLock) {
- playlist.set(2, item_5);
- mAgent.replacePlaylistItem(2, item_5);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(1, mAgent.getCurShuffledIndex());
-
- // Move last and remove the last item: [0, 2 (cur), 5] -> [0, 2, 5 (cur)] -> [0, 2 (cur)]
- MediaItem2 lastItem = playlist.get(1);
- mAgent.skipToPlaylistItem(lastItem);
- mEventCallback.clear();
- synchronized (mWaitLock) {
- playlist.remove(lastItem);
- mAgent.removePlaylistItem(lastItem);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(1, mAgent.getCurShuffledIndex());
-
- // Remove all items
- for (int i = playlist.size() - 1; i >= 0; --i) {
- MediaItem2 item = playlist.get(i);
- mAgent.skipToPlaylistItem(item);
- mEventCallback.clear();
- synchronized (mWaitLock) {
- playlist.remove(item);
- mAgent.removePlaylistItem(item);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- }
- assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
- }
-
-
- @Test
- public void testPlaylistWithInvalidItem() throws Exception {
- int listSize = 2;
- List<MediaItem2> playlist = createAndSetPlaylist(listSize);
-
- // Add item: [0 (cur), 1] -> [0 (cur), 3 (no_dsd), 1]
- mEventCallback.clear();
- MediaItem2 invalidItem2 = generateMediaItemWithoutDataSourceDesc(2);
- synchronized (mWaitLock) {
- playlist.add(1, invalidItem2);
- mAgent.addPlaylistItem(1, invalidItem2);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(0, mAgent.getCurShuffledIndex());
-
- // Test skip to next item: [0 (cur), 2 (no_dsd), 1] -> [0, 2 (no_dsd), 1 (cur)]
- mAgent.skipToNextItem();
- assertEquals(2, mAgent.getCurShuffledIndex());
-
- // Test skip to previous item: [0, 2 (no_dsd), 1 (cur)] -> [0 (cur), 2 (no_dsd), 1]
- mAgent.skipToPreviousItem();
- assertEquals(0, mAgent.getCurShuffledIndex());
-
- // Remove current item: [0 (cur), 2 (no_dsd), 1] -> [2 (no_dsd), 1 (cur)]
- mEventCallback.clear();
- MediaItem2 item = playlist.get(0);
- synchronized (mWaitLock) {
- playlist.remove(item);
- mAgent.removePlaylistItem(item);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(1, mAgent.getCurShuffledIndex());
-
- // Remove current item: [2 (no_dsd), 1 (cur)] -> [2 (no_dsd)]
- mEventCallback.clear();
- item = playlist.get(1);
- synchronized (mWaitLock) {
- playlist.remove(item);
- mAgent.removePlaylistItem(item);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
-
- // Add invalid item: [2 (no_dsd)] -> [0 (no_dsd), 2 (no_dsd)]
- MediaItem2 invalidItem0 = generateMediaItemWithoutDataSourceDesc(0);
- mEventCallback.clear();
- synchronized (mWaitLock) {
- playlist.add(0, invalidItem0);
- mAgent.addPlaylistItem(0, invalidItem0);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
-
- // Add valid item: [0 (no_dsd), 2 (no_dsd)] -> [0 (no_dsd), 1, 2 (no_dsd)]
- MediaItem2 invalidItem1 = generateMediaItem(1);
- mEventCallback.clear();
- synchronized (mWaitLock) {
- playlist.add(1, invalidItem1);
- mAgent.addPlaylistItem(1, invalidItem1);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(1, mAgent.getCurShuffledIndex());
-
- // Replace the valid item with an invalid item:
- // [0 (no_dsd), 1 (cur), 2 (no_dsd)] -> [0 (no_dsd), 3 (no_dsd), 2 (no_dsd)]
- MediaItem2 invalidItem3 = generateMediaItemWithoutDataSourceDesc(3);
- mEventCallback.clear();
- synchronized (mWaitLock) {
- playlist.set(1, invalidItem3);
- mAgent.replacePlaylistItem(1, invalidItem3);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- assertPlaylistEquals(playlist, mAgent.getPlaylist());
- assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
- }
-
- @Test
- public void testPlaylistAfterOnCurrentDataSourceChanged() throws Exception {
- int listSize = 2;
- verify(mPlayer).registerPlayerEventCallback(Matchers.any(), Matchers.any());
-
- createAndSetPlaylist(listSize);
- assertEquals(0, mAgent.getCurShuffledIndex());
-
- mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
- assertEquals(1, mAgent.getCurShuffledIndex());
- mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
- assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
-
- mAgent.skipToNextItem();
- assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
-
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ONE);
- assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
-
- mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
- assertEquals(0, mAgent.getCurShuffledIndex());
- mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
- assertEquals(1, mAgent.getCurShuffledIndex());
- mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
- assertEquals(0, mAgent.getCurShuffledIndex());
- }
-
- private List<MediaItem2> createAndSetPlaylist(int listSize) throws Exception {
- List<MediaItem2> items = new ArrayList<>();
- for (int i = 0; i < listSize; ++i) {
- items.add(generateMediaItem(i));
- }
- mEventCallback.clear();
- synchronized (mWaitLock) {
- mAgent.setPlaylist(items, null);
- mWaitLock.wait(WAIT_TIME_MS);
- assertTrue(mEventCallback.onPlaylistChangedCalled);
- }
- return items;
- }
-
- private void assertPlaylistEquals(List<MediaItem2> expected, List<MediaItem2> actual) {
- if (expected == actual) {
- return;
- }
- assertTrue(expected != null && actual != null);
- assertEquals(expected.size(), actual.size());
- for (int i = 0; i < expected.size(); ++i) {
- assertTrue(expected.get(i).equals(actual.get(i)));
- }
- }
-
- private MediaItem2 generateMediaItemWithoutDataSourceDesc(int key) {
- return new MediaItem2.Builder(0)
- .setMediaId("TEST_MEDIA_ID_WITHOUT_DSD_" + key)
- .build();
- }
-
- private MediaItem2 generateMediaItem(int key) {
- return new MediaItem2.Builder(0)
- .setMediaId("TEST_MEDIA_ID_" + key)
- .build();
- }
-}