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  */
16 
17 package com.android.cts.numberblocking.hostside;
18 
19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
20 import com.android.ddmlib.Log;
21 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
22 import com.android.tradefed.build.IBuildInfo;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.log.LogUtil;
25 import com.android.tradefed.result.CollectingTestListener;
26 import com.android.tradefed.testtype.DeviceTestCase;
27 import com.android.tradefed.testtype.IBuildReceiver;
28 import com.android.tradefed.util.RunInterruptedException;
29 import com.android.tradefed.util.RunUtil;
30 
31 import java.io.File;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 
35 /**
36  * Multi-user tests for number blocking.
37  */
38 // To run the tests in this file w/o running all the cts tests:
39 // make cts
40 // cts-tradefed
41 // run cts -m CtsHostsideNumberBlockingTestCases
42 public class NumberBlockingTest extends DeviceTestCase implements IBuildReceiver {
43     private static final String BLOCKED_NUMBER = "556";
44     private static final String PHONE_ACCOUNT_ID = "test_call_provider_id";
45     private static final String TEST_APK = "CtsHostsideNumberBlockingAppTest.apk";
46     private static final String NUMBER_BLOCKING_TESTS_PKG =
47             NumberBlockingTest.class.getPackage().getName();
48     private static final String CALL_BLOCKING_TEST_CLASS_NAME = "CallBlockingTest";
49     private static final String NUMBER_BLOCKING_APP_TEST_CLASS_NAME = "NumberBlockingAppTest";
50     private static final String TEST_APP_CONNECTION_SERVICE_NAME = "DummyConnectionService";
51     private static final String SECONDARY_USER_NAME = "NumberBlockingTest SecondaryUser";
52     private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
53     private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
54 
55     private int mSecondaryUserId;
56     private int mPrimaryUserSerialNumber;
57     private int mSecondaryUserSerialNumber;
58 
59     private IBuildInfo mCtsBuild;
60     private boolean mHasFeature;
61 
62     @Override
setUp()63     protected void setUp() throws Exception {
64         super.setUp();
65 
66         mHasFeature = getDevice().isMultiUserSupported()
67                 && getDevice().hasFeature(FEATURE_TELEPHONY)
68                 && getDevice().hasFeature(FEATURE_CONNECTION_SERVICE);
69 
70         if (!mHasFeature) {
71             return;
72         }
73 
74         installTestAppForUser(getDevice().getPrimaryUserId());
75         createSecondaryUser();
76         installTestAppForUser(mSecondaryUserId);
77 
78         mPrimaryUserSerialNumber = getUserSerialNumber(getDevice().getPrimaryUserId());
79         mSecondaryUserSerialNumber = getUserSerialNumber(mSecondaryUserId);
80     }
81 
82     @Override
tearDown()83     protected void tearDown() throws Exception {
84         if (mHasFeature) {
85             getDevice().removeUser(mSecondaryUserId);
86         }
87 
88         super.tearDown();
89     }
90 
91     @Override
setBuild(IBuildInfo iBuildInfo)92     public void setBuild(IBuildInfo iBuildInfo) {
93         mCtsBuild = iBuildInfo;
94     }
95 
testNumberBlocking()96     public void testNumberBlocking() throws Exception {
97         if (!mHasFeature) {
98             LogUtil.CLog.logAndDisplay(Log.LogLevel.INFO,
99                     "Skipping number blocking test as the feature is not supported.");
100             return;
101         }
102 
103         try {
104             // First run tests for primary user.
105             // Cleanup state prior to running tests.
106             setTestAppAsDefaultSmsAppForUser(
107                     true /* setToSmsApp */, getDevice().getPrimaryUserId());
108             runTestAsPrimaryUser(NUMBER_BLOCKING_APP_TEST_CLASS_NAME,
109                     "testCleanupBlockedNumberAsPrimaryUserSucceeds");
110 
111             // Block a number as a privileged app that can block numbers.
112             runTestAsPrimaryUser(
113                     NUMBER_BLOCKING_APP_TEST_CLASS_NAME, "testBlockNumberAsPrimaryUserSucceeds");
114             setTestAppAsDefaultSmsAppForUser(
115                     false /* setToSmsApp */, getDevice().getPrimaryUserId());
116 
117             // Ensure incoming call from blocked number is rejected, and unregister the phone
118             // account.
119             runTestAsPrimaryUser(CALL_BLOCKING_TEST_CLASS_NAME, "testRegisterPhoneAccount");
120             enablePhoneAccountForUser(mPrimaryUserSerialNumber);
121             runTestAsPrimaryUser(CALL_BLOCKING_TEST_CLASS_NAME,
122                     "testIncomingCallFromBlockedNumberIsRejected");
123             runTestAsPrimaryUser(CALL_BLOCKING_TEST_CLASS_NAME, "testUnregisterPhoneAccount");
124 
125             // Run tests as secondary user.
126             startUserAndWait(mSecondaryUserId);
127 
128             // Ensure that a privileged app cannot block numbers when the current user is a
129             // secondary user.
130             setTestAppAsDefaultSmsAppForUser(true /* setToSmsApp */, mSecondaryUserId);
131             runTestAsSecondaryUser(NUMBER_BLOCKING_APP_TEST_CLASS_NAME,
132                     "testSecondaryUserCannotBlockNumbers");
133             setTestAppAsDefaultSmsAppForUser(false /* setToSmsApp */, mSecondaryUserId);
134 
135             // Calls should be blocked by Telecom for secondary users as well.
136             runTestAsSecondaryUser(CALL_BLOCKING_TEST_CLASS_NAME, "testRegisterPhoneAccount");
137             enablePhoneAccountForUser(mSecondaryUserSerialNumber);
138             runTestAsSecondaryUser(CALL_BLOCKING_TEST_CLASS_NAME,
139                     "testIncomingCallFromBlockedNumberIsRejected");
140         } finally {
141             // Cleanup state by unblocking the blocked number.
142             setTestAppAsDefaultSmsAppForUser(
143                     true /* setToSmsApp */, getDevice().getPrimaryUserId());
144             runTestAsPrimaryUser(
145                     NUMBER_BLOCKING_APP_TEST_CLASS_NAME, "testUnblockNumberAsPrimaryUserSucceeds");
146         }
147     }
148 
149     /** Starts user {@code userId} and waits until it is in state RUNNING_UNLOCKED. */
startUserAndWait(int userId)150     protected void startUserAndWait(int userId) throws Exception {
151         getDevice().startUser(userId);
152 
153         final String desiredState = "RUNNING_UNLOCKED";
154         final long USER_STATE_TIMEOUT_MS = 60_0000; // 1 minute
155         final long timeout = System.currentTimeMillis() + USER_STATE_TIMEOUT_MS;
156         final String command = String.format("am get-started-user-state %d", userId);
157         String output = "";
158         while (System.currentTimeMillis() <= timeout) {
159             output = getDevice().executeShellCommand(command);
160             if (output.contains(desiredState)) {
161                 return;
162             }
163             RunUtil.getDefault().sleep(100);
164         }
165         fail("User state of " + userId + " was '" + output + "' rather than " + desiredState);
166     }
167 
createSecondaryUser()168     private void createSecondaryUser() throws Exception {
169         mSecondaryUserId = getDevice().createUser(SECONDARY_USER_NAME);
170         getDevice().waitForDeviceAvailable();
171     }
172 
installTestAppForUser(int userId)173     private void installTestAppForUser(int userId) throws Exception {
174         LogUtil.CLog.logAndDisplay(Log.LogLevel.INFO, "Installing test app for user: " + userId);
175         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
176         File testAppFile = buildHelper.getTestFile(TEST_APK);
177         String installResult;
178         try {
179             installResult = getDevice().installPackageForUser(
180                     testAppFile, true /*reinstall*/, userId);
181         } catch (DeviceNotAvailableException dna) {
182             fail("Device not available to install test app " + dna);
183             return;
184         }
185         assertNull(String.format(
186                 "failed to install number blocking test app. Reason: %s", installResult),
187                 installResult);
188 
189         waitForTestAppInstallation(NUMBER_BLOCKING_TESTS_PKG);
190     }
191 
runTestAsPrimaryUser(String className, String methodName)192     private void runTestAsPrimaryUser(String className, String methodName) throws Exception {
193         runTestAsUser(className, methodName, getDevice().getPrimaryUserId());
194     }
195 
runTestAsSecondaryUser(String className, String methodName)196     private void runTestAsSecondaryUser(String className, String methodName) throws Exception {
197         runTestAsUser(className, methodName, mSecondaryUserId);
198     }
199 
runTestAsUser(String className, String methodName, int userId)200     private void runTestAsUser(String className, String methodName, int userId) throws Exception {
201         LogUtil.CLog.logAndDisplay(Log.LogLevel.INFO, "Running %s.%s for user: %d",
202                 className, methodName, userId);
203         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
204                 NUMBER_BLOCKING_TESTS_PKG,
205                 "androidx.test.runner.AndroidJUnitRunner",
206                 getDevice().getIDevice());
207         testRunner.addInstrumentationArg("blocked_number", BLOCKED_NUMBER);
208         testRunner.addInstrumentationArg("phone_account_id", PHONE_ACCOUNT_ID);
209         testRunner.setMethodName(NUMBER_BLOCKING_TESTS_PKG + "." + className, methodName);
210         CollectingTestListener listener = new CollectingTestListener();
211         getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener);
212         assertEquals(1, listener.getNumTotalTests());
213         assertFalse(listener.getCurrentRunResults().getTestResults().keySet().toString(),
214                 listener.getCurrentRunResults().hasFailedTests());
215     }
216 
enablePhoneAccountForUser(int userSerialNumber)217     private void enablePhoneAccountForUser(int userSerialNumber) throws Exception {
218         String command = String.format(
219                 "telecom set-phone-account-enabled %s\\/%s.%s\\$%s %s %d",
220                 NUMBER_BLOCKING_TESTS_PKG,
221                 NUMBER_BLOCKING_TESTS_PKG,
222                 CALL_BLOCKING_TEST_CLASS_NAME,
223                 TEST_APP_CONNECTION_SERVICE_NAME,
224                 PHONE_ACCOUNT_ID,
225                 userSerialNumber);
226         String commandResponse = getDevice().executeShellCommand(command);
227         assertTrue(commandResponse, commandResponse.contains("Success"));
228     }
229 
setTestAppAsDefaultSmsAppForUser(boolean setToSmsApp, int userId)230     private void setTestAppAsDefaultSmsAppForUser(boolean setToSmsApp, int userId)
231             throws Exception {
232         String command = String.format("appops set --user %d %s WRITE_SMS %s", userId,
233                 NUMBER_BLOCKING_TESTS_PKG,
234                 setToSmsApp ? "allow" : "default");
235         assertEquals("", getDevice().executeShellCommand(command));
236     }
237 
238     // TODO: Replace this with API in ITestDevice once it is available.
getUserSerialNumber(int userId)239     private int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
240         Pattern pattern = Pattern.compile("^.*UserInfo\\{" + userId + "\\:.*serialNo=(\\d+).*$");
241         // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
242         String commandOutput = getDevice().executeShellCommand("dumpsys user");
243         String[] tokens = commandOutput.split("\\n");
244         for (String token : tokens) {
245             token = token.trim();
246             Matcher matcher = pattern.matcher(token);
247             if (matcher.matches()) {
248                 int serialNumber = Integer.parseInt(matcher.group(1));
249                 LogUtil.CLog.logAndDisplay(
250                         Log.LogLevel.INFO,
251                         String.format("Serial number of user %d : %d", userId, serialNumber));
252                 return serialNumber;
253             }
254         }
255         fail("Couldn't find user " + userId);
256         return -1;
257     }
258 
waitForTestAppInstallation(String packageName)259     private void waitForTestAppInstallation(String packageName) {
260         try {
261             int retries = 0;
262             while (!getDevice().getInstalledPackageNames().contains(packageName)
263                     && retries < 10) {
264                 RunUtil.getDefault().sleep(50);
265                 retries++;
266             }
267 
268             assertTrue(getDevice().getInstalledPackageNames().contains(packageName));
269         } catch (DeviceNotAvailableException dne) {
270             fail("Device not available.");
271         } catch (RunInterruptedException ie) {
272             fail("Failed to wait for change.");
273         }
274     }
275 }
276