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 package com.android.cellbroadcastreceiver.unit;
17 
18 import static androidx.test.espresso.Espresso.onView;
19 import static androidx.test.espresso.action.ViewActions.click;
20 import static androidx.test.espresso.matcher.ViewMatchers.withText;
21 
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.Matchers.any;
24 import static org.mockito.Matchers.anyInt;
25 import static org.mockito.Matchers.anyString;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.eq;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 
33 import android.content.BroadcastReceiver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.SharedPreferences;
37 import android.content.res.Configuration;
38 import android.content.res.Resources;
39 import android.os.Looper;
40 import android.os.RemoteException;
41 import android.os.UserManager;
42 import android.telephony.SubscriptionInfo;
43 import android.telephony.SubscriptionManager;
44 
45 import androidx.preference.Preference;
46 import androidx.preference.PreferenceManager;
47 import androidx.preference.TwoStatePreference;
48 import androidx.test.InstrumentationRegistry;
49 import androidx.test.filters.FlakyTest;
50 import androidx.test.uiautomator.UiDevice;
51 
52 import com.android.cellbroadcastreceiver.CellBroadcastChannelManager;
53 import com.android.cellbroadcastreceiver.CellBroadcastConfigService;
54 import com.android.cellbroadcastreceiver.CellBroadcastSettings;
55 import com.android.cellbroadcastreceiver.R;
56 import com.android.modules.utils.build.SdkLevel;
57 
58 import junit.framework.Assert;
59 
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.mockito.ArgumentCaptor;
64 import org.mockito.Captor;
65 import org.mockito.Mock;
66 import org.mockito.MockitoAnnotations;
67 
68 import java.lang.reflect.Field;
69 import java.util.Locale;
70 
71 
72 public class CellBroadcastSettingsTest extends
73         CellBroadcastActivityTestCase<CellBroadcastSettings> {
74 
75     private UiDevice mDevice;
76     private static final long DEVICE_WAIT_TIME = 1000L;
77     private static final String ROAMING_OPERATOR_SUPPORTED = "roaming_operator_supported";
78     private static final String ACTION_TESTING_MODE_CHANGED =
79             "com.android.cellbroadcastreceiver.intent.ACTION_TESTING_MODE_CHANGED";
80     private static final String TESTING_MODE = "testing_mode";
81     private static final String MASTER_TOGGLE_ENABLED = "enable_alerts_master_toggle";
82     private static final int PREFERENCE_PUT_TYPE_BOOL = 0;
83     private static final int PREFERENCE_PUT_TYPE_STRING = 1;
84     private static final long TEST_TIMEOUT_MILLIS = 1000L;
85 
86     @Captor
87     private ArgumentCaptor<Intent> mIntent;
88     @Mock
89     private UserManager mUserManager;
90     @Mock
91     private SharedPreferences mMockedSharedPreference;
92     @Mock
93     private SharedPreferences.Editor mEditor;
94 
95     FakeSharedPreferences mFakeSharedPreferences = new FakeSharedPreferences();
96 
CellBroadcastSettingsTest()97     public CellBroadcastSettingsTest() {
98         super(CellBroadcastSettings.class);
99     }
100 
101     @Before
setUp()102     public void setUp() throws Exception {
103         super.setUp();
104         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
105         MockitoAnnotations.initMocks(this);
106         CellBroadcastSettings.resetResourcesCache();
107     }
108 
109     @After
tearDown()110     public void tearDown() throws Exception {
111         CellBroadcastSettings.resetResourcesCache();
112         CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
113         super.tearDown();
114     }
115 
116     @InstrumentationTest
117     // This test has a module dependency, so it is disabled for OEM testing because it is not a true
118     // unit test
119     @FlakyTest
120     @Test
testRotateAlertReminderDialogOpen()121     public void testRotateAlertReminderDialogOpen() throws InterruptedException {
122         try {
123             mDevice.wakeUp();
124             mDevice.pressMenu();
125         } catch (RemoteException exception) {
126             Assert.fail("Exception " + exception);
127         }
128 
129         InstrumentationRegistry.getInstrumentation().startActivitySync(createActivityIntent());
130         int w = mDevice.getDisplayWidth();
131         int h = mDevice.getDisplayHeight();
132 
133         waitUntilDialogOpens(()-> {
134             mDevice.swipe(w / 2 /* start X */,
135                     h / 2 /* start Y */,
136                     w / 2 /* end X */,
137                     0 /* end Y */,
138                     100 /* steps */);
139 
140             openAlertReminderDialog();
141         }, DEVICE_WAIT_TIME);
142 
143         try {
144             mDevice.setOrientationLeft();
145             mDevice.setOrientationNatural();
146             mDevice.setOrientationRight();
147         } catch (Exception e) {
148             Assert.fail("Exception " + e);
149         }
150     }
151 
152     @Test
testResetAllPreferences()153     public void testResetAllPreferences() throws Throwable {
154         Looper.prepare();
155         mContext.injectSharedPreferences(mFakeSharedPreferences);
156         // set a few preferences so we can verify they are reset to the default
157         PreferenceManager.getDefaultSharedPreferences(mContext).edit()
158                 .putBoolean(CellBroadcastSettings.KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE, true)
159                 .putBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_VIBRATE, false).apply();
160         assertTrue("receive_cmas_in_second_language was not set to true",
161                 PreferenceManager.getDefaultSharedPreferences(mContext)
162                         .getBoolean(CellBroadcastSettings.KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE,
163                                 false));
164         assertFalse("enable_alert_vibrate was not set to false",
165                 PreferenceManager.getDefaultSharedPreferences(mContext)
166                         .getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_VIBRATE, true));
167 
168         // see preferences.xml for default values.
169         // receive_cmas_in_second_language is false by default
170         // enable_alert_vibrate is true by default
171         CellBroadcastSettings.resetAllPreferences(mContext);
172 
173         assertFalse("receive_cmas_in_second_language was not reset to the default (false)",
174                 PreferenceManager.getDefaultSharedPreferences(mContext)
175                 .getBoolean(CellBroadcastSettings.KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE, true));
176         assertTrue("enable_alert_vibrate was not reset to the default (true)",
177                 PreferenceManager.getDefaultSharedPreferences(mContext)
178                 .getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_VIBRATE, false));
179     }
180 
181     @Test
testHasAnyPreferenceChanged()182     public void testHasAnyPreferenceChanged() {
183         mContext.injectSharedPreferences(mFakeSharedPreferences);
184         assertFalse(CellBroadcastSettings.hasAnyPreferenceChanged(mContext));
185         PreferenceManager.getDefaultSharedPreferences(mContext).edit()
186                 .putBoolean("any_preference_changed_by_user", true).apply();
187         assertTrue(CellBroadcastSettings.hasAnyPreferenceChanged(mContext));
188     }
189 
190     @Test
testPreferenceChangeByUser()191     public void testPreferenceChangeByUser() {
192         Context mockContext = mock(Context.class);
193         Looper.prepare();
194         CellBroadcastSettings.CellBroadcastSettingsFragment fragment =
195                 new CellBroadcastSettings.CellBroadcastSettingsFragment();
196         doReturn(mUserManager).when(mockContext).getSystemService(Context.USER_SERVICE);
197         doReturn(true).when(mUserManager).isSystemUser();
198         doReturn(mMockedSharedPreference).when(mockContext).getSharedPreferences(anyString(),
199                 anyInt());
200         doReturn(mEditor).when(mMockedSharedPreference).edit();
201         doReturn(mEditor).when(mEditor).putBoolean(anyString(), anyBoolean());
202 
203         fragment.onPreferenceChangedByUser(mockContext);
204 
205         verify(mockContext, times(1)).startService(mIntent.capture());
206         assertEquals(CellBroadcastConfigService.ACTION_ENABLE_CHANNELS,
207                 (String) mIntent.getValue().getAction());
208     }
209 
210     @Test
testGetResources()211     public void testGetResources() {
212         Context mockContext = mock(Context.class);
213         Resources mockResources = mock(Resources.class);
214         Configuration configuration = new Configuration();
215         doReturn(mockResources).when(mockContext).getResources();
216         doReturn(configuration).when(mockResources).getConfiguration();
217 
218         CellBroadcastSettings.getResources(
219                 mockContext, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
220         verify(mockContext, never()).getSystemService(anyString());
221         verify(mockContext, times(1)).getResources();
222 
223         CellBroadcastSettings.getResources(
224                 mockContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
225         verify(mockContext, never()).getSystemService(anyString());
226         verify(mockContext, times(2)).getResources();
227 
228         Context mockContext2 = mock(Context.class);
229         doReturn(mockResources).when(mockContext2).getResources();
230         SubscriptionManager mockSubManager = mock(SubscriptionManager.class);
231         doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE).when(mockContext)
232                 .getSystemServiceName(eq(SubscriptionManager.class));
233         doReturn(mockSubManager).when(mockContext).getSystemService(
234                 eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE));
235         SubscriptionInfo mockSubInfo = mock(SubscriptionInfo.class);
236         doReturn(mockSubInfo).when(mockSubManager).getActiveSubscriptionInfo(anyInt());
237         doReturn(0).when(mockSubInfo).getMcc();
238         doReturn(0).when(mockSubInfo).getMnc();
239         doReturn(mockContext2).when(mockContext).createConfigurationContext(any());
240 
241         // The resource will not be cached for the sub
242         CellBroadcastSettings.getResources(
243                 mockContext, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID - 1);
244 
245         verify(mockContext, times(1)).createConfigurationContext(any());
246         verify(mockContext2, times(1)).getResources();
247 
248         // The resources will be cached for ths sub
249         doReturn(123).when(mockSubInfo).getMcc();
250         doReturn(456).when(mockSubInfo).getMnc();
251         // The cache logic is updated on S
252         final int timesExpected = SdkLevel.isAtLeastS() ? 2 : 1;
253 
254         CellBroadcastSettings.getResources(
255                 mockContext, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID - 1);
256 
257         verify(mockContext, times(timesExpected)).createConfigurationContext(any());
258         verify(mockContext2, times(timesExpected)).getResources();
259 
260         // The resources should be read from the cached directly
261         CellBroadcastSettings.getResources(
262                 mockContext, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID - 1);
263 
264         verify(mockContext, times(timesExpected)).createConfigurationContext(any());
265         verify(mockContext2, times(timesExpected)).getResources();
266 
267         Configuration configuration2 = new Configuration();
268         configuration2.setLocale(Locale.ROOT);
269         doReturn(configuration2).when(mockResources).getConfiguration();
270         CellBroadcastSettings.getResources(
271                 mockContext, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID - 2);
272 
273         verify(mockContext, times(timesExpected + 1)).createConfigurationContext(any());
274         verify(mockContext2, times(timesExpected + 1)).getResources();
275     }
276 
277     @Test
testGetResourcesByOperator()278     public void testGetResourcesByOperator() {
279         Context mockContext = mock(Context.class);
280         Resources mockResources = mock(Resources.class);
281         doReturn(mockResources).when(mockContext).getResources();
282 
283         CellBroadcastSettings.getResourcesByOperator(mockContext,
284                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "");
285         verify(mockContext, never()).createConfigurationContext(any());
286         verify(mockContext, times(1)).getResources();
287 
288         int mcc = 123;
289         int mnc = 456;
290         Context mockContext2 = mock(Context.class);
291         ArgumentCaptor<Configuration> captorConfig = ArgumentCaptor.forClass(Configuration.class);
292         doReturn(mockResources).when(mockContext2).getResources();
293         doReturn(mockContext2).when(mockContext).createConfigurationContext(any());
294 
295         CellBroadcastSettings.getResourcesByOperator(mockContext,
296                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
297                 Integer.toString(mcc) + Integer.toString(mnc));
298         verify(mockContext, times(1)).getResources();
299         verify(mockContext2, times(1)).getResources();
300         verify(mockContext, times(1)).createConfigurationContext(captorConfig.capture());
301         assertEquals(mcc, captorConfig.getValue().mcc);
302         assertEquals(mnc, captorConfig.getValue().mnc);
303     }
304 
waitUntilDialogOpens(Runnable r, long maxWaitMs)305     public void waitUntilDialogOpens(Runnable r, long maxWaitMs) {
306         long waitTime = 0;
307         while (waitTime < maxWaitMs) {
308             try {
309                 r.run();
310                 // if the assert succeeds, return
311                 return;
312             } catch (Exception e) {
313                 waitTime += 100;
314                 waitForMs(100);
315             }
316         }
317         // if timed out, run one last time without catching exception
318         r.run();
319     }
320 
321     @Override
createActivityIntent()322     protected Intent createActivityIntent() {
323         Intent intent = new Intent(mContext, CellBroadcastSettings.class);
324         intent.setPackage("com.android.cellbroadcastreceiver");
325         intent.setAction("android.intent.action.MAIN");
326         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
327         return intent;
328     }
329 
openAlertReminderDialog()330     private void openAlertReminderDialog() {
331         onView(withText(mContext.getString(com.android.cellbroadcastreceiver.R
332                 .string.alert_reminder_interval_title))).perform(click());
333     }
334 
335     @Test
testDisabledExtremeToggle()336     public void testDisabledExtremeToggle() throws Throwable {
337         SubscriptionManager mockSubManager = mock(SubscriptionManager.class);
338         injectSystemService(SubscriptionManager.class, mockSubManager);
339         SubscriptionInfo mockSubInfo = mock(SubscriptionInfo.class);
340         doReturn(mockSubInfo).when(mockSubManager).getActiveSubscriptionInfo(anyInt());
341 
342         setPreference(PREFERENCE_PUT_TYPE_BOOL, MASTER_TOGGLE_ENABLED, "true");
343         doReturn(true).when(mContext.getResources()).getBoolean(
344                 R.bool.extreme_threat_alerts_enabled_default);
345         doReturn(false).when(mContext.getResources()).getBoolean(
346                 R.bool.disable_extreme_alert_settings);
347 
348         CellBroadcastSettings cellBroadcastSettingActivity = startActivity();
349 
350         TwoStatePreference extremeCheckBox =
351                 cellBroadcastSettingActivity.mCellBroadcastSettingsFragment.findPreference(
352                         CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS);
353 
354         assertTrue(extremeCheckBox.isEnabled());
355 
356         stopActivity();
357         waitForMs(100);
358 
359         doReturn(true).when(mContext.getResources()).getBoolean(
360                 R.bool.disable_extreme_alert_settings);
361 
362         cellBroadcastSettingActivity.mCellBroadcastSettingsFragment
363                 .initAlertsToggleDisabledAsNeeded();
364         cellBroadcastSettingActivity.mCellBroadcastSettingsFragment.onResume();
365 
366         assertFalse(extremeCheckBox.isEnabled());
367     }
368 
369     @Test
testTopIntroductionForRoamingSupport()370     public void testTopIntroductionForRoamingSupport() throws Throwable {
371         String topIntroRoamingText = "test";
372         doReturn(topIntroRoamingText).when(mContext.getResources()).getString(
373                 eq(R.string.top_intro_roaming_text));
374         setSubscriptionManager();
375         setPreference(PREFERENCE_PUT_TYPE_STRING, ROAMING_OPERATOR_SUPPORTED, "XXX");
376 
377         CellBroadcastSettings settings = startActivity();
378 
379         Preference topIntroPreference = settings.mCellBroadcastSettingsFragment.findPreference(
380                 CellBroadcastSettings.KEY_PREFS_TOP_INTRO);
381         assertEquals(topIntroRoamingText, topIntroPreference.getTitle().toString());
382     }
383 
384     @Test
testDoNotShowTestCheckBox()385     public void testDoNotShowTestCheckBox() throws Throwable {
386         setSubscriptionManager();
387         setPreference(PREFERENCE_PUT_TYPE_BOOL, TESTING_MODE, "false");
388         doReturn(false).when(mContext.getResources()).getBoolean(
389                 eq(R.bool.show_separate_exercise_settings));
390         doReturn(false).when(mContext.getResources()).getBoolean(
391                 eq(R.bool.show_separate_operator_defined_settings));
392         doReturn(new String[]{"0x111D:rat=gsm, emergency=true"}).when(mContext.getResources())
393                 .getStringArray(eq(R.array.exercise_alert_range_strings));
394         doReturn(new String[]{"0x111E:rat=gsm, emergency=true"}).when(mContext.getResources())
395                 .getStringArray(eq(R.array.operator_defined_alert_range_strings));
396         CellBroadcastSettings settings = startActivity();
397 
398         TwoStatePreference exerciseTestCheckBox =
399                 settings.mCellBroadcastSettingsFragment.findPreference(
400                         CellBroadcastSettings.KEY_ENABLE_EXERCISE_ALERTS);
401         TwoStatePreference operatorDefinedCheckBox =
402                 settings.mCellBroadcastSettingsFragment.findPreference(
403                         CellBroadcastSettings.KEY_OPERATOR_DEFINED_ALERTS);
404 
405         // received the ACTION_TESTING_MODE_CHANGED, do not show exerciseTestCheckBox &
406         // operatorDefinedCheckBox
407         Field fieldTestingModeChangedReceiver =
408                 CellBroadcastSettings.CellBroadcastSettingsFragment.class.getDeclaredField(
409                         "mTestingModeChangedReceiver");
410         fieldTestingModeChangedReceiver.setAccessible(true);
411         BroadcastReceiver broadcastReceiver =
412                 (BroadcastReceiver) fieldTestingModeChangedReceiver.get(
413                         settings.mCellBroadcastSettingsFragment);
414         broadcastReceiver.onReceive(mContext, new Intent().setAction(ACTION_TESTING_MODE_CHANGED));
415 
416         assertFalse(exerciseTestCheckBox.isVisible());
417         assertFalse(operatorDefinedCheckBox.isVisible());
418     }
419 
420     @Test
testShowTestCheckBox()421     public void testShowTestCheckBox() throws Throwable {
422         setSubscriptionManager();
423         setPreference(PREFERENCE_PUT_TYPE_BOOL, TESTING_MODE, "true");
424         doReturn(true).when(mContext.getResources()).getBoolean(
425                 eq(R.bool.show_separate_exercise_settings));
426         doReturn(true).when(mContext.getResources()).getBoolean(
427                 eq(R.bool.show_separate_operator_defined_settings));
428         doReturn(new String[]{"0x111D:rat=gsm, emergency=true"}).when(mContext.getResources())
429                 .getStringArray(eq(R.array.exercise_alert_range_strings));
430         doReturn(new String[]{"0x111E:rat=gsm, emergency=true"}).when(mContext.getResources())
431                 .getStringArray(eq(R.array.operator_defined_alert_range_strings));
432         CellBroadcastSettings settings = startActivity();
433 
434         TwoStatePreference exerciseTestCheckBox =
435                 settings.mCellBroadcastSettingsFragment.findPreference(
436                         CellBroadcastSettings.KEY_ENABLE_EXERCISE_ALERTS);
437         TwoStatePreference operatorDefinedCheckBox =
438                 settings.mCellBroadcastSettingsFragment.findPreference(
439                         CellBroadcastSettings.KEY_OPERATOR_DEFINED_ALERTS);
440 
441         // received the ACTION_TESTING_MODE_CHANGED, show exerciseTestCheckBox &
442         // operatorDefinedCheckBox
443         Field fieldTestingModeChangedReceiver =
444                 CellBroadcastSettings.CellBroadcastSettingsFragment.class.getDeclaredField(
445                         "mTestingModeChangedReceiver");
446         fieldTestingModeChangedReceiver.setAccessible(true);
447         BroadcastReceiver broadcastReceiver =
448                 (BroadcastReceiver) fieldTestingModeChangedReceiver.get(
449                         settings.mCellBroadcastSettingsFragment);
450         broadcastReceiver.onReceive(mContext, new Intent().setAction(ACTION_TESTING_MODE_CHANGED));
451 
452         waitForChange(() -> exerciseTestCheckBox.isVisible(), TEST_TIMEOUT_MILLIS);
453         assertTrue(exerciseTestCheckBox.isVisible());
454         waitForChange(() -> operatorDefinedCheckBox.isVisible(), TEST_TIMEOUT_MILLIS);
455         assertTrue(operatorDefinedCheckBox.isVisible());
456     }
457 
setSubscriptionManager()458     private void setSubscriptionManager() {
459         SubscriptionManager mockSubManager = mock(SubscriptionManager.class);
460         injectSystemService(SubscriptionManager.class, mockSubManager);
461         SubscriptionInfo mockSubInfo = mock(SubscriptionInfo.class);
462         doReturn(mockSubInfo).when(mockSubManager).getActiveSubscriptionInfo(anyInt());
463     }
464 
setPreference(int putType, String key, String value)465     private void setPreference(int putType, String key, String value) {
466         mContext.injectSharedPreferences(mFakeSharedPreferences);
467         switch (putType) {
468             case PREFERENCE_PUT_TYPE_BOOL:
469                 PreferenceManager.getDefaultSharedPreferences(mContext).edit()
470                         .putBoolean(key, Boolean.valueOf(value)).apply();
471                 break;
472             case PREFERENCE_PUT_TYPE_STRING:
473                 PreferenceManager.getDefaultSharedPreferences(mContext).edit()
474                         .putString(key, value).apply();
475                 break;
476         }
477     }
478 }
479