1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting.facade.media; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 import java.util.concurrent.Callable; 22 23 import android.app.Service; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.media.session.MediaController; 27 import android.media.session.MediaSession; 28 import android.media.session.MediaSessionManager; 29 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener; 30 import android.media.session.PlaybackState; 31 import android.media.session.MediaSession.Callback; 32 import android.view.KeyEvent; 33 34 import com.googlecode.android_scripting.Log; 35 import com.googlecode.android_scripting.MainThread; 36 import com.googlecode.android_scripting.facade.EventFacade; 37 import com.googlecode.android_scripting.facade.FacadeManager; 38 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 39 import com.googlecode.android_scripting.rpc.Rpc; 40 import com.googlecode.android_scripting.rpc.RpcDefault; 41 import com.googlecode.android_scripting.rpc.RpcParameter; 42 43 /** 44 * Expose functionalities of MediaSession related classes 45 * like MediaSession, MediaSessionManager, MediaController. 46 * 47 */ 48 public class MediaSessionFacade extends RpcReceiver { 49 50 private final Service mService; 51 private final EventFacade mEventFacade; 52 private final MediaSession mSession; 53 private final MediaSessionManager mManager; 54 private final OnActiveSessionsChangedListener mSessionListener; 55 private final Callback mCallback; 56 57 private List<MediaController> mActiveControllers = null; 58 59 public MediaSessionFacade(FacadeManager manager) { 60 super(manager); 61 mService = manager.getService(); 62 mEventFacade = manager.getReceiver(EventFacade.class); 63 Log.d("Creating MediaSession."); 64 mSession = new MediaSession(mService, "SL4A"); 65 mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); 66 mManager = (MediaSessionManager) mService.getSystemService(Context.MEDIA_SESSION_SERVICE); 67 mCallback = new MediaButtonCallback(mEventFacade); 68 mSessionListener = new MediaSessionListener(); 69 mManager.addOnActiveSessionsChangedListener(mSessionListener, 70 new ComponentName(mService.getPackageName(), this.getClass().getName())); 71 mSession.setActive(true); 72 } 73 74 private class MediaSessionListener implements OnActiveSessionsChangedListener { 75 76 @Override 77 public void onActiveSessionsChanged(List<MediaController> controllers) { 78 Log.d("Active MediaSessions have changed. Update current controller."); 79 int size = controllers.size(); 80 for (int i = 0; i < size; i++) { 81 MediaController controller = controllers.get(i); 82 long flags = controller.getFlags(); 83 // We only care about sessions that handle transport controls, 84 // which will be true for apps using RCC 85 if ((flags & MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) { 86 Log.d("The current active MediaSessions is " + controller.getTag()); 87 return; 88 } 89 } 90 } 91 } 92 93 @Rpc(description = "Retrieve a list of active sessions.") 94 public List<String> mediaGetActiveSessions() { 95 mActiveControllers = mManager.getActiveSessions(null); 96 List<String> results = new ArrayList<String>(); 97 for (MediaController mc : mActiveControllers) { 98 results.add(mc.getTag()); 99 } 100 return results; 101 } 102 103 @Rpc(description = "Add callback to media session.") 104 public void mediaSessionAddCallback() { 105 MainThread.run(mService, new Callable<Object>() { 106 @Override 107 public Object call() throws Exception { 108 Log.d("Adding callback."); 109 mSession.setCallback(mCallback); 110 PlaybackState.Builder bob = new PlaybackState.Builder(); 111 bob.setActions(PlaybackState.ACTION_PLAY | 112 PlaybackState.ACTION_PAUSE | 113 PlaybackState.ACTION_STOP); 114 bob.setState(PlaybackState.STATE_PLAYING, 0, 1); 115 mSession.setPlaybackState(bob.build()); 116 return null; 117 } 118 }); 119 } 120 121 @Rpc(description = "Whether current media session is active.") 122 public Boolean mediaSessionIsActive() { 123 return mSession.isActive(); 124 } 125 126 @Rpc(description = "Simulate a media key press.") 127 public void mediaDispatchMediaKeyEvent(String key) { 128 int keyCode; 129 if (key.equals("Play")) { 130 keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 131 } else if (key.equals("Pause")) { 132 keyCode = KeyEvent.KEYCODE_MEDIA_PAUSE; 133 } else if (key.equals("Stop")) { 134 keyCode = KeyEvent.KEYCODE_MEDIA_STOP; 135 } else if (key.equals("Next")) { 136 keyCode = KeyEvent.KEYCODE_MEDIA_NEXT; 137 } else if (key.equals("Previous")) { 138 keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS; 139 } else if (key.equals("Forward")) { 140 keyCode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; 141 } else if (key.equals("Rewind")) { 142 keyCode = KeyEvent.KEYCODE_MEDIA_REWIND; 143 } else { 144 Log.d("Unrecognized media key."); 145 return; 146 } 147 KeyEvent keyDown = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); 148 KeyEvent keyUp = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); 149 mManager.dispatchMediaKeyEvent(keyDown); 150 mManager.dispatchMediaKeyEvent(keyUp); 151 } 152 153 private MediaController getMediaController(int idx) { 154 return mActiveControllers.get(idx); 155 } 156 157 @Rpc(description = "Call Play on the currently active media session.") 158 public void mediaSessionPlay(@RpcParameter(name = "index") @RpcDefault(value = "0") 159 Integer idx) { 160 getMediaController(idx).getTransportControls().play(); 161 } 162 163 @Rpc(description = "Call Pause on the currently active media session.") 164 public void mediaSessionPause(@RpcParameter(name = "index") @RpcDefault(value = "0") 165 Integer idx) { 166 getMediaController(idx).getTransportControls().pause(); 167 } 168 169 @Rpc(description = "Call Stop on the currently active media session.") 170 public void mediaSessionStop(@RpcParameter(name = "index") @RpcDefault(value = "0") 171 Integer idx) { 172 getMediaController(idx).getTransportControls().stop(); 173 } 174 175 @Rpc(description = "Call Next on the currently active media session.") 176 public void mediaSessionNext(@RpcParameter(name = "index") @RpcDefault(value = "0") 177 Integer idx) { 178 getMediaController(idx).getTransportControls().skipToNext(); 179 } 180 181 @Override 182 public void shutdown() { 183 mSession.setCallback(null); 184 mSession.release(); 185 } 186 } 187