blob: 71a1e5632c9dfe5f0027d4f851811a515246d8f4 [file] [log] [blame]
/*
* 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.car.media.common.browse;
import static androidx.lifecycle.Transformations.map;
import static com.android.car.arch.common.LiveDataFunctions.dataOf;
import static com.android.car.arch.common.LiveDataFunctions.loadingSwitchMap;
import static com.android.car.arch.common.LiveDataFunctions.pair;
import static com.android.car.arch.common.LiveDataFunctions.split;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.app.Application;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
import android.text.TextUtils;
import androidx.annotation.RestrictTo;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.car.arch.common.FutureData;
import com.android.car.arch.common.switching.SwitchingLiveData;
import com.android.car.media.common.MediaConstants;
import com.android.car.media.common.MediaItemMetadata;
import java.util.List;
/**
* Contains observable data needed for displaying playback and browse/search UI. Instances can be
* obtained via {@link MediaBrowserViewModel.Factory}
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class MediaBrowserViewModelImpl extends AndroidViewModel implements
MediaBrowserViewModel.WithMutableBrowseId {
private final SwitchingLiveData<MediaBrowserCompat> mMediaBrowserSwitch =
SwitchingLiveData.newInstance();
private final MutableLiveData<String> mCurrentBrowseId = new MutableLiveData<>();
private final MutableLiveData<String> mCurrentSearchQuery = dataOf(null);
private final LiveData<MediaBrowserCompat> mConnectedMediaBrowser =
map(mMediaBrowserSwitch.asLiveData(), MediaBrowserViewModelImpl::requireConnected);
private final LiveData<FutureData<List<MediaItemMetadata>>> mSearchedMediaItems;
private final LiveData<FutureData<List<MediaItemMetadata>>> mBrowsedMediaItems;
private final LiveData<BrowseState> mBrowseState;
private final LiveData<String> mPackageName;
public MediaBrowserViewModelImpl(@NonNull Application application) {
super(application);
mPackageName = map(mConnectedMediaBrowser,
mediaBrowser -> {
if (mediaBrowser == null) return null;
return mediaBrowser.getServiceComponent().getPackageName();
});
mBrowsedMediaItems =
loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentBrowseId),
split((mediaBrowser, browseId) ->
mediaBrowser == null
? null
: new BrowsedMediaItems(mediaBrowser, browseId)));
mSearchedMediaItems =
loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentSearchQuery),
split((mediaBrowser, query) ->
(mediaBrowser == null || TextUtils.isEmpty(query))
? null
: new SearchedMediaItems(mediaBrowser, query)));
mBrowseState = new MediatorLiveData<BrowseState>() {
{
setValue(BrowseState.EMPTY);
addSource(mBrowsedMediaItems, items -> update());
}
private void update() {
setValue(getState());
}
private BrowseState getState() {
if (mBrowsedMediaItems.getValue() == null) {
// Uninitialized
return BrowseState.EMPTY;
}
if (mBrowsedMediaItems.getValue().isLoading()) {
return BrowseState.LOADING;
}
List<MediaItemMetadata> items = mBrowsedMediaItems.getValue().getData();
if (items == null) {
// Normally this could be null if it hasn't been initialized, but in that case
// isLoading would not be false, so this means it must have encountered an
// error.
return BrowseState.ERROR;
}
if (items.isEmpty()) {
return BrowseState.EMPTY;
}
return BrowseState.LOADED;
}
};
}
private static MediaBrowserCompat requireConnected(@Nullable MediaBrowserCompat mediaBrowser) {
if (mediaBrowser != null && !mediaBrowser.isConnected()) {
throw new IllegalStateException(
"Only connected MediaBrowsers may be provided to MediaBrowserViewModel.");
}
return mediaBrowser;
}
/**
* Set the source {@link MediaBrowserCompat} to use for browsing. If {@code mediaBrowser} emits
* non-null, the MediaBrowser emitted must already be in a connected state.
*/
void setConnectedMediaBrowser(@Nullable LiveData<MediaBrowserCompat> mediaBrowser) {
mMediaBrowserSwitch.setSource(mediaBrowser);
}
LiveData<? extends MediaBrowserCompat> getMediaBrowserSource() {
return mMediaBrowserSwitch.getSource();
}
/**
* Set the current item to be browsed. If available, the list of items will be emitted by {@link
* #getBrowsedMediaItems()}.
*/
@UiThread
@Override
public void setCurrentBrowseId(@Nullable String browseId) {
mCurrentBrowseId.setValue(browseId);
}
/**
* Set the current item to be searched for. If available, the list of items will be emitted
* by {@link #getBrowsedMediaItems()}.
*/
@UiThread
@Override
public void search(@Nullable String query) {
mCurrentSearchQuery.setValue(query);
}
@Override
public LiveData<String> getPackageName() {
return mPackageName;
}
@Override
public LiveData<BrowseState> getBrowseState() {
return mBrowseState;
}
@Override
public LiveData<FutureData<List<MediaItemMetadata>>> getBrowsedMediaItems() {
return mBrowsedMediaItems;
}
@Override
public LiveData<FutureData<List<MediaItemMetadata>>> getSearchedMediaItems() {
return mSearchedMediaItems;
}
@SuppressWarnings("deprecation")
@Override
public LiveData<Boolean> supportsSearch() {
return map(mConnectedMediaBrowser, mediaBrowserCompat -> {
if (mediaBrowserCompat == null) {
return false;
}
Bundle extras = mediaBrowserCompat.getExtras();
if (extras == null) {
return false;
}
if (extras.containsKey(MediaConstants.MEDIA_SEARCH_SUPPORTED)) {
return extras.getBoolean(MediaConstants.MEDIA_SEARCH_SUPPORTED);
}
if (extras.containsKey(MediaConstants.MEDIA_SEARCH_SUPPORTED_PRERELEASE)) {
return extras.getBoolean(MediaConstants.MEDIA_SEARCH_SUPPORTED_PRERELEASE);
}
return false;
});
}
@SuppressWarnings("deprecation")
@Override
public LiveData<Integer> rootBrowsableHint() {
return map(mConnectedMediaBrowser, mediaBrowserCompat -> {
if (mediaBrowserCompat == null) {
return 0;
}
Bundle extras = mediaBrowserCompat.getExtras();
if (extras == null) {
return 0;
}
if (extras.containsKey(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT)) {
return extras.getInt(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT, 0);
}
if (extras.containsKey(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT_PRERELEASE)) {
return extras.getInt(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT_PRERELEASE, 0);
}
return 0;
});
}
@SuppressWarnings("deprecation")
@Override
public LiveData<Integer> rootPlayableHint() {
return map(mConnectedMediaBrowser, mediaBrowserCompat -> {
if (mediaBrowserCompat == null) {
return 0;
}
Bundle extras = mediaBrowserCompat.getExtras();
if (extras == null) {
return 0;
}
if (extras.containsKey(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT)) {
return extras.getInt(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT, 0);
}
if (extras.containsKey(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT_PRERELEASE)) {
return extras.getInt(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT_PRERELEASE, 0);
}
return 0;
});
}
}