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