1 /*
2  * Copyright (C) 2022 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.cellbroadcastreceiver.compliancetests;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.app.KeyguardManager;
24 import android.app.LocaleManager;
25 import android.app.UiAutomation;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.net.Uri;
31 import android.os.LocaleList;
32 import android.provider.Settings;
33 import android.provider.Telephony;
34 import android.support.test.uiautomator.By;
35 import android.support.test.uiautomator.BySelector;
36 import android.support.test.uiautomator.UiObject;
37 import android.support.test.uiautomator.UiObject2;
38 import android.support.test.uiautomator.UiObjectNotFoundException;
39 import android.support.test.uiautomator.UiScrollable;
40 import android.support.test.uiautomator.UiSelector;
41 import android.support.test.uiautomator.Until;
42 import android.text.TextUtils;
43 import android.widget.LinearLayout;
44 
45 import com.android.internal.util.HexDump;
46 
47 import junitparams.JUnitParamsRunner;
48 import junitparams.Parameters;
49 
50 import org.json.JSONObject;
51 import org.junit.After;
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 
56 import java.util.Iterator;
57 
58 @RunWith(JUnitParamsRunner.class)
59 public class CellBroadcastUiTest extends CellBroadcastBaseTest {
60     private static final String TAG = "CellBroadcastUiTest";
61     private static final int UI_TIMEOUT = 10000;
62     private static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
63     private static final String SELECT_BY_SERIAL_NUMBER =
64             Telephony.CellBroadcasts.SERIAL_NUMBER + "=?";
65     private static int sSerialId = 0;
66     /** Bitmask for messages of ETWS type (including future extensions). */
67     private static final int MESSAGE_ID_ETWS_TYPE_MASK = 0xFFF8;
68     /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
69     private static final int MESSAGE_ID_ETWS_TYPE = 0x1100; // 4352
70     private static final String CELL_BROADCAST_LIST_ACTIVITY =
71             "com.android.cellbroadcastreceiver.CellBroadcastSettings";
72     private static final BySelector SYSUI_FULL_SCREEN_DIALOG =
73             By.res("com.android.systemui", "immersive_cling_title");
74     private static final BySelector SYSUI_CLOSE_BUTTON =
75             By.res("com.android.systemui", "ok");
76     private static final BySelector FULL_SCREEN_DIALOG =
77             By.res("android:id/immersive_cling_title");
78     private static final BySelector CLOSE_BUTTON =
79             By.res("android:id/ok");
80 
81     private static final BySelector YES_BUTTON =
82             By.res("android:id/button1");
83 
84     private boolean mIsOptOutDialogHandled = false;
85 
86     @Before
beforeTest()87     public void beforeTest() throws Exception {
88         super.beforeTest();
89 
90         if ("testEmergencyAlertSettingsUi".equals(mTestNameRule.getMethodName())
91                 || "testAlertUiOnReceivedAlert".equals(mTestNameRule.getMethodName())) {
92             KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class);
93             assumeTrue("cannot test under secure keyguard",
94                     keyguardManager != null && !keyguardManager.isKeyguardSecure());
95             // dismiss keyguard and wait from idle
96             if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
97                 dismissKeyGuard();
98             }
99         }
100         if ("testAlertUiOnReceivedAlert".equals(mTestNameRule.getMethodName())) {
101             PackageManager pm = getContext().getPackageManager();
102             assumeTrue("FULL_ACCESS_CELL_BROADCAST_HISTORY permission "
103                     + "is necessary for this test", pm.checkPermission(
104                     "com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY",
105                     "com.android.shell") != PackageManager.PERMISSION_DENIED);
106         }
107         if ("testEmergencyAlertSettingsUi".equals(mTestNameRule.getMethodName())
108                 || "testAlertUiOnReceivedAlert".equals(mTestNameRule.getMethodName())) {
109             // close disturbing dialog if exist
110             UiObject2 yesButton = sDevice.wait(Until.findObject(YES_BUTTON), 100);
111             if (yesButton != null) {
112                 logd("dismiss disturbing dialog");
113                 yesButton.click();
114             }
115             // if left alertdialog exist, close it
116             UiObject2 okItem = sDevice.wait(Until.findObject(
117                     By.res(sPackageName, "dismissButton")), 100);
118             if (okItem != null) {
119                 logd("dismiss left alertdialog");
120                 okItem.click();
121             }
122             sDevice.pressHome();
123         }
124     }
125 
126     @After
afterTest()127     public void afterTest() {
128         if (sPreconditionError != 0) {
129             return;
130         }
131 
132         if ("testAlertUiOnReceivedAlert".equals(mTestNameRule.getMethodName())
133                 && (sSerialId > 0)) {
134             deleteMessageWithShellPermissionIdentity();
135 
136             if (!mIsOptOutDialogHandled) {
137                 UiObject2 yesButton = sDevice.wait(Until.findObject(YES_BUTTON), 1000);
138                 if (yesButton != null) {
139                     logd("yesButton click");
140                     yesButton.click();
141                     mIsOptOutDialogHandled = true;
142                 }
143             }
144         }
145 
146         if ("testEmergencyAlertSettingsUi".equals(mTestNameRule.getMethodName())) {
147             sDevice.pressBack();
148             sDevice.pressHome();
149         }
150 
151         if ("testEmergencyAlertSettingsUi".equals(mTestNameRule.getMethodName())
152                 || "testAlertUiOnReceivedAlert".equals(mTestNameRule.getMethodName())) {
153             LocaleManager localeManager = getContext().getSystemService(LocaleManager.class);
154             localeManager.setApplicationLocales(sPackageName, LocaleList.getEmptyLocaleList());
155         }
156     }
157 
158     @Test
159     @Parameters(method = "paramsCarrierAndChannelForTest")
testAlertUiOnReceivedAlert(String carrierName, String channel)160     public void testAlertUiOnReceivedAlert(String carrierName, String channel) throws Throwable {
161         logd("CellBroadcastUiTest#testAlertUiOnReceivedAlert");
162         CellBroadcastCarrierTestConfig carrierInfo =
163                 new CellBroadcastCarrierTestConfig(sCarriersObject, carrierName);
164         CellBroadcastChannelTestConfig channelInfo =
165                 new CellBroadcastChannelTestConfig(sChannelsObject, carrierName, channel);
166         // setup mccmnc
167         if (sInputMccMnc == null || (sInputMccMnc != null
168                 && !sInputMccMnc.equals(carrierInfo.mMccMnc))) {
169             setSimInfo(carrierName, carrierInfo.mMccMnc);
170         }
171 
172         // change language of CBR
173         changeLocale(carrierInfo, sPackageName, true);
174 
175         if (!channelInfo.mChannelDefaultValue || TextUtils.isEmpty(channelInfo.mExpectedTitle)
176                 || channelInfo.mFilteredLanguageBySecondLanguagePref
177                 || channelInfo.mIsEnabledOnTestMode
178                 || !channelInfo.mNeedDisplay) {
179             // let's skip for alerttitle
180             return;
181         }
182         boolean isMessageEnglish = !channelInfo.mIgnoreMessageByLanguageFilter
183                 || (channelInfo.mIgnoreMessageByLanguageFilter && carrierInfo.mLanguageTag != null
184                 && !carrierInfo.mLanguageTag.equals("en"));
185 
186         // receive broadcast message
187         receiveBroadcastMessage(channel, channelInfo.mWarningType, isMessageEnglish);
188 
189         logd("carrier " + carrierName + ", expectedTitle = "
190                 + channelInfo.mExpectedTitle + " for channel " + channel
191                 + ", alertTypeIsNotification = " + channelInfo.mAlertTypeIsNotification);
192         if (channelInfo.mAlertTypeIsNotification) {
193             verifyNotificationPosted(carrierName, channelInfo.mExpectedTitle, channel,
194                     sPackageName, channelInfo.mIgnoreMessageByLanguageFilter);
195         } else {
196             verifyAlertDialogTitle(carrierName, channelInfo.mExpectedTitle, channel,
197                     carrierInfo.mDisableNavigation, carrierInfo.mNeedFullScreen, sPackageName,
198                     channelInfo.mIgnoreMessageByLanguageFilter);
199         }
200     }
201 
receiveBroadcastMessage(String channelName, String warningType, boolean isMessageEnglish)202     public void receiveBroadcastMessage(String channelName, String warningType,
203             boolean isMessageEnglish) {
204         int channel = Integer.parseInt(channelName);
205         String hexChannel = String.format("%04X", channel);
206 
207         sSerialId++;
208         String serialHexString = String.format("%04X", sSerialId);
209         logd("receiveBroadcastMessage, channel = " + hexChannel
210                 + ", serialIdHexString = " + serialHexString);
211 
212         boolean isEtws = (channel & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE;
213         String langCode = isMessageEnglish ? "01" : "00"; // 01 is english, 00 is german
214         String etwsWarningType = TextUtils.isEmpty(warningType) ? "00" : warningType;
215         String pduCode = isEtws ? etwsWarningType : langCode;
216         String hexString = serialHexString + hexChannel + pduCode + "11D4F29C0E0AB2CB727A08";
217         byte[] data = HexDump.hexStringToByteArray(hexString);
218 
219         sMockModemManager.newBroadcastSms(sSlotId, data);
220     }
221 
verifyAlertDialogTitle(String carrier, String title, String channel, boolean disableNavigation, boolean needsFullScreen, String packageName, boolean ignoreMessageByLanguageFilter)222     private void verifyAlertDialogTitle(String carrier, String title, String channel,
223             boolean disableNavigation, boolean needsFullScreen, String packageName,
224             boolean ignoreMessageByLanguageFilter) {
225         boolean expectedResult = ignoreMessageByLanguageFilter ? false : true;
226         if (needsFullScreen && !isConfirmedForFullScreenGuide(getContext())) {
227             checkForFullScreenGuide();
228         }
229         boolean result = false;
230         String outputTitle = null;
231         UiObject2 item = sDevice.wait(Until.findObject(By.res(packageName, "alertTitle")),
232                 UI_TIMEOUT);
233         if (item != null) {
234             outputTitle = item.getText();
235             if (outputTitle != null && outputTitle.startsWith(title)) {
236                 result = true;
237             }
238             if (disableNavigation) {
239                 // for certain country like chile, check if system navigation bar is disabled
240                 sDevice.openNotification();
241                 UiSelector notificationStackScroller = new UiSelector()
242                         .packageName("com.android.systemui")
243                         .resourceId("com.android.systemui:id/notification_stack_scroller");
244                 UiObject widget = new UiObject(notificationStackScroller);
245                 boolean canOpenNotification = widget.waitForExists(3000);
246                 assertEquals("carrier=" + carrier + ", channel=" + channel
247                         + ", system ui should be disabled. ", canOpenNotification, false);
248             }
249             // dismiss dialog
250             UiObject2 okItem = sDevice.wait(Until.findObject(
251                     By.res(packageName, "dismissButton")), UI_TIMEOUT);
252             if (okItem != null) {
253                 okItem.click();
254             }
255         }
256         assertEquals("carrier=" + carrier + ", channel=" + channel
257                 + ", output title=" + outputTitle
258                 + ", expected title=" + title, expectedResult, result);
259     }
260 
261     /** Pulls down notification shade and verifies that message text is found. */
verifyNotificationPosted(String carrier, String title, String channel, String packageName, boolean ignoreMessageByLanguageFilter)262     private void verifyNotificationPosted(String carrier, String title, String channel,
263             String packageName, boolean ignoreMessageByLanguageFilter)
264             throws UiObjectNotFoundException {
265         boolean expectedResult = ignoreMessageByLanguageFilter ? false : true;
266 
267         // open notification shade
268         sDevice.openNotification();
269         boolean hasObject = sDevice.wait(Until.hasObject(By.text(title)), UI_TIMEOUT);
270         if (hasObject) {
271             dismissNotificationAsNeeded(title, packageName);
272         }
273         assertEquals("carrier=" + carrier + ", channel=" + channel
274                 + ", expected title=" + title, expectedResult, hasObject);
275     }
276 
dismissNotificationAsNeeded(String title, String packageName)277     private void dismissNotificationAsNeeded(String title, String packageName)
278             throws UiObjectNotFoundException {
279         UiSelector notificationStackScroller = new UiSelector()
280                 .packageName("com.android.systemui")
281                 .resourceId("com.android.systemui:id/notification_stack_scroller");
282         UiObject object = sDevice.findObject(notificationStackScroller);
283         UiObject object2 = object.getChild(new UiSelector().textContains(title));
284         if (object2 != null) {
285             object2.click();
286             UiObject2 okItem = sDevice.wait(Until.findObject(
287                     By.res(packageName, "dismissButton")), UI_TIMEOUT);
288             if (okItem != null) {
289                 okItem.click();
290             }
291         }
292     }
293 
294     @Test
295     @Parameters(method = "paramsForTest")
testEmergencyAlertSettingsUi(String carrierName)296     public void testEmergencyAlertSettingsUi(String carrierName) throws Throwable {
297         logd("CellBroadcastUiTest#testEmergencyAlertSettingsUi");
298         CellBroadcastCarrierTestConfig carrierInfo =
299                 new CellBroadcastCarrierTestConfig(sCarriersObject, carrierName);
300         // setup mccmnc
301         if (sInputMccMnc == null || (sInputMccMnc != null
302                 && !sInputMccMnc.equals(carrierInfo.mMccMnc))) {
303             setSimInfo(carrierName, carrierInfo.mMccMnc);
304         }
305 
306         // change language of CBR
307         changeLocale(carrierInfo, sPackageName, false);
308 
309         // launch setting activity of CBR
310         Intent intent = new Intent(Intent.ACTION_MAIN);
311         intent.setComponent(new ComponentName(sPackageName, CELL_BROADCAST_LIST_ACTIVITY));
312         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
313         getContext().startActivity(intent);
314 
315         UiScrollable listView = new UiScrollable(new UiSelector().resourceId("android:id"
316                 + "/list_container"));
317         listView.waitForExists(2000);
318 
319         // check if there is each setting menu
320         JSONObject settingsForCarrier = sSettingsObject.getJSONObject(carrierName);
321         for (Iterator<String> iterator = settingsForCarrier.keys(); iterator.hasNext(); ) {
322             String settingName = iterator.next();
323             CellBroadcastSettingTestConfig settingInfo =
324                     new CellBroadcastSettingTestConfig(settingsForCarrier, settingName);
325             if (!settingInfo.mIsToggleAvailability) {
326                 continue;
327             }
328             UiObject item = null;
329             try {
330                 item = listView.getChildByText(new UiSelector()
331                         .className(LinearLayout.class.getName()), settingName, true);
332             } catch (UiObjectNotFoundException e) {
333                 logd("let's scrollforward if it cannot find switch");
334                 listView.scrollForward();
335                 try {
336                     item = listView.getChildByText(new UiSelector()
337                                     .className(LinearLayout.class.getName()),
338                             settingName, true);
339                 } catch (UiObjectNotFoundException e2) {
340                     assertTrue("carrier=" + carrierName + ", settingName="
341                             + settingName, false);
342                 }
343             }
344             UiObject itemSwitch = null;
345             try {
346                 itemSwitch = item.getChild(new UiSelector()
347                         .className(android.widget.Switch.class.getName()));
348                 logd("itemSwitch = " + itemSwitch.isChecked());
349             } catch (UiObjectNotFoundException e) {
350                 logd("switch not found, let's find again");
351                 String searchText = settingName;
352                 if (settingInfo.mSummary != null) {
353                     searchText = settingInfo.mSummary;
354                 } else {
355                     // let's scrollforward if it cannot find switch
356                     listView.scrollForward();
357                 }
358                 item = listView.getChildByText(new UiSelector()
359                         .className(LinearLayout.class.getName()), searchText, true);
360                 itemSwitch = item.getChild(new UiSelector()
361                         .className(android.widget.Switch.class.getName()));
362             }
363             assertEquals("carrierName=" + carrierName + ", settingName=" + settingName
364                     + ", expectedSwitchValue=" + settingInfo.mIsToggleAvailability,
365                     settingInfo.mExpectedSwitchValue, itemSwitch.isChecked());
366         }
367     }
368 
dismissKeyGuard()369     private void dismissKeyGuard() throws Exception {
370         logd("dismissKeyGuard");
371         sDevice.wakeUp();
372         sDevice.executeShellCommand("wm dismiss-keyguard");
373     }
374 
checkForFullScreenGuide()375     private boolean checkForFullScreenGuide() {
376         logd("checkForFullScreenGuide");
377         UiObject2 viewObject = sDevice.wait(Until.findObject(FULL_SCREEN_DIALOG),
378                 UI_TIMEOUT);
379         if (viewObject != null) {
380             return dismissFullScreenGuide(CLOSE_BUTTON);
381         } else {
382             logd("check systemui's fullscreen guide");
383             viewObject = sDevice.wait(Until.findObject(SYSUI_FULL_SCREEN_DIALOG), UI_TIMEOUT);
384             if (viewObject != null) {
385                 return dismissFullScreenGuide(SYSUI_CLOSE_BUTTON);
386             } else {
387                 logd("failed to find fullscreen guide");
388                 return false;
389             }
390         }
391     }
392 
dismissFullScreenGuide(BySelector closeButton)393     private boolean dismissFullScreenGuide(BySelector closeButton) {
394         logd("Found full screen dialog, dismissing.");
395         UiObject2 okButton = sDevice.wait(Until.findObject(closeButton), UI_TIMEOUT);
396         if (okButton != null) {
397             okButton.click();
398             return true;
399         } else {
400             logd("Unable to dismiss full screen dialog");
401         }
402         return false;
403     }
404 
isConfirmedForFullScreenGuide(Context context)405     private boolean isConfirmedForFullScreenGuide(Context context) {
406         logd("isConfirmedForFullScreenGuide");
407         String value = null;
408         boolean isConfirmed = false;
409         try {
410             value = Settings.Secure.getString(context.getContentResolver(),
411                     Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS);
412             isConfirmed = "confirmed".equals(value);
413             logd("Loaded isConfirmed = " + isConfirmed);
414         } catch (Throwable t) {
415             logd("Error loading confirmations, value=" + value);
416         }
417         return isConfirmed;
418     }
419 
changeLocale(CellBroadcastCarrierTestConfig info, String packageName, boolean checkAlertUi)420     private void changeLocale(CellBroadcastCarrierTestConfig info,
421             String packageName, boolean checkAlertUi) {
422         LocaleManager localeManager = getContext().getSystemService(LocaleManager.class);
423         if (info.mLanguageTag != null && (checkAlertUi || info.mCheckSettingWithMainLanguage)) {
424             logd("setApplicationLocales " + info.mLanguageTag);
425             localeManager.setApplicationLocales(packageName,
426                     LocaleList.forLanguageTags(info.mLanguageTag));
427         } else {
428             logd("setApplicationLocales to default");
429             localeManager.setApplicationLocales(packageName,
430                     LocaleList.forLanguageTags("en-US"));
431         }
432     }
433 
deleteMessageWithShellPermissionIdentity()434     private void deleteMessageWithShellPermissionIdentity() {
435         UiAutomation uiAutomation = sInstrumentation.getUiAutomation();
436         uiAutomation.adoptShellPermissionIdentity();
437         try {
438             getContext().getContentResolver().delete(CONTENT_URI,
439                     SELECT_BY_SERIAL_NUMBER, new String[]{String.valueOf(sSerialId)});
440         } catch (SecurityException e) {
441             logd("runWithShellPermissionIdentity exception = " + e);
442         } finally {
443             uiAutomation.dropShellPermissionIdentity();
444         }
445     }
446 }
447