• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2019, 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 package com.android.car.media.testmediaapp;
17 
18 import android.content.Context;
19 import android.media.AudioManager;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.support.v4.media.MediaBrowserCompat.MediaItem;
23 import android.support.v4.media.session.MediaSessionCompat;
24 import android.support.v4.media.session.PlaybackStateCompat;
25 import android.util.Log;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.media.MediaBrowserServiceCompat;
30 
31 import com.android.car.media.testmediaapp.loader.TmaLoader;
32 import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaAccountType;
33 import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaNodeReplyDelay;
34 import com.android.car.media.testmediaapp.prefs.TmaPrefs;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 
40 /**
41  * Implementation of {@link MediaBrowserServiceCompat} that delivers {@link MediaItem}s based on
42  * json configuration files stored in the application's assets. Those assets combined with a few
43  * preferences (see: {@link TmaPrefs}), allow to create a variety of use cases (including error
44  * states) to stress test the Car Media application. <p/>
45  * The media items are cached in the {@link TmaLibrary}, and can be virtually played with
46  * {@link TmaPlayer}.
47  */
48 public class TmaBrowser extends MediaBrowserServiceCompat {
49 
50     private static final String MEDIA_SESSION_TAG = "TEST_MEDIA_SESSION";
51     private static final String ROOT_ID = "_ROOT_ID_";
52     private static final String SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED";
53 
54     private TmaPrefs mPrefs;
55     private Handler mHandler;
56     private MediaSessionCompat mSession;
57     private TmaLibrary mLibrary;
58     private TmaPlayer mPlayer;
59 
60     private BrowserRoot mRoot;
61     private String mLastLoadedNodeId;
62 
63     @Override
onCreate()64     public void onCreate() {
65         super.onCreate();
66         mPrefs = TmaPrefs.getInstance(this);
67         mHandler = new Handler();
68         mSession = new MediaSessionCompat(this, MEDIA_SESSION_TAG);
69         setSessionToken(mSession.getSessionToken());
70 
71         mLibrary = new TmaLibrary(new TmaLoader(this));
72         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
73         mPlayer = new TmaPlayer(this, mLibrary, audioManager, mHandler, mSession);
74 
75         mSession.setCallback(mPlayer);
76         mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
77                 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
78 
79         mPrefs.mAccountType.registerChangeListener(
80                 (oldValue, newValue) -> onAccountChanged(newValue));
81 
82         mPrefs.mRootNodeType.registerChangeListener(
83                 (oldValue, newValue) -> invalidateRoot());
84 
85         mPrefs.mRootReplyDelay.registerChangeListener(
86                 (oldValue, newValue) -> invalidateRoot());
87 
88         Bundle extras = new Bundle();
89         extras.putBoolean(SEARCH_SUPPORTED, true);
90         mRoot = new BrowserRoot(ROOT_ID, extras);
91     }
92 
93     @Override
onDestroy()94     public void onDestroy() {
95         mSession.release();
96         mHandler = null;
97         mPrefs = null;
98         super.onDestroy();
99     }
100 
onAccountChanged(TmaAccountType accountType)101     private void onAccountChanged(TmaAccountType accountType) {
102         if (accountType == TmaAccountType.NONE) {
103             mPlayer.setPlaybackState(
104                     new TmaMediaEvent(TmaMediaEvent.EventState.ERROR,
105                             TmaMediaEvent.StateErrorCode.AUTHENTICATION_EXPIRED,
106                             getResources().getString(R.string.no_account),
107                             getResources().getString(R.string.select_account),
108                             TmaMediaEvent.ResolutionIntent.PREFS, 0));
109         } else {
110             // TODO don't reset error in all cases...
111             PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
112             playbackState.setState(PlaybackStateCompat.STATE_PAUSED, 0, 0);
113             mSession.setPlaybackState(playbackState.build());
114         }
115         invalidateRoot();
116     }
117 
invalidateRoot()118     private void invalidateRoot() {
119         notifyChildrenChanged(ROOT_ID);
120     }
121 
122     @Override
onGetRoot( @onNull String clientPackageName, int clientUid, Bundle rootHints)123     public BrowserRoot onGetRoot(
124             @NonNull String clientPackageName, int clientUid, Bundle rootHints) {
125         return mRoot;
126     }
127 
128     @Override
onLoadChildren(@onNull String parentId, @NonNull Result<List<MediaItem>> result)129     public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaItem>> result) {
130         mLastLoadedNodeId = parentId;
131         getMediaItemsWithDelay(parentId, result, null);
132     }
133 
134     @Override
onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result)135     public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) {
136         getMediaItemsWithDelay(mLastLoadedNodeId, result, query);
137     }
138 
getMediaItemsWithDelay(@onNull String parentId, @NonNull Result<List<MediaItem>> result, @Nullable String filter)139     private void getMediaItemsWithDelay(@NonNull String parentId,
140             @NonNull Result<List<MediaItem>> result, @Nullable String filter) {
141         // TODO: allow per item override of the delay ?
142         TmaNodeReplyDelay delay = mPrefs.mRootReplyDelay.getValue();
143         Runnable task = () -> {
144             TmaMediaItem node;
145             if (TmaAccountType.NONE.equals(mPrefs.mAccountType.getValue())) {
146                 node = null;
147             } else if (ROOT_ID.equals(parentId)) {
148                 node = mLibrary.getRoot(mPrefs.mRootNodeType.getValue());
149             } else {
150                 node = mLibrary.getMediaItemById(parentId);
151             }
152 
153             if (node == null) {
154                 result.sendResult(null);
155             } else {
156                 List<MediaItem> items = new ArrayList<>(node.mChildren.size());
157                 for (TmaMediaItem child : node.mChildren) {
158                     MediaItem item = child.toMediaItem();
159                     CharSequence title = item.getDescription().getTitle();
160                     if (filter == null || (title != null && title.toString().contains(filter))) {
161                         items.add(item);
162                     }
163                 }
164                 result.sendResult(items);
165             }
166         };
167         if (delay == TmaNodeReplyDelay.NONE) {
168             task.run();
169         } else {
170             result.detach();
171             mHandler.postDelayed(task, delay.mReplyDelayMs);
172         }
173     }
174 }
175