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 
17 package com.android.setupwizardlib.view;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.mockito.Mockito.mock;
21 import static org.robolectric.RuntimeEnvironment.application;
22 
23 import android.app.Activity;
24 import android.graphics.SurfaceTexture;
25 import android.net.Uri;
26 import androidx.annotation.RawRes;
27 import android.view.View;
28 import com.android.setupwizardlib.R;
29 import org.junit.Before;
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 import org.mockito.Mock;
33 import org.mockito.MockitoAnnotations;
34 import org.robolectric.Robolectric;
35 import org.robolectric.RobolectricTestRunner;
36 import org.robolectric.Shadows;
37 import org.robolectric.annotation.Config;
38 import org.robolectric.shadows.ShadowMediaPlayer;
39 import org.robolectric.shadows.ShadowMediaPlayer.InvalidStateBehavior;
40 import org.robolectric.shadows.ShadowMediaPlayer.MediaInfo;
41 import org.robolectric.shadows.util.DataSource;
42 import org.robolectric.util.ReflectionHelpers;
43 import org.robolectric.util.ReflectionHelpers.ClassParameter;
44 
45 @RunWith(RobolectricTestRunner.class)
46 @Config(sdk = Config.NEWEST_SDK)
47 public class IllustrationVideoViewTest {
48 
49   @Mock private SurfaceTexture surfaceTexture;
50 
51   private IllustrationVideoView view;
52 
53   @Before
setUp()54   public void setUp() {
55     MockitoAnnotations.initMocks(this);
56     ShadowMediaPlayer.addMediaInfo(
57         DataSource.toDataSource(
58             "android.resource://" + application.getPackageName() + "/" + android.R.color.white),
59         new ShadowMediaPlayer.MediaInfo(100, 10));
60     ShadowMediaPlayer.addMediaInfo(
61         DataSource.toDataSource(
62             "android.resource://" + application.getPackageName() + "/" + android.R.color.black),
63         new ShadowMediaPlayer.MediaInfo(100, 10));
64   }
65 
66   @Test
testPausedWhenWindowFocusLost()67   public void testPausedWhenWindowFocusLost() {
68     createDefaultView();
69     Robolectric.flushForegroundThreadScheduler();
70     view.start();
71 
72     assertThat(view.mMediaPlayer).isNotNull();
73     assertThat(view.surface).isNotNull();
74 
75     view.onWindowFocusChanged(false);
76     assertThat(getShadowMediaPlayer().getState()).isEqualTo(ShadowMediaPlayer.State.PAUSED);
77   }
78 
79   @Test
testStartedWhenWindowFocusRegained()80   public void testStartedWhenWindowFocusRegained() {
81     testPausedWhenWindowFocusLost();
82     Robolectric.flushForegroundThreadScheduler();
83 
84     view.onWindowFocusChanged(true);
85     assertThat(getShadowMediaPlayer().getState()).isEqualTo(ShadowMediaPlayer.State.STARTED);
86   }
87 
88   @Test
testSurfaceReleasedWhenTextureDestroyed()89   public void testSurfaceReleasedWhenTextureDestroyed() {
90     createDefaultView();
91     view.start();
92 
93     assertThat(view.mMediaPlayer).isNotNull();
94     assertThat(view.surface).isNotNull();
95 
96     // MediaPlayer is set to null after destroy. Retrieve it first before we call destroy.
97     ShadowMediaPlayer shadowMediaPlayer = getShadowMediaPlayer();
98     view.onSurfaceTextureDestroyed(surfaceTexture);
99     assertThat(shadowMediaPlayer.getState()).isEqualTo(ShadowMediaPlayer.State.END);
100   }
101 
102   @Test
testXmlSetVideoResId()103   public void testXmlSetVideoResId() {
104     createDefaultView();
105     assertThat(getShadowMediaPlayer().getSourceUri().toString())
106         .isEqualTo("android.resource://com.android.setupwizardlib/" + android.R.color.white);
107   }
108 
109   @Test
testSetVideoResId()110   public void testSetVideoResId() {
111     createDefaultView();
112 
113     @RawRes int black = android.R.color.black;
114     view.setVideoResource(black);
115 
116     assertThat(getShadowMediaPlayer().getSourceUri().toString())
117         .isEqualTo("android.resource://com.android.setupwizardlib/" + android.R.color.black);
118   }
119 
120   @Test
prepareVideo_shouldSetAspectRatio()121   public void prepareVideo_shouldSetAspectRatio() {
122     createDefaultView();
123 
124     ReflectionHelpers.setField(getShadowMediaPlayer(), "videoWidth", 720);
125     ReflectionHelpers.setField(getShadowMediaPlayer(), "videoHeight", 1280);
126 
127     Robolectric.flushForegroundThreadScheduler();
128     view.start();
129 
130     view.measure(
131         View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY),
132         View.MeasureSpec.makeMeasureSpec(720, View.MeasureSpec.EXACTLY));
133 
134     final float aspectRatio = (float) view.getMeasuredHeight() / view.getMeasuredWidth();
135     assertThat(aspectRatio).isWithin(0.001f).of(1280f / 720f);
136   }
137 
138   @Test
prepareVideo_zeroHeight_shouldSetAspectRatioToZero()139   public void prepareVideo_zeroHeight_shouldSetAspectRatioToZero() {
140     createDefaultView();
141 
142     ReflectionHelpers.setField(getShadowMediaPlayer(), "videoWidth", 720);
143     ReflectionHelpers.setField(getShadowMediaPlayer(), "videoHeight", 0);
144 
145     Robolectric.flushForegroundThreadScheduler();
146     view.start();
147 
148     final float aspectRatio = (float) view.getHeight() / view.getWidth();
149     assertThat(aspectRatio).isEqualTo(0.0f);
150   }
151 
152   @Test
setVideoResId_resetDiffVideoResFromDiffPackage_videoResShouldBeSet()153   public void setVideoResId_resetDiffVideoResFromDiffPackage_videoResShouldBeSet() {
154     // VideoRes default set as android.R.color.white with
155     // default package(com.android.setupwizardlib)
156     createDefaultView();
157 
158     // reset different videoRes from different package
159     String newPackageName = "com.android.fakepackage";
160     @RawRes int black = android.R.color.black;
161     addMediaInfo(black, newPackageName);
162     view.setVideoResource(black, newPackageName);
163 
164     // should be reset to black with the new package
165     assertThat(getShadowMediaPlayer().getSourceUri().toString())
166         .isEqualTo("android.resource://" + newPackageName + "/" + android.R.color.black);
167   }
168 
169   @Test
setVideoResId_resetDiffVideoResFromSamePackage_videoResShouldBeSet()170   public void setVideoResId_resetDiffVideoResFromSamePackage_videoResShouldBeSet() {
171     // VideoRes default set as android.R.color.white with
172     // default package(com.android.setupwizardlib)
173     createDefaultView();
174 
175     // reset different videoRes from the same package(default package)
176     String defaultPackageName = "com.android.setupwizardlib";
177     @RawRes int black = android.R.color.black;
178     addMediaInfo(black, defaultPackageName);
179     view.setVideoResource(black, defaultPackageName);
180 
181     // should be reset to black with the same package(default package)
182     assertThat(getShadowMediaPlayer().getSourceUri().toString())
183         .isEqualTo("android.resource://" + defaultPackageName + "/" + android.R.color.black);
184   }
185 
186   @Test
setVideoResId_resetSameVideoResFromDifferentPackage_videoResShouldBeSet()187   public void setVideoResId_resetSameVideoResFromDifferentPackage_videoResShouldBeSet() {
188     // VideoRes default set as android.R.color.white with
189     // default package(com.android.setupwizardlib)
190     createDefaultView();
191 
192     // reset same videoRes from different package
193     @RawRes int white = android.R.color.white;
194     String newPackageName = "com.android.fakepackage";
195     addMediaInfo(white, newPackageName);
196     view.setVideoResource(white, newPackageName);
197 
198     // should be white with the new package
199     assertThat(getShadowMediaPlayer().getSourceUri().toString())
200         .isEqualTo("android.resource://" + newPackageName + "/" + android.R.color.white);
201   }
202 
getShadowMediaPlayer()203   private ShadowMediaPlayer getShadowMediaPlayer() {
204     return Shadows.shadowOf(view.mMediaPlayer);
205   }
206 
createDefaultView()207   private void createDefaultView() {
208     view =
209         new IllustrationVideoView(
210             application,
211             Robolectric.buildAttributeSet()
212                 // Any resource attribute should work, since the data source is fake
213                 .addAttribute(R.attr.suwVideo, "@android:color/white")
214                 .build());
215 
216     Activity activity = Robolectric.setupActivity(Activity.class);
217     activity.setContentView(view);
218     setWindowVisible();
219 
220     view.setSurfaceTexture(mock(SurfaceTexture.class));
221     view.onSurfaceTextureAvailable(surfaceTexture, 500, 500);
222     getShadowMediaPlayer().setInvalidStateBehavior(InvalidStateBehavior.EMULATE);
223   }
224 
setWindowVisible()225   private void setWindowVisible() {
226     Object viewRootImpl = ReflectionHelpers.callInstanceMethod(view, "getViewRootImpl");
227     ReflectionHelpers.callInstanceMethod(
228         viewRootImpl, "handleAppVisibility", ClassParameter.from(boolean.class, true));
229     assertThat(view.isAttachedToWindow()).isTrue();
230     assertThat(view.getWindowVisibility()).isEqualTo(View.VISIBLE);
231   }
232 
addMediaInfo(@awRes int res, String packageName)233   private void addMediaInfo(@RawRes int res, String packageName) {
234     ShadowMediaPlayer.addMediaInfo(
235         DataSource.toDataSource(
236             application, Uri.parse("android.resource://" + packageName + "/" + res), null),
237         new MediaInfo(5000, 1));
238   }
239 }
240