1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.media.common.browse; 18 19 import static androidx.lifecycle.Transformations.map; 20 21 import static com.android.car.arch.common.LiveDataFunctions.dataOf; 22 import static com.android.car.arch.common.LiveDataFunctions.loadingSwitchMap; 23 import static com.android.car.arch.common.LiveDataFunctions.pair; 24 import static com.android.car.arch.common.LiveDataFunctions.split; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.app.Application; 29 import android.os.Bundle; 30 import android.support.v4.media.MediaBrowserCompat; 31 import android.text.TextUtils; 32 33 import androidx.annotation.RestrictTo; 34 import androidx.lifecycle.AndroidViewModel; 35 import androidx.lifecycle.LiveData; 36 import androidx.lifecycle.MediatorLiveData; 37 import androidx.lifecycle.MutableLiveData; 38 39 import com.android.car.arch.common.FutureData; 40 import com.android.car.arch.common.switching.SwitchingLiveData; 41 import com.android.car.media.common.MediaConstants; 42 import com.android.car.media.common.MediaItemMetadata; 43 44 import java.util.List; 45 46 /** 47 * Contains observable data needed for displaying playback and browse/search UI. Instances can be 48 * obtained via {@link MediaBrowserViewModel.Factory} 49 */ 50 @RestrictTo(RestrictTo.Scope.LIBRARY) 51 class MediaBrowserViewModelImpl extends AndroidViewModel implements MediaBrowserViewModel { 52 53 private final boolean mIsRoot; 54 55 private final SwitchingLiveData<MediaBrowserCompat> mMediaBrowserSwitch = 56 SwitchingLiveData.newInstance(); 57 58 final MutableLiveData<String> mCurrentBrowseId = dataOf(null); 59 final MutableLiveData<String> mCurrentSearchQuery = dataOf(null); 60 private final LiveData<MediaBrowserCompat> mConnectedMediaBrowser = 61 map(mMediaBrowserSwitch.asLiveData(), MediaBrowserViewModelImpl::requireConnected); 62 63 private final LiveData<FutureData<List<MediaItemMetadata>>> mSearchedMediaItems; 64 private final LiveData<FutureData<List<MediaItemMetadata>>> mBrowsedMediaItems; 65 66 private final LiveData<BrowseState> mBrowseState; 67 68 private final LiveData<String> mPackageName; 69 MediaBrowserViewModelImpl(@onNull Application application, boolean isRoot)70 MediaBrowserViewModelImpl(@NonNull Application application, boolean isRoot) { 71 super(application); 72 73 mIsRoot = isRoot; 74 75 mPackageName = map(mConnectedMediaBrowser, 76 mediaBrowser -> { 77 if (mediaBrowser == null) return null; 78 return mediaBrowser.getServiceComponent().getPackageName(); 79 }); 80 81 mBrowsedMediaItems = 82 loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentBrowseId), 83 split((mediaBrowser, browseId) -> { 84 if (mediaBrowser == null || (!mIsRoot && browseId == null)) { 85 return null; 86 } 87 88 String parentId = (mIsRoot) ? mediaBrowser.getRoot() : browseId; 89 return new BrowsedMediaItems(mediaBrowser, parentId); 90 })); 91 mSearchedMediaItems = 92 loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentSearchQuery), 93 split((mediaBrowser, query) -> 94 (mediaBrowser == null || TextUtils.isEmpty(query)) 95 ? null 96 : new SearchedMediaItems(mediaBrowser, query))); 97 98 mBrowseState = new MediatorLiveData<BrowseState>() { 99 { 100 setValue(BrowseState.EMPTY); 101 addSource(mBrowsedMediaItems, items -> update()); 102 } 103 104 private void update() { 105 setValue(getState()); 106 } 107 108 private BrowseState getState() { 109 if (mBrowsedMediaItems.getValue() == null) { 110 // Uninitialized 111 return BrowseState.EMPTY; 112 } 113 if (mBrowsedMediaItems.getValue().isLoading()) { 114 return BrowseState.LOADING; 115 } 116 List<MediaItemMetadata> items = mBrowsedMediaItems.getValue().getData(); 117 if (items == null) { 118 // Normally this could be null if it hasn't been initialized, but in that case 119 // isLoading would not be false, so this means it must have encountered an 120 // error. 121 return BrowseState.ERROR; 122 } 123 if (items.isEmpty()) { 124 return BrowseState.EMPTY; 125 } 126 return BrowseState.LOADED; 127 } 128 }; 129 130 } 131 requireConnected(@ullable MediaBrowserCompat mediaBrowser)132 private static MediaBrowserCompat requireConnected(@Nullable MediaBrowserCompat mediaBrowser) { 133 if (mediaBrowser != null && !mediaBrowser.isConnected()) { 134 throw new IllegalStateException( 135 "Only connected MediaBrowsers may be provided to MediaBrowserViewModel."); 136 } 137 return mediaBrowser; 138 } 139 140 /** 141 * Set the source {@link MediaBrowserCompat} to use for browsing. If {@code mediaBrowser} emits 142 * non-null, the MediaBrowser emitted must already be in a connected state. 143 */ setConnectedMediaBrowser(@ullable LiveData<MediaBrowserCompat> mediaBrowser)144 void setConnectedMediaBrowser(@Nullable LiveData<MediaBrowserCompat> mediaBrowser) { 145 mMediaBrowserSwitch.setSource(mediaBrowser); 146 } 147 getMediaBrowserSource()148 LiveData<? extends MediaBrowserCompat> getMediaBrowserSource() { 149 return mMediaBrowserSwitch.getSource(); 150 } 151 152 @Override getPackageName()153 public LiveData<String> getPackageName() { 154 return mPackageName; 155 } 156 157 @Override getBrowseState()158 public LiveData<BrowseState> getBrowseState() { 159 return mBrowseState; 160 } 161 162 @Override getBrowsedMediaItems()163 public LiveData<FutureData<List<MediaItemMetadata>>> getBrowsedMediaItems() { 164 return mBrowsedMediaItems; 165 } 166 167 @Override getSearchedMediaItems()168 public LiveData<FutureData<List<MediaItemMetadata>>> getSearchedMediaItems() { 169 return mSearchedMediaItems; 170 } 171 172 @SuppressWarnings("deprecation") 173 @Override supportsSearch()174 public LiveData<Boolean> supportsSearch() { 175 return map(mConnectedMediaBrowser, mediaBrowserCompat -> { 176 if (mediaBrowserCompat == null) { 177 return false; 178 } 179 Bundle extras = mediaBrowserCompat.getExtras(); 180 if (extras == null) { 181 return false; 182 } 183 if (extras.containsKey(MediaConstants.MEDIA_SEARCH_SUPPORTED)) { 184 return extras.getBoolean(MediaConstants.MEDIA_SEARCH_SUPPORTED); 185 } 186 if (extras.containsKey(MediaConstants.MEDIA_SEARCH_SUPPORTED_PRERELEASE)) { 187 return extras.getBoolean(MediaConstants.MEDIA_SEARCH_SUPPORTED_PRERELEASE); 188 } 189 return false; 190 }); 191 } 192 193 @SuppressWarnings("deprecation") 194 @Override 195 public LiveData<Integer> rootBrowsableHint() { 196 return map(mConnectedMediaBrowser, mediaBrowserCompat -> { 197 if (mediaBrowserCompat == null) { 198 return 0; 199 } 200 Bundle extras = mediaBrowserCompat.getExtras(); 201 if (extras == null) { 202 return 0; 203 } 204 if (extras.containsKey(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT)) { 205 return extras.getInt(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT, 0); 206 } 207 if (extras.containsKey(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT_PRERELEASE)) { 208 return extras.getInt(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT_PRERELEASE, 0); 209 } 210 return 0; 211 }); 212 } 213 214 @SuppressWarnings("deprecation") 215 @Override 216 public LiveData<Integer> rootPlayableHint() { 217 return map(mConnectedMediaBrowser, mediaBrowserCompat -> { 218 if (mediaBrowserCompat == null) { 219 return 0; 220 } 221 Bundle extras = mediaBrowserCompat.getExtras(); 222 if (extras == null) { 223 return 0; 224 } 225 if (extras.containsKey(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT)) { 226 return extras.getInt(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT, 0); 227 } 228 if (extras.containsKey(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT_PRERELEASE)) { 229 return extras.getInt(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT_PRERELEASE, 0); 230 } 231 return 0; 232 }); 233 } 234 } 235