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.stream.media; 17 18 import android.content.Context; 19 import android.media.MediaMetadata; 20 import android.media.session.MediaController; 21 import android.media.session.MediaSessionManager; 22 import android.media.session.PlaybackState; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.support.annotation.MainThread; 26 import android.support.annotation.NonNull; 27 import android.support.annotation.Nullable; 28 import android.util.Log; 29 import android.view.KeyEvent; 30 import com.android.car.apps.common.util.Assert; 31 32 import java.util.LinkedHashSet; 33 import java.util.List; 34 import java.util.Set; 35 36 /** 37 * A class to listen for changes in sessions from {@link MediaSessionManager}. It also notifies 38 * listeners of changes in the playback state or metadata. 39 */ 40 public class MediaStateManager { 41 private static final String TAG = "MediaStateManager"; 42 private static final String TELECOM_PACKAGE = "com.android.server.telecom"; 43 44 private final Context mContext; 45 46 private MediaAppInfo mConnectedAppInfo; 47 private MediaController mController; 48 private Handler mHandler; 49 private final Set<Listener> mListeners; 50 51 public interface Listener { onMediaSessionConnected(PlaybackState playbackState, MediaMetadata metaData, MediaAppInfo appInfo)52 void onMediaSessionConnected(PlaybackState playbackState, MediaMetadata metaData, 53 MediaAppInfo appInfo); 54 onPlaybackStateChanged(@ullable PlaybackState state)55 void onPlaybackStateChanged(@Nullable PlaybackState state); 56 onMetadataChanged(@ullable MediaMetadata metadata)57 void onMetadataChanged(@Nullable MediaMetadata metadata); 58 onSessionDestroyed()59 void onSessionDestroyed(); 60 } 61 MediaStateManager(@onNull Context context)62 public MediaStateManager(@NonNull Context context) { 63 mContext = context; 64 mHandler = new Handler(Looper.getMainLooper()); 65 mListeners = new LinkedHashSet<>(); 66 } 67 start()68 public void start() { 69 if (Log.isLoggable(TAG, Log.DEBUG)) { 70 Log.d(TAG, "Starting MediaStateManager"); 71 } 72 MediaSessionManager sessionManager 73 = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); 74 75 try { 76 sessionManager.addOnActiveSessionsChangedListener(mSessionChangedListener, null); 77 78 List<MediaController> controllers = sessionManager.getActiveSessions(null); 79 updateMediaController(controllers); 80 } catch (SecurityException e) { 81 // User hasn't granted the permission so we should just go away silently. 82 } 83 } 84 85 @MainThread destroy()86 public void destroy() { 87 Assert.isMainThread(); 88 if (Log.isLoggable(TAG, Log.DEBUG)) { 89 Log.d(TAG, "destroy()"); 90 } 91 stop(); 92 mListeners.clear(); 93 mHandler = null; 94 } 95 96 @MainThread stop()97 public void stop() { 98 Assert.isMainThread(); 99 if (Log.isLoggable(TAG, Log.DEBUG)) { 100 Log.d(TAG, "stop()"); 101 } 102 103 if (mController != null) { 104 mController.unregisterCallback(mMediaControllerCallback); 105 mController = null; 106 } 107 // Calling this with null will clear queue of callbacks and message. This needs to be done 108 // here because prior to the above lines to disconnect and unregister the 109 // controller a posted runnable to do work maybe have happened and thus we need to clear it 110 // out to prevent race conditions. 111 mHandler.removeCallbacksAndMessages(null); 112 } 113 dispatchMediaButton(KeyEvent keyEvent)114 public void dispatchMediaButton(KeyEvent keyEvent) { 115 if (mController != null) { 116 MediaController.TransportControls transportControls 117 = mController.getTransportControls(); 118 int eventId = keyEvent.getKeyCode(); 119 120 switch (eventId) { 121 case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD: 122 transportControls.skipToPrevious(); 123 break; 124 case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD: 125 transportControls.skipToNext(); 126 break; 127 case KeyEvent.KEYCODE_MEDIA_PLAY: 128 transportControls.play(); 129 break; 130 case KeyEvent.KEYCODE_MEDIA_PAUSE: 131 transportControls.pause(); 132 break; 133 case KeyEvent.KEYCODE_MEDIA_STOP: 134 transportControls.stop(); 135 break; 136 default: 137 mController.dispatchMediaButtonEvent(keyEvent); 138 } 139 } 140 } 141 addListener(@onNull Listener listener)142 public void addListener(@NonNull Listener listener) { 143 mListeners.add(listener); 144 } 145 removeListener(@onNull Listener listener)146 public void removeListener(@NonNull Listener listener) { 147 mListeners.remove(listener); 148 } 149 updateMediaController(List<MediaController> controllers)150 private void updateMediaController(List<MediaController> controllers) { 151 if (controllers.size() > 0) { 152 // If the telecom package is trying to onStart a media session, ignore it 153 // so that the existing media item continues to appear in the stream. 154 if (TELECOM_PACKAGE.equals(controllers.get(0).getPackageName())) { 155 return; 156 } 157 158 if (mController != null) { 159 mController.unregisterCallback(mMediaControllerCallback); 160 } 161 // Currently the first controller is the active one playing music. 162 // If this is no longer the case, consider checking notification listener 163 // for a MediaStyle notification to get currently playing media app. 164 mController = controllers.get(0); 165 mController.registerCallback(mMediaControllerCallback); 166 167 mConnectedAppInfo = new MediaAppInfo(mContext, mController.getPackageName()); 168 169 if (Log.isLoggable(TAG, Log.DEBUG)) { 170 Log.d(TAG, "updating media controller"); 171 } 172 173 for (Listener listener : mListeners) { 174 listener.onMediaSessionConnected(mController.getPlaybackState(), 175 mController.getMetadata(), mConnectedAppInfo); 176 } 177 } else { 178 Log.w(TAG, "Updating controllers with an empty list!"); 179 } 180 } 181 isMainThread()182 public static boolean isMainThread() { 183 return Looper.myLooper() == Looper.getMainLooper(); 184 } 185 186 private final MediaSessionManager.OnActiveSessionsChangedListener 187 mSessionChangedListener = new MediaSessionManager.OnActiveSessionsChangedListener() { 188 @Override 189 public void onActiveSessionsChanged(List<MediaController> controllers) { 190 updateMediaController(controllers); 191 } 192 }; 193 194 private final MediaController.Callback mMediaControllerCallback = 195 new MediaController.Callback() { 196 @Override 197 public void onPlaybackStateChanged(@NonNull final PlaybackState state) { 198 mHandler.post(new Runnable() { 199 @Override 200 public void run() { 201 if (Log.isLoggable(TAG, Log.DEBUG)) { 202 Log.d(TAG, "onPlaybackStateChanged(" + state + ")"); 203 } 204 for (Listener listener : mListeners) { 205 listener.onPlaybackStateChanged(state); 206 } 207 } 208 }); 209 } 210 211 @Override 212 public void onMetadataChanged(@Nullable final MediaMetadata metadata) { 213 mHandler.post(new Runnable() { 214 @Override 215 public void run() { 216 if (Log.isLoggable(TAG, Log.DEBUG)) { 217 Log.d(TAG, "onMetadataChanged(" + metadata + ")"); 218 } 219 for (Listener listener : mListeners) { 220 listener.onMetadataChanged(metadata); 221 } 222 } 223 }); 224 } 225 226 @Override 227 public void onSessionDestroyed() { 228 mHandler.post(new Runnable() { 229 @Override 230 public void run() { 231 if (Log.isLoggable(TAG, Log.DEBUG)) { 232 Log.d(TAG, "onSessionDestroyed()"); 233 } 234 235 mConnectedAppInfo = null; 236 if (mController != null) { 237 mController.unregisterCallback(mMediaControllerCallback); 238 mController = null; 239 } 240 241 for (Listener listener : mListeners) { 242 listener.onSessionDestroyed(); 243 } 244 } 245 }); 246 } 247 }; 248 } 249