1 /* 2 * Copyright (c) 2016, 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.localmediaplayer; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.media.MediaDescription; 23 import android.media.browse.MediaBrowser; 24 import android.media.session.MediaSession; 25 import android.os.Bundle; 26 import android.service.media.MediaBrowserService; 27 import android.support.annotation.Nullable; 28 import android.util.Log; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 33 public class LocalMediaBrowserService extends MediaBrowserService { 34 private static final String TAG = "LMBService"; 35 private static final String ROOT_ID = "__ROOT__"; 36 private static final String MEDIA_SESSION_TAG = "LOCAL_MEDIA_SESSION"; 37 38 static final String FOLDERS_ID = "__FOLDERS__"; 39 static final String ARTISTS_ID = "__ARTISTS__"; 40 static final String ALBUMS_ID = "__ALBUMS__"; 41 static final String GENRES_ID = "__GENRES__"; 42 43 static final String ACTION_PLAY = "com.android.car.media.localmediaplayer.ACTION_PLAY"; 44 static final String ACTION_PAUSE = "com.android.car.media.localmediaplayer.ACTION_PAUSE"; 45 static final String ACTION_NEXT = "com.android.car.media.localmediaplayer.ACTION_NEXT"; 46 static final String ACTION_PREV = "com.android.car.media.localmediaplayer.ACTION_PREV"; 47 48 private BrowserRoot mRoot = new BrowserRoot(ROOT_ID, null); 49 List<MediaBrowser.MediaItem> mRootItems = new ArrayList<>(); 50 51 private DataModel mDataModel; 52 private Player mPlayer; 53 private MediaSession mSession; 54 private String mLastCategory; 55 56 private BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { 57 @Override 58 public void onReceive(Context context, Intent intent) { 59 if (intent.getAction() == null) { 60 return; 61 } 62 63 switch (intent.getAction()) { 64 case ACTION_PLAY: 65 mPlayer.onPlay(); 66 break; 67 case ACTION_PAUSE: 68 mPlayer.onPause(); 69 break; 70 case ACTION_NEXT: 71 mPlayer.onSkipToNext(); 72 break; 73 case ACTION_PREV: 74 mPlayer.onSkipToPrevious(); 75 break; 76 default: 77 Log.w(TAG, "Ingoring intent with unknown action=" + intent); 78 } 79 } 80 }; 81 addRootItems()82 private void addRootItems() { 83 MediaDescription folders = new MediaDescription.Builder() 84 .setMediaId(FOLDERS_ID) 85 .setTitle(getString(R.string.folders_title)) 86 .setIconUri(Utils.getUriForResource(this, R.drawable.ic_folder)) 87 .build(); 88 mRootItems.add(new MediaBrowser.MediaItem(folders, MediaBrowser.MediaItem.FLAG_BROWSABLE)); 89 90 MediaDescription albums = new MediaDescription.Builder() 91 .setMediaId(ALBUMS_ID) 92 .setTitle(getString(R.string.albums_title)) 93 .setIconUri(Utils.getUriForResource(this, R.drawable.ic_album)) 94 .build(); 95 mRootItems.add(new MediaBrowser.MediaItem(albums, MediaBrowser.MediaItem.FLAG_BROWSABLE)); 96 97 MediaDescription artists = new MediaDescription.Builder() 98 .setMediaId(ARTISTS_ID) 99 .setTitle(getString(R.string.artists_title)) 100 .setIconUri(Utils.getUriForResource(this, R.drawable.ic_artist)) 101 .build(); 102 mRootItems.add(new MediaBrowser.MediaItem(artists, MediaBrowser.MediaItem.FLAG_BROWSABLE)); 103 104 MediaDescription genres = new MediaDescription.Builder() 105 .setMediaId(GENRES_ID) 106 .setTitle(getString(R.string.genres_title)) 107 .setIconUri(Utils.getUriForResource(this, R.drawable.ic_genre)) 108 .build(); 109 mRootItems.add(new MediaBrowser.MediaItem(genres, MediaBrowser.MediaItem.FLAG_BROWSABLE)); 110 } 111 112 @Override onCreate()113 public void onCreate() { 114 super.onCreate(); 115 116 // TODO: This doesn't handle the case where the user revokes the permission very well, the 117 // prompt will only show up once this service has been recreated which is non-deterministic. 118 if (!Utils.hasRequiredPermissions(this)) { 119 Intent intent = new Intent(this, PermissionsActivity.class); 120 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 121 startActivity(intent); 122 } 123 124 mDataModel = new DataModel(this); 125 addRootItems(); 126 mSession = new MediaSession(this, MEDIA_SESSION_TAG); 127 setSessionToken(mSession.getSessionToken()); 128 mPlayer = new Player(this, mSession, mDataModel); 129 mSession.setCallback(mPlayer); 130 mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS 131 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 132 mPlayer.maybeRestoreState(); 133 134 IntentFilter filter = new IntentFilter(); 135 filter.addAction(ACTION_PLAY); 136 filter.addAction(ACTION_PAUSE); 137 filter.addAction(ACTION_NEXT); 138 filter.addAction(ACTION_PREV); 139 registerReceiver(mNotificationReceiver, filter); 140 } 141 142 @Override onDestroy()143 public void onDestroy() { 144 mPlayer.saveState(); 145 mPlayer.destroy(); 146 mSession.release(); 147 unregisterReceiver(mNotificationReceiver); 148 super.onDestroy(); 149 } 150 151 @Nullable 152 @Override onGetRoot(String clientName, int clientUid, Bundle rootHints)153 public BrowserRoot onGetRoot(String clientName, int clientUid, Bundle rootHints) { 154 if (Log.isLoggable(TAG, Log.DEBUG)) { 155 Log.d(TAG, "onGetRoot clientName=" + clientName); 156 } 157 return mRoot; 158 } 159 160 @Override onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result)161 public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) { 162 if (Log.isLoggable(TAG, Log.DEBUG)) { 163 Log.d(TAG, "onLoadChildren parentId=" + parentId); 164 } 165 166 switch (parentId) { 167 case ROOT_ID: 168 result.sendResult(mRootItems); 169 mLastCategory = parentId; 170 break; 171 case FOLDERS_ID: 172 mDataModel.onQueryByFolder(parentId, result); 173 mLastCategory = parentId; 174 break; 175 case ALBUMS_ID: 176 mDataModel.onQueryByAlbum(parentId, result); 177 mLastCategory = parentId; 178 break; 179 case ARTISTS_ID: 180 mDataModel.onQueryByArtist(parentId, result); 181 mLastCategory = parentId; 182 break; 183 case GENRES_ID: 184 mDataModel.onQueryByGenre(parentId, result); 185 mLastCategory = parentId; 186 break; 187 default: 188 mDataModel.onQueryByKey(mLastCategory, parentId, result); 189 } 190 } 191 } 192