• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.UiThread;
29 import android.app.Application;
30 import android.os.Bundle;
31 import android.support.v4.media.MediaBrowserCompat;
32 import android.text.TextUtils;
33 
34 import androidx.annotation.RestrictTo;
35 import androidx.lifecycle.AndroidViewModel;
36 import androidx.lifecycle.LiveData;
37 import androidx.lifecycle.MediatorLiveData;
38 import androidx.lifecycle.MutableLiveData;
39 
40 import com.android.car.arch.common.FutureData;
41 import com.android.car.arch.common.switching.SwitchingLiveData;
42 import com.android.car.media.common.MediaConstants;
43 import com.android.car.media.common.MediaItemMetadata;
44 
45 import java.util.List;
46 
47 /**
48  * Contains observable data needed for displaying playback and browse/search UI. Instances can be
49  * obtained via {@link MediaBrowserViewModel.Factory}
50  */
51 @RestrictTo(RestrictTo.Scope.LIBRARY)
52 public class MediaBrowserViewModelImpl extends AndroidViewModel implements
53         MediaBrowserViewModel.WithMutableBrowseId {
54 
55     private final SwitchingLiveData<MediaBrowserCompat> mMediaBrowserSwitch =
56             SwitchingLiveData.newInstance();
57 
58     private final MutableLiveData<String> mCurrentBrowseId = new MutableLiveData<>();
59     private 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)70     public MediaBrowserViewModelImpl(@NonNull Application application) {
71         super(application);
72 
73         mPackageName = map(mConnectedMediaBrowser,
74                 mediaBrowser -> {
75                     if (mediaBrowser == null) return null;
76                     return mediaBrowser.getServiceComponent().getPackageName();
77                 });
78 
79         mBrowsedMediaItems =
80                 loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentBrowseId),
81                         split((mediaBrowser, browseId) ->
82                                 mediaBrowser == null
83                                         ? null
84                                         : new BrowsedMediaItems(mediaBrowser, browseId)));
85         mSearchedMediaItems =
86                 loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentSearchQuery),
87                         split((mediaBrowser, query) ->
88                                 (mediaBrowser == null || TextUtils.isEmpty(query))
89                                         ? null
90                                         : new SearchedMediaItems(mediaBrowser, query)));
91 
92         mBrowseState = new MediatorLiveData<BrowseState>() {
93             {
94                 setValue(BrowseState.EMPTY);
95                 addSource(mBrowsedMediaItems, items -> update());
96             }
97 
98             private void update() {
99                 setValue(getState());
100             }
101 
102             private BrowseState getState() {
103                 if (mBrowsedMediaItems.getValue() == null) {
104                     // Uninitialized
105                     return BrowseState.EMPTY;
106                 }
107                 if (mBrowsedMediaItems.getValue().isLoading()) {
108                     return BrowseState.LOADING;
109                 }
110                 List<MediaItemMetadata> items = mBrowsedMediaItems.getValue().getData();
111                 if (items == null) {
112                     // Normally this could be null if it hasn't been initialized, but in that case
113                     // isLoading would not be false, so this means it must have encountered an
114                     // error.
115                     return BrowseState.ERROR;
116                 }
117                 if (items.isEmpty()) {
118                     return BrowseState.EMPTY;
119                 }
120                 return BrowseState.LOADED;
121             }
122         };
123 
124     }
125 
requireConnected(@ullable MediaBrowserCompat mediaBrowser)126     private static MediaBrowserCompat requireConnected(@Nullable MediaBrowserCompat mediaBrowser) {
127         if (mediaBrowser != null && !mediaBrowser.isConnected()) {
128             throw new IllegalStateException(
129                     "Only connected MediaBrowsers may be provided to MediaBrowserViewModel.");
130         }
131         return mediaBrowser;
132     }
133 
134     /**
135      * Set the source {@link MediaBrowserCompat} to use for browsing. If {@code mediaBrowser} emits
136      * non-null, the MediaBrowser emitted must already be in a connected state.
137      */
setConnectedMediaBrowser(@ullable LiveData<MediaBrowserCompat> mediaBrowser)138     void setConnectedMediaBrowser(@Nullable LiveData<MediaBrowserCompat> mediaBrowser) {
139         mMediaBrowserSwitch.setSource(mediaBrowser);
140     }
141 
getMediaBrowserSource()142     LiveData<? extends MediaBrowserCompat> getMediaBrowserSource() {
143         return mMediaBrowserSwitch.getSource();
144     }
145 
146     /**
147      * Set the current item to be browsed. If available, the list of items will be emitted by {@link
148      * #getBrowsedMediaItems()}.
149      */
150     @UiThread
151     @Override
setCurrentBrowseId(@ullable String browseId)152     public void setCurrentBrowseId(@Nullable String browseId) {
153         mCurrentBrowseId.setValue(browseId);
154     }
155 
156     /**
157      * Set the current item to be searched for. If available, the list of items will be emitted
158      * by {@link #getBrowsedMediaItems()}.
159      */
160     @UiThread
161     @Override
search(@ullable String query)162     public void search(@Nullable String query) {
163         mCurrentSearchQuery.setValue(query);
164     }
165 
166     @Override
getPackageName()167     public LiveData<String> getPackageName() {
168         return mPackageName;
169     }
170 
171     @Override
getBrowseState()172     public LiveData<BrowseState> getBrowseState() {
173         return mBrowseState;
174     }
175 
176     @Override
getBrowsedMediaItems()177     public LiveData<FutureData<List<MediaItemMetadata>>> getBrowsedMediaItems() {
178         return mBrowsedMediaItems;
179     }
180 
181     @Override
getSearchedMediaItems()182     public LiveData<FutureData<List<MediaItemMetadata>>> getSearchedMediaItems() {
183         return mSearchedMediaItems;
184     }
185 
186     @SuppressWarnings("deprecation")
187     @Override
supportsSearch()188     public LiveData<Boolean> supportsSearch() {
189         return map(mConnectedMediaBrowser, mediaBrowserCompat -> {
190             if (mediaBrowserCompat == null) {
191                 return false;
192             }
193             Bundle extras = mediaBrowserCompat.getExtras();
194             if (extras == null) {
195                 return false;
196             }
197             if (extras.containsKey(MediaConstants.MEDIA_SEARCH_SUPPORTED)) {
198                 return extras.getBoolean(MediaConstants.MEDIA_SEARCH_SUPPORTED);
199             }
200             if (extras.containsKey(MediaConstants.MEDIA_SEARCH_SUPPORTED_PRERELEASE)) {
201                 return extras.getBoolean(MediaConstants.MEDIA_SEARCH_SUPPORTED_PRERELEASE);
202             }
203             return false;
204         });
205     }
206 
207     @SuppressWarnings("deprecation")
208     @Override
209     public LiveData<Integer> rootBrowsableHint() {
210         return map(mConnectedMediaBrowser, mediaBrowserCompat -> {
211             if (mediaBrowserCompat == null) {
212                 return 0;
213             }
214             Bundle extras = mediaBrowserCompat.getExtras();
215             if (extras == null) {
216                 return 0;
217             }
218             if (extras.containsKey(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT)) {
219                 return extras.getInt(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT, 0);
220             }
221             if (extras.containsKey(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT_PRERELEASE)) {
222                 return extras.getInt(MediaConstants.CONTENT_STYLE_BROWSABLE_HINT_PRERELEASE, 0);
223             }
224             return 0;
225         });
226     }
227 
228     @SuppressWarnings("deprecation")
229     @Override
230     public LiveData<Integer> rootPlayableHint() {
231         return map(mConnectedMediaBrowser, mediaBrowserCompat -> {
232             if (mediaBrowserCompat == null) {
233                 return 0;
234             }
235             Bundle extras = mediaBrowserCompat.getExtras();
236             if (extras == null) {
237                 return 0;
238             }
239             if (extras.containsKey(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT)) {
240                 return extras.getInt(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT, 0);
241             }
242             if (extras.containsKey(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT_PRERELEASE)) {
243                 return extras.getInt(MediaConstants.CONTENT_STYLE_PLAYABLE_HINT_PRERELEASE, 0);
244             }
245             return 0;
246         });
247     }
248 }
249