1 /*
2  * Copyright (C) 2014 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 android.media.cts;
17 
18 import com.android.compatibility.common.util.SystemUtil;
19 
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.media.AudioManager;
24 import android.media.session.MediaController;
25 import android.media.session.MediaSession;
26 import android.media.session.MediaSessionManager;
27 import android.media.session.PlaybackState;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.Looper;
31 import android.test.InstrumentationTestCase;
32 import android.test.UiThreadTest;
33 import android.view.KeyEvent;
34 
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.TimeUnit;
40 
41 public class MediaSessionManagerTest extends InstrumentationTestCase {
42     private static final String TAG = "MediaSessionManagerTest";
43     private static final int TIMEOUT_MS = 3000;
44     private static final int WAIT_MS = 500;
45 
46     private MediaSessionManager mSessionManager;
47 
48     @Override
setUp()49     protected void setUp() throws Exception {
50         super.setUp();
51         mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext()
52                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
53     }
54 
testGetActiveSessions()55     public void testGetActiveSessions() throws Exception {
56         try {
57             List<MediaController> controllers = mSessionManager.getActiveSessions(null);
58             fail("Expected security exception for unauthorized call to getActiveSessions");
59         } catch (SecurityException e) {
60             // Expected
61         }
62         // TODO enable a notification listener, test again, disable, test again
63     }
64 
65     @UiThreadTest
testAddOnActiveSessionsListener()66     public void testAddOnActiveSessionsListener() throws Exception {
67         try {
68             mSessionManager.addOnActiveSessionsChangedListener(null, null);
69             fail("Expected IAE for call to addOnActiveSessionsChangedListener");
70         } catch (IllegalArgumentException e) {
71             // Expected
72         }
73 
74         MediaSessionManager.OnActiveSessionsChangedListener listener
75                 = new MediaSessionManager.OnActiveSessionsChangedListener() {
76             @Override
77             public void onActiveSessionsChanged(List<MediaController> controllers) {
78 
79             }
80         };
81         try {
82             mSessionManager.addOnActiveSessionsChangedListener(listener, null);
83             fail("Expected security exception for call to addOnActiveSessionsChangedListener");
84         } catch (SecurityException e) {
85             // Expected
86         }
87 
88         // TODO enable a notification listener, test again, disable, verify
89         // updates stopped
90     }
91 
assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount)92     private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) {
93         assertTrue(lhs.getKeyCode() == keyCode
94                 && lhs.getAction() == action
95                 && lhs.getRepeatCount() == repeatCount);
96     }
97 
injectInputEvent(int keyCode, boolean longPress)98     private void injectInputEvent(int keyCode, boolean longPress) throws IOException {
99         // Injecting key with instrumentation requires a window/view, but we don't have it.
100         // Inject key event through the adb commend to workaround.
101         final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode;
102         SystemUtil.runShellCommand(getInstrumentation(), command);
103     }
104 
testSetOnVolumeKeyLongPressListener()105     public void testSetOnVolumeKeyLongPressListener() throws Exception {
106         Context context = getInstrumentation().getTargetContext();
107         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
108             // Skip this test on TV platform because the PhoneWindowManager dispatches volume key
109             // events directly to the audio service to change the system volume.
110             return;
111         }
112 
113         HandlerThread handlerThread = new HandlerThread(TAG);
114         handlerThread.start();
115         Handler handler = new Handler(handlerThread.getLooper());
116 
117         // Ensure that the listener is called for long-press.
118         VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler);
119         mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
120         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
121         assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
122         assertEquals(listener.mKeyEvents.size(), 3);
123         assertKeyEventEquals(listener.mKeyEvents.get(0),
124                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0);
125         assertKeyEventEquals(listener.mKeyEvents.get(1),
126                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1);
127         assertKeyEventEquals(listener.mKeyEvents.get(2),
128                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0);
129 
130         // Ensure the the listener isn't called for short-press.
131         listener = new VolumeKeyLongPressListener(1, handler);
132         mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
133         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
134         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
135         assertEquals(listener.mKeyEvents.size(), 0);
136 
137         // Ensure that the listener isn't called anymore.
138         mSessionManager.setOnVolumeKeyLongPressListener(null, handler);
139         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
140         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
141         assertEquals(listener.mKeyEvents.size(), 0);
142     }
143 
testSetOnMediaKeyListener()144     public void testSetOnMediaKeyListener() throws Exception {
145         HandlerThread handlerThread = new HandlerThread(TAG);
146         handlerThread.start();
147         Handler handler = new Handler(handlerThread.getLooper());
148 
149         MediaSessionCallback callback = new MediaSessionCallback(2);
150         MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
151         session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
152                 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
153         session.setCallback(callback, handler);
154         PlaybackState state = new PlaybackState.Builder()
155                 .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
156         // Fake the media session service so this session can take the media key events.
157         session.setPlaybackState(state);
158         session.setActive(true);
159 
160         // A media playback is also needed to receive media key events.
161         Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
162 
163         // Ensure that the listener is called for media key event,
164         // and any other media sessions don't get the key.
165         MediaKeyListener listener = new MediaKeyListener(2, true, handler);
166         mSessionManager.setOnMediaKeyListener(listener, handler);
167         injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
168         assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
169         assertEquals(listener.mKeyEvents.size(), 2);
170         assertKeyEventEquals(listener.mKeyEvents.get(0),
171                 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
172         assertKeyEventEquals(listener.mKeyEvents.get(1),
173                 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
174         assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
175         assertEquals(callback.mKeyEvents.size(), 0);
176 
177         // Ensure that the listener is called for media key event,
178         // and another media session gets the key.
179         listener = new MediaKeyListener(2, false, handler);
180         mSessionManager.setOnMediaKeyListener(listener, handler);
181         injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
182         assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
183         assertEquals(listener.mKeyEvents.size(), 2);
184         assertKeyEventEquals(listener.mKeyEvents.get(0),
185                 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
186         assertKeyEventEquals(listener.mKeyEvents.get(1),
187                 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
188         assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
189         assertEquals(callback.mKeyEvents.size(), 2);
190         assertKeyEventEquals(callback.mKeyEvents.get(0),
191                 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
192         assertKeyEventEquals(callback.mKeyEvents.get(1),
193                 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
194 
195         // Ensure that the listener isn't called anymore.
196         listener = new MediaKeyListener(1, true, handler);
197         mSessionManager.setOnMediaKeyListener(listener, handler);
198         mSessionManager.setOnMediaKeyListener(null, handler);
199         injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
200         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
201         assertEquals(listener.mKeyEvents.size(), 0);
202     }
203 
204     private class VolumeKeyLongPressListener
205             implements MediaSessionManager.OnVolumeKeyLongPressListener {
206         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
207         private final CountDownLatch mCountDownLatch;
208         private final Handler mHandler;
209 
VolumeKeyLongPressListener(int count, Handler handler)210         public VolumeKeyLongPressListener(int count, Handler handler) {
211             mCountDownLatch = new CountDownLatch(count);
212             mHandler = handler;
213         }
214 
215         @Override
onVolumeKeyLongPress(KeyEvent event)216         public void onVolumeKeyLongPress(KeyEvent event) {
217             mKeyEvents.add(event);
218             // Ensure the listener is called on the thread.
219             assertEquals(mHandler.getLooper(), Looper.myLooper());
220             mCountDownLatch.countDown();
221         }
222     }
223 
224     private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener {
225         private final CountDownLatch mCountDownLatch;
226         private final boolean mConsume;
227         private final Handler mHandler;
228         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
229 
MediaKeyListener(int count, boolean consume, Handler handler)230         public MediaKeyListener(int count, boolean consume, Handler handler) {
231             mCountDownLatch = new CountDownLatch(count);
232             mConsume = consume;
233             mHandler = handler;
234         }
235 
236         @Override
onMediaKey(KeyEvent event)237         public boolean onMediaKey(KeyEvent event) {
238             mKeyEvents.add(event);
239             // Ensure the listener is called on the thread.
240             assertEquals(mHandler.getLooper(), Looper.myLooper());
241             mCountDownLatch.countDown();
242             return mConsume;
243         }
244     }
245 
246     private class MediaSessionCallback extends MediaSession.Callback {
247         private final CountDownLatch mCountDownLatch;
248         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
249 
MediaSessionCallback(int count)250         private MediaSessionCallback(int count) {
251             mCountDownLatch = new CountDownLatch(count);
252         }
253 
onMediaButtonEvent(Intent mediaButtonIntent)254         public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
255             KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra(
256                     Intent.EXTRA_KEY_EVENT);
257             assertNotNull(event);
258             mKeyEvents.add(event);
259             mCountDownLatch.countDown();
260             return true;
261         }
262     }
263 }
264