1/*
2 * Copyright (C) 2017 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
17package androidx.leanback.media;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertSame;
21import static org.junit.Assert.assertTrue;
22import static org.mockito.Matchers.anyInt;
23import static org.mockito.Matchers.anyString;
24import static org.mockito.Mockito.never;
25import static org.mockito.Mockito.times;
26import static org.mockito.Mockito.when;
27
28import android.content.Context;
29import android.support.test.InstrumentationRegistry;
30import android.support.test.filters.SmallTest;
31import android.view.ContextThemeWrapper;
32import android.view.KeyEvent;
33import android.view.ViewGroup;
34import android.widget.FrameLayout;
35import android.widget.LinearLayout;
36
37import androidx.leanback.widget.PlaybackControlsRow;
38import androidx.leanback.widget.PlaybackRowPresenter;
39import androidx.leanback.widget.PlaybackTransportRowPresenter;
40import androidx.leanback.widget.RowPresenter;
41
42import org.junit.Test;
43import org.mockito.Mockito;
44
45@SmallTest
46public class PlaybackTransportControlGlueTest {
47
48    public static class PlayerAdapterSample extends PlayerAdapter {
49        @Override
50        public void play() {
51        }
52
53        @Override
54        public void pause() {
55        }
56    }
57
58    public static class PlaybackTransportControlGlueImpl
59            extends PlaybackTransportControlGlue {
60        public PlaybackTransportControlGlueImpl(Context context) {
61            super(context, new PlayerAdapterSample());
62        }
63
64        public PlaybackTransportControlGlueImpl(Context context, PlayerAdapter impl) {
65            super(context, impl);
66        }
67    }
68
69    Context mContext;
70    PlaybackTransportControlGlueImpl mGlue;
71    PlaybackTransportRowPresenter.ViewHolder mViewHolder;
72    PlayerAdapter mAdapter;
73
74    void setupWithMockAdapterAndViewHolder() {
75        mContext = new ContextThemeWrapper(
76                InstrumentationRegistry.getInstrumentation().getTargetContext(),
77                androidx.leanback.test.R.style.Theme_Leanback);
78
79        mAdapter = Mockito.mock(PlayerAdapter.class);
80        when(mAdapter.isPrepared()).thenReturn(true);
81        when(mAdapter.getCurrentPosition()).thenReturn(123L);
82        when(mAdapter.getDuration()).thenReturn(20000L);
83        when(mAdapter.getBufferedPosition()).thenReturn(321L);
84        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
85            @Override
86            public void run() {
87                mGlue = new PlaybackTransportControlGlueImpl(mContext, mAdapter);
88                PlaybackGlueHostImpl host = new PlaybackGlueHostImpl();
89                mGlue.setHost(host);
90
91                PlaybackTransportRowPresenter presenter = (PlaybackTransportRowPresenter)
92                        mGlue.getPlaybackRowPresenter();
93                FrameLayout parent = new FrameLayout(mContext);
94                mViewHolder = (PlaybackTransportRowPresenter.ViewHolder)
95                        presenter.onCreateViewHolder(parent);
96                presenter.onBindViewHolder(mViewHolder, mGlue.getControlsRow());
97            }
98        });
99    }
100
101    void playMockAdapter() {
102        mGlue.play();
103        Mockito.verify(mAdapter, times(1)).play();
104        when(mAdapter.isPlaying()).thenReturn(true);
105        mAdapter.getCallback().onPlayStateChanged(mAdapter);
106    }
107
108    @Test
109    public void usingDefaultRowAndPresenter() {
110        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
111        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
112            @Override
113            public void run() {
114                mGlue = new PlaybackTransportControlGlueImpl(mContext);
115            }
116        });
117        PlaybackGlueHostImpl host = new PlaybackGlueHostImpl();
118
119        mGlue.setHost(host);
120        assertSame(mGlue, host.mGlue);
121        assertSame(host, mGlue.getHost());
122        assertTrue(host.mPlaybackRowPresenter instanceof PlaybackTransportRowPresenter);
123        assertTrue(host.mRow instanceof PlaybackControlsRow);
124
125    }
126    @Test
127    public void customRowPresenter() {
128        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
129        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
130            @Override
131            public void run() {
132                mGlue = new PlaybackTransportControlGlueImpl(mContext);
133            }
134        });
135        PlaybackRowPresenter presenter = new PlaybackRowPresenter() {
136            @Override
137            protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
138                return new RowPresenter.ViewHolder(new LinearLayout(parent.getContext()));
139            }
140        };
141        mGlue.setPlaybackRowPresenter(presenter);
142        PlaybackGlueHostImpl host = new PlaybackGlueHostImpl();
143
144        mGlue.setHost(host);
145        assertSame(mGlue, host.mGlue);
146        assertSame(host, mGlue.getHost());
147        assertSame(host.mPlaybackRowPresenter, presenter);
148        assertTrue(host.mRow instanceof PlaybackControlsRow);
149
150    }
151
152    @Test
153    public void customControlsRow() {
154        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
155        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
156            @Override
157            public void run() {
158                mGlue = new PlaybackTransportControlGlueImpl(mContext);
159            }
160        });
161        PlaybackControlsRow row = new PlaybackControlsRow(mContext);
162        mGlue.setControlsRow(row);
163        PlaybackGlueHostImpl host = new PlaybackGlueHostImpl();
164
165        mGlue.setHost(host);
166        assertSame(mGlue, host.mGlue);
167        assertSame(host, mGlue.getHost());
168        assertTrue(host.mPlaybackRowPresenter instanceof PlaybackTransportRowPresenter);
169        assertSame(host.mRow, row);
170
171    }
172
173    @Test
174    public void customRowAndPresenter() {
175        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
176        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
177            @Override
178            public void run() {
179                mGlue = new PlaybackTransportControlGlueImpl(mContext);
180            }
181        });
182        PlaybackControlsRow row = new PlaybackControlsRow(mContext);
183        mGlue.setControlsRow(row);
184        PlaybackRowPresenter presenter = new PlaybackRowPresenter() {
185            @Override
186            protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
187                return new RowPresenter.ViewHolder(new LinearLayout(parent.getContext()));
188            }
189        };
190        mGlue.setPlaybackRowPresenter(presenter);
191        PlaybackGlueHostImpl host = new PlaybackGlueHostImpl();
192
193        mGlue.setHost(host);
194        assertSame(mGlue, host.mGlue);
195        assertSame(host, mGlue.getHost());
196        assertSame(host.mPlaybackRowPresenter, presenter);
197        assertSame(host.mRow, row);
198
199    }
200
201    @Test
202    public void playerAdapterTest() {
203        setupWithMockAdapterAndViewHolder();
204
205        mGlue.play();
206        Mockito.verify(mAdapter, times(1)).play();
207        mGlue.pause();
208        Mockito.verify(mAdapter, times(1)).pause();
209        mGlue.seekTo(123L);
210        Mockito.verify(mAdapter, times(1)).seekTo(123L);
211        assertEquals(123L, mGlue.getCurrentPosition());
212        assertEquals(20000L, mGlue.getDuration());
213        assertEquals(321L, mGlue.getBufferedPosition());
214
215        assertSame(mGlue.mAdapterCallback, mAdapter.getCallback());
216
217        when(mAdapter.getCurrentPosition()).thenReturn(124L);
218        mAdapter.getCallback().onCurrentPositionChanged(mAdapter);
219        assertEquals(124L, mGlue.getControlsRow().getCurrentPosition());
220
221        when(mAdapter.getBufferedPosition()).thenReturn(333L);
222        mAdapter.getCallback().onBufferedPositionChanged(mAdapter);
223        assertEquals(333L, mGlue.getControlsRow().getBufferedPosition());
224
225        when(mAdapter.getDuration()).thenReturn((long) (Integer.MAX_VALUE) * 2);
226        mAdapter.getCallback().onDurationChanged(mAdapter);
227        assertEquals((long) (Integer.MAX_VALUE) * 2, mGlue.getControlsRow().getDuration());
228
229    }
230
231    @Test
232    public void savePlayerAdapterEventBeforeAttachToHost() {
233        mContext = new ContextThemeWrapper(
234                InstrumentationRegistry.getInstrumentation().getTargetContext(),
235                androidx.leanback.test.R.style.Theme_Leanback);
236
237        final PlayerAdapter impl = Mockito.mock(PlayerAdapter.class);
238        when(impl.isPrepared()).thenReturn(true);
239        when(impl.getCurrentPosition()).thenReturn(123L);
240        when(impl.getDuration()).thenReturn(20000L);
241        when(impl.getBufferedPosition()).thenReturn(321L);
242        final PlaybackGlueHost.PlayerCallback hostCallback = Mockito.mock(
243                PlaybackGlueHost.PlayerCallback.class);
244        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
245            @Override
246            public void run() {
247                mGlue = new PlaybackTransportControlGlueImpl(mContext, impl);
248                // fire events before attach to host.
249                impl.getCallback().onBufferingStateChanged(impl, true);
250                impl.getCallback().onVideoSizeChanged(impl, 200, 150);
251                impl.getCallback().onError(impl, 12, "abc");
252                PlaybackGlueHostImpl host = new PlaybackGlueHostImpl();
253                host.setPlayerCallback(hostCallback);
254                mGlue.setHost(host);
255            }
256        });
257
258        // when attach to host, should pass the buffering state, video size and last error message
259        // to the host.
260        Mockito.verify(hostCallback, times(1)).onBufferingStateChanged(true);
261        Mockito.verify(hostCallback, times(1)).onVideoSizeChanged(200, 150);
262        Mockito.verify(hostCallback, times(1)).onError(12, "abc");
263        Mockito.reset(hostCallback);
264
265        final PlaybackGlueHost.PlayerCallback hostCallback2 = Mockito.mock(
266                PlaybackGlueHost.PlayerCallback.class);
267        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
268            @Override
269            public void run() {
270                PlaybackGlueHostImpl host = new PlaybackGlueHostImpl();
271                host.setPlayerCallback(hostCallback2);
272                mGlue.setHost(host);
273            }
274        });
275
276        // when detach from host, should have host stop buffering.
277        Mockito.verify(hostCallback, times(1)).onBufferingStateChanged(false);
278        Mockito.verify(hostCallback, times(0)).onVideoSizeChanged(anyInt(), anyInt());
279        Mockito.verify(hostCallback, times(0)).onError(anyInt(), anyString());
280
281        // attach to a different host, buffering state and video size should be saved, one time
282        // error state is not saved.
283        Mockito.verify(hostCallback2, times(1)).onBufferingStateChanged(true);
284        Mockito.verify(hostCallback2, times(1)).onVideoSizeChanged(200, 150);
285        Mockito.verify(hostCallback2, times(0)).onError(anyInt(), anyString());
286    }
287
288    @Test
289    public void playStateReceivePlayPause() {
290        setupWithMockAdapterAndViewHolder();
291        playMockAdapter();
292
293        mGlue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
294                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
295        Mockito.verify(mAdapter, times(1)).pause();
296    }
297
298    @Test
299    public void playStateReceivePause() {
300        setupWithMockAdapterAndViewHolder();
301        playMockAdapter();
302
303        mGlue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE,
304                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE));
305        Mockito.verify(mAdapter, times(1)).pause();
306    }
307
308    @Test
309    public void playStateReceivePlay() {
310        setupWithMockAdapterAndViewHolder();
311        playMockAdapter();
312
313        mGlue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY,
314                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
315        Mockito.verify(mAdapter, never()).pause();
316    }
317
318    @Test
319    public void pauseStateReceivePlayPause() {
320        setupWithMockAdapterAndViewHolder();
321
322        mGlue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
323                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
324        Mockito.verify(mAdapter, times(1)).play();
325    }
326
327    @Test
328    public void pauseStateReceivePause() {
329        setupWithMockAdapterAndViewHolder();
330
331        mGlue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE,
332                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE));
333        Mockito.verify(mAdapter, never()).play();
334    }
335
336    @Test
337    public void pauseStateReceivePlay() {
338        setupWithMockAdapterAndViewHolder();
339
340        mGlue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY,
341                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
342        Mockito.verify(mAdapter, times(1)).play();
343    }
344}
345