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 */
16package android.support.v17.leanback.app;
17
18import static junit.framework.Assert.assertEquals;
19
20import static org.junit.Assert.assertNull;
21import static org.junit.Assert.assertSame;
22import static org.junit.Assert.assertTrue;
23import static org.mockito.Matchers.any;
24import static org.mockito.Mockito.times;
25import static org.mockito.Mockito.verify;
26
27import android.graphics.drawable.Drawable;
28import android.os.Bundle;
29import android.support.test.filters.FlakyTest;
30import android.support.test.filters.MediumTest;
31import android.support.test.filters.Suppress;
32import android.support.test.runner.AndroidJUnit4;
33import android.support.v17.leanback.media.PlaybackControlGlue;
34import android.support.v17.leanback.media.PlaybackGlue;
35import android.support.v17.leanback.testutils.PollingCheck;
36import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
37import android.support.v17.leanback.widget.ListRow;
38import android.support.v17.leanback.widget.OnItemViewClickedListener;
39import android.support.v17.leanback.widget.OnItemViewSelectedListener;
40import android.support.v17.leanback.widget.PlaybackControlsRow;
41import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
42import android.support.v17.leanback.widget.Presenter;
43import android.support.v17.leanback.widget.Row;
44import android.support.v17.leanback.widget.RowPresenter;
45import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
46import android.view.KeyEvent;
47import android.view.View;
48
49import org.junit.Test;
50import org.junit.runner.RunWith;
51import org.mockito.ArgumentCaptor;
52import org.mockito.Mockito;
53
54@MediumTest
55@RunWith(AndroidJUnit4.class)
56public class PlaybackFragmentTest extends SingleFragmentTestBase {
57
58    private static final String TAG = "PlaybackFragmentTest";
59    private static final long TRANSITION_LENGTH = 1000;
60
61    @Test
62    public void testDetachCalledWhenDestroyFragment() throws Throwable {
63        final SingleFragmentTestActivity activity =
64                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
65        final PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
66        PlaybackGlue glue = fragment.getGlue();
67        activityTestRule.runOnUiThread(new Runnable() {
68            @Override
69            public void run() {
70                activity.finish();
71            }
72        });
73        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
74            @Override
75            public boolean canProceed() {
76                return fragment.mDestroyCalled;
77            }
78        });
79        assertNull(glue.getHost());
80    }
81
82    @Test
83    public void testSelectedListener() throws Throwable {
84        SingleFragmentTestActivity activity =
85                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
86        PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
87
88        assertTrue(fragment.getView().hasFocus());
89
90        OnItemViewSelectedListener selectedListener = Mockito.mock(
91                OnItemViewSelectedListener.class);
92        fragment.setOnItemViewSelectedListener(selectedListener);
93
94
95        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
96        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
97                controlsRow.getPrimaryActionsAdapter();
98
99        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
100                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
101
102        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
103                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
104
105        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
106                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
107
108        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
109                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
110        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
111        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
112                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
113        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
114
115
116        // First navigate left within PlaybackControlsRow items.
117        verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
118                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
119        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
120        verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
121                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
122        assertSame("Same controls row should be passed to the listener", controlsRow,
123                rowCaptor.getValue());
124        assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
125
126        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
127        verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
128                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
129        assertSame("Same controls row should be passed to the listener", controlsRow,
130                rowCaptor.getValue());
131        assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
132
133        // Now navigate down to a ListRow item.
134        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
135
136        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
137        waitForScrollIdle(fragment.getVerticalGridView());
138        verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
139                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
140        assertSame("Same list row should be passed to the listener", listRow0,
141                rowCaptor.getValue());
142        // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
143        // selected.
144        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
145                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
146        assertTrue("None of the items in the first ListRow are passed to the selected listener.",
147                listRowItemPassed);
148    }
149
150    @Test
151    public void testClickedListener() throws Throwable {
152        SingleFragmentTestActivity activity =
153                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
154        PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
155
156        assertTrue(fragment.getView().hasFocus());
157
158        OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
159        fragment.setOnItemViewClickedListener(clickedListener);
160
161
162        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
163        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
164                controlsRow.getPrimaryActionsAdapter();
165
166        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
167                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
168
169        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
170                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
171
172        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
173                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
174
175        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
176                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
177        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
178        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
179                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
180        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
181
182
183        // First navigate left within PlaybackControlsRow items.
184        verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
185                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
186        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
187        verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
188                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
189        assertSame("Same controls row should be passed to the listener", controlsRow,
190                rowCaptor.getValue());
191        assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
192
193        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
194        verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
195                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
196        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
197        verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
198                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
199        assertSame("Same controls row should be passed to the listener", controlsRow,
200                rowCaptor.getValue());
201        assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
202
203        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
204        verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
205                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
206        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
207        verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
208                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
209        assertSame("Same controls row should be passed to the listener", controlsRow,
210                rowCaptor.getValue());
211        assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
212
213        // Now navigate down to a ListRow item.
214        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
215
216        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
217        waitForScrollIdle(fragment.getVerticalGridView());
218        verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
219                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
220        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
221        verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
222                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
223        assertSame("Same list row should be passed to the listener", listRow0,
224                rowCaptor.getValue());
225        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
226                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
227        assertTrue("None of the items in the first ListRow are passed to the click listener.",
228                listRowItemPassed);
229    }
230
231    @FlakyTest
232    @Suppress
233    @Test
234    public void alignmentRowToBottom() throws Throwable {
235        SingleFragmentTestActivity activity =
236                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
237        final PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
238
239        assertTrue(fragment.getAdapter().size() > 2);
240
241        View playRow = fragment.getVerticalGridView().getChildAt(0);
242        assertTrue(playRow.hasFocus());
243        assertEquals(playRow.getResources().getDimensionPixelSize(
244                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
245                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
246
247        activityTestRule.runOnUiThread(new Runnable() {
248            @Override
249            public void run() {
250                fragment.getVerticalGridView().setSelectedPositionSmooth(
251                        fragment.getAdapter().size() - 1);
252            }
253        });
254        waitForScrollIdle(fragment.getVerticalGridView());
255
256        View lastRow = fragment.getVerticalGridView().getChildAt(
257                fragment.getVerticalGridView().getChildCount() - 1);
258        assertEquals(fragment.getAdapter().size() - 1,
259                fragment.getVerticalGridView().getChildAdapterPosition(lastRow));
260        assertTrue(lastRow.hasFocus());
261        assertEquals(lastRow.getResources().getDimensionPixelSize(
262                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
263                fragment.getVerticalGridView().getHeight() - lastRow.getBottom());
264    }
265
266    public static class PurePlaybackFragment extends PlaybackFragment {
267        @Override
268        public void onCreate(Bundle savedInstanceState) {
269            super.onCreate(savedInstanceState);
270            setFadingEnabled(false);
271            PlaybackControlsRow row = new PlaybackControlsRow();
272            SparseArrayObjectAdapter primaryAdapter = new SparseArrayObjectAdapter(
273                    new ControlButtonPresenterSelector());
274            primaryAdapter.set(0, new PlaybackControlsRow.SkipPreviousAction(getActivity()));
275            primaryAdapter.set(1, new PlaybackControlsRow.PlayPauseAction(getActivity()));
276            primaryAdapter.set(2, new PlaybackControlsRow.SkipNextAction(getActivity()));
277            row.setPrimaryActionsAdapter(primaryAdapter);
278            row.setSecondaryActionsAdapter(null);
279            setPlaybackRow(row);
280            setPlaybackRowPresenter(new PlaybackControlsRowPresenter());
281        }
282    }
283
284    @Test
285    public void setupRowAndPresenterWithoutGlue() {
286        SingleFragmentTestActivity activity =
287                launchAndWaitActivity(PurePlaybackFragment.class, 1000);
288        final PurePlaybackFragment fragment = (PurePlaybackFragment)
289                activity.getTestFragment();
290
291        assertTrue(fragment.getAdapter().size() == 1);
292        View playRow = fragment.getVerticalGridView().getChildAt(0);
293        assertTrue(playRow.hasFocus());
294        assertEquals(playRow.getResources().getDimensionPixelSize(
295                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
296                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
297    }
298
299    public static class ControlGlueFragment extends PlaybackFragment {
300        @Override
301        public void onCreate(Bundle savedInstanceState) {
302            super.onCreate(savedInstanceState);
303            int[] ffspeeds = new int[] {PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0,
304                    PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1};
305            PlaybackGlue glue = new PlaybackControlGlue(
306                    getActivity(), ffspeeds) {
307                @Override
308                public boolean hasValidMedia() {
309                    return true;
310                }
311
312                @Override
313                public boolean isMediaPlaying() {
314                    return false;
315                }
316
317                @Override
318                public CharSequence getMediaTitle() {
319                    return "Title";
320                }
321
322                @Override
323                public CharSequence getMediaSubtitle() {
324                    return "SubTitle";
325                }
326
327                @Override
328                public int getMediaDuration() {
329                    return 100;
330                }
331
332                @Override
333                public Drawable getMediaArt() {
334                    return null;
335                }
336
337                @Override
338                public long getSupportedActions() {
339                    return PlaybackControlGlue.ACTION_PLAY_PAUSE;
340                }
341
342                @Override
343                public int getCurrentSpeedId() {
344                    return PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
345                }
346
347                @Override
348                public int getCurrentPosition() {
349                    return 50;
350                }
351            };
352            glue.setHost(new PlaybackFragmentGlueHost(this));
353        }
354    }
355
356    @Test
357    public void setupWithControlGlue() throws Throwable {
358        SingleFragmentTestActivity activity =
359                launchAndWaitActivity(ControlGlueFragment.class, 1000);
360        final ControlGlueFragment fragment = (ControlGlueFragment)
361                activity.getTestFragment();
362
363        assertTrue(fragment.getAdapter().size() == 1);
364
365        View playRow = fragment.getVerticalGridView().getChildAt(0);
366        assertTrue(playRow.hasFocus());
367        assertEquals(playRow.getResources().getDimensionPixelSize(
368                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
369                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
370    }
371}
372