• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.cts.appbinding;
17 
18 import com.android.tradefed.util.RunUtil;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.fail;
23 
24 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
25 import com.android.tradefed.build.IBuildInfo;
26 import com.android.tradefed.device.DeviceNotAvailableException;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
29 import com.android.tradefed.testtype.IBuildReceiver;
30 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
31 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40 
41 @RunWith(DeviceJUnit4ClassRunner.class)
42 public class AppBindingHostTest extends BaseHostJUnit4Test implements IBuildReceiver {
43 
44     private static final boolean SKIP_UNINSTALL = false;
45 
46     private static final String APK_1 = "CtsAppBindingService1.apk";
47     private static final String APK_2 = "CtsAppBindingService2.apk";
48     private static final String APK_3 = "CtsAppBindingService3.apk";
49     private static final String APK_4 = "CtsAppBindingService4.apk";
50     private static final String APK_5 = "CtsAppBindingService5.apk";
51     private static final String APK_6 = "CtsAppBindingService6.apk";
52     private static final String APK_7 = "CtsAppBindingService7.apk";
53     private static final String APK_B = "CtsAppBindingServiceB.apk";
54 
55     private static final String PACKAGE_A = "com.android.cts.appbinding.app";
56     private static final String PACKAGE_B = "com.android.cts.appbinding.app.b";
57 
58     private static final String PACKAGE_A_PROC = PACKAGE_A + ":persistent";
59 
60     private static final String APP_BINDING_SETTING = "app_binding_constants";
61 
62     private static final String SERVICE_1 = "com.android.cts.appbinding.app.MyService";
63     private static final String SERVICE_2 = "com.android.cts.appbinding.app.MyService2";
64 
65     private IBuildInfo mCtsBuild;
66     private int mCurrentUserId;
67 
68     private static final int DEFAULT_TIMEOUT_SEC = 30;
69     private static final int DEFAULT_LONG_TIMEOUT_SEC = 70;
70 
71     private interface ThrowingRunnable {
run()72         void run() throws Throwable;
73     }
74 
75     @Override
setBuild(IBuildInfo buildInfo)76     public void setBuild(IBuildInfo buildInfo) {
77         mCtsBuild = buildInfo;
78     }
79 
installAppAsUser(String appFileName, boolean grantPermissions, int userId)80     private void installAppAsUser(String appFileName, boolean grantPermissions, int userId)
81             throws Exception {
82         CLog.d("Installing app " + appFileName + " for user " + userId);
83         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
84         String result = getDevice().installPackageForUser(
85                 buildHelper.getTestFile(appFileName), true, grantPermissions, userId, "-t");
86         assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
87                 result);
88 
89         waitForBroadcastIdle();
90     }
91 
waitForBroadcastIdle()92     private void waitForBroadcastIdle() throws Exception {
93         runCommand("am wait-for-broadcast-idle");
94         RunUtil.getDefault().sleep(100); // Just wait a bit to make sure the system isn't too busy...
95     }
96 
runCommand(String command)97     private String runCommand(String command) throws Exception {
98         return runCommand(command, "", true);
99     }
100 
runCommand(String command, String expectedOutputPattern)101     private String runCommand(String command, String expectedOutputPattern) throws Exception {
102         return runCommand(command, expectedOutputPattern, true);
103     }
104 
runCommandAndNotMatch(String command, String expectedOutputPattern)105     private String runCommandAndNotMatch(String command, String expectedOutputPattern)
106             throws Exception {
107         return runCommand(command, expectedOutputPattern, false);
108     }
109 
runCommand(String command, String expectedOutputPattern, boolean shouldMatch)110     private String runCommand(String command, String expectedOutputPattern,
111             boolean shouldMatch) throws Exception {
112         CLog.d("Executing command: " + command);
113         final String output = getDevice().executeShellCommand(command);
114 
115         CLog.d("Output:\n"
116                 + "====================\n"
117                 + output
118                 + "====================");
119 
120         final Pattern pat = Pattern.compile(
121                 expectedOutputPattern, Pattern.MULTILINE | Pattern.COMMENTS);
122         if (pat.matcher(output.trim()).find() != shouldMatch) {
123             fail("Output from \"" + command + "\" "
124                     + (shouldMatch ? "didn't match" : "unexpectedly matched")
125                     + " \"" + expectedOutputPattern + "\"");
126         }
127         return output;
128     }
129 
runCommandAndExtract(String command, String startPattern, boolean startInclusive, String endPattern, boolean endInclusive)130     private String runCommandAndExtract(String command,
131             String startPattern, boolean startInclusive,
132             String endPattern, boolean endInclusive) throws Exception {
133         final String[] output = runCommand(command).split("\\n");
134         final StringBuilder sb = new StringBuilder();
135 
136         final Pattern start = Pattern.compile(startPattern, Pattern.COMMENTS);
137         final Pattern end = Pattern.compile(endPattern, Pattern.COMMENTS);
138 
139         boolean in = false;
140         for (String s : output) {
141             if (in) {
142                 if (end.matcher(s.trim()).find()) {
143                     if (endInclusive) {
144                         sb.append(s);
145                         sb.append("\n");
146                     }
147                     break;
148                 }
149                 sb.append(s);
150                 sb.append("\n");
151             } else {
152                 if (start.matcher(s.trim()).find()) {
153                     if (startInclusive) {
154                         sb.append(s);
155                         sb.append("\n");
156                     }
157                     continue;
158                 }
159                 in = true;
160             }
161         }
162 
163         return sb.toString();
164     }
165 
updateConstants(String settings)166     private void updateConstants(String settings) throws Exception {
167         runCommand("settings put global " + APP_BINDING_SETTING + " '" + settings + "'");
168     }
169 
isSmsCapable()170     private boolean isSmsCapable() throws Exception {
171         String output = runCommand("dumpsys phone");
172         if (output.contains("isSmsCapable=true")) {
173             CLog.d("Device is SMS capable");
174             return true;
175         }
176         CLog.d("Device is not SMS capable");
177         return false;
178     }
179 
setSmsApp(String pkg, int userId)180     private void setSmsApp(String pkg, int userId) throws Throwable {
181         runWithRetries(300, () -> {
182             String output1 = runCommand("cmd role get-role-holders --user " + userId
183                             + " android.app.role.SMS ");
184             if (output1.equals(pkg)) {
185                 CLog.d(pkg + " has been set default sms app.");
186             } else {
187                 String output2 = runCommand("cmd role add-role-holder --user " + userId
188                                 + " android.app.role.SMS " + pkg);
189                 if (output2.contains("TimeoutException")) {
190                     RunUtil.getDefault().sleep(10000);
191                     throw new RuntimeException("cmd role add-role-holder timeout.");
192                 }
193             }
194         });
195     }
196 
uninstallTestApps(boolean always)197     private void uninstallTestApps(boolean always) throws Exception {
198         if (SKIP_UNINSTALL && !always) {
199             return;
200         }
201         getDevice().uninstallPackage(PACKAGE_A);
202         getDevice().uninstallPackage(PACKAGE_B);
203 
204         waitForBroadcastIdle();
205     }
206 
runWithRetries(int timeoutSeconds, ThrowingRunnable r)207     private void runWithRetries(int timeoutSeconds, ThrowingRunnable r) throws Throwable {
208         final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000;
209         Throwable lastThrowable = null;
210 
211         int sleep = 200;
212         while (System.currentTimeMillis() < timeout) {
213             try {
214                 r.run();
215                 return;
216             } catch (Throwable th) {
217                 lastThrowable = th;
218             }
219             RunUtil.getDefault().sleep(sleep);
220             sleep = Math.min(1000, sleep * 2);
221         }
222         throw lastThrowable;
223     }
224 
225     @Before
setUp()226     public void setUp() throws Exception {
227         // Reset to the default setting.
228         updateConstants(",");
229 
230         uninstallTestApps(true);
231 
232         mCurrentUserId = getDevice().getCurrentUser();
233     }
234 
235     @After
tearDown()236     public void tearDown() throws Exception {
237         uninstallTestApps(false);
238 
239         // Reset to the default setting.
240         updateConstants(",");
241     }
242 
installAndCheckBound(String apk, String packageName, String serviceClass, int userId)243     private void installAndCheckBound(String apk, String packageName,
244             String serviceClass, int userId) throws Throwable {
245         // Install
246         installAppAsUser(apk, true, userId);
247 
248         // Set as the default app
249         setSmsApp(packageName, userId);
250 
251         checkBound(packageName, serviceClass, userId);
252     }
253 
checkBound(String packageName, String serviceClass, int userId)254     private void checkBound(String packageName, String serviceClass, int userId) throws Throwable {
255         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
256             runCommand("dumpsys activity service " + packageName + "/" + serviceClass,
257                     Pattern.quote("[" + packageName + "]") + " .* "
258                     + Pattern.quote("[" + serviceClass + "]"));
259         });
260 
261         // This should contain:
262         // "conn,0,[Default SMS app],PACKAGE,CLASS,bound,connected"
263 
264         // The binding information is propagated asynchronously, so we need a retry here too.
265         // (Even though the activity manager said it's already bound.)
266         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
267             runCommand("dumpsys app_binding -s",
268                     "^" + Pattern.quote("conn,[Default SMS app]," + userId + "," + packageName + ","
269                             + serviceClass + ",bound,connected,"));
270         });
271     }
272 
installAndCheckNotBound(String apk, String packageName, int userId, String expectedErrorPattern)273     private void installAndCheckNotBound(String apk, String packageName, int userId,
274             String expectedErrorPattern) throws Throwable {
275         // Install
276         installAppAsUser(apk, true, userId);
277 
278         // Set as the default app
279         setSmsApp(packageName, userId);
280 
281         checkNotBoundWithError(packageName, userId, expectedErrorPattern);
282     }
283 
checkNotBoundWithError(String packageName, int userId, String expectedErrorPattern)284     private void checkNotBoundWithError(String packageName, int userId,
285             String expectedErrorPattern) throws Throwable {
286         // This should contain:
287         // "finder,0,[Default SMS app],PACKAGE,null,ERROR-MESSAGE"
288         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
289             runCommand("dumpsys app_binding -s",
290                     "^" + Pattern.quote("finder,[Default SMS app]," + userId + ","
291                             + packageName + ",null,") + ".*"
292                             + Pattern.quote(expectedErrorPattern) + ".*$");
293         });
294     }
295 
checkPackageNotBound(String packageName, int userId)296     private void checkPackageNotBound(String packageName, int userId) throws Throwable {
297         // This should contain:
298         // "finder,0,[Default SMS app],DIFFERENT-PACKAGE,..."
299         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
300             runCommand("dumpsys app_binding -s",
301                     "^" + Pattern.quote("finder,[Default SMS app]," + userId + ",")
302                             + "(?!" // Negative look ahead
303                             + Pattern.quote(packageName + ",")
304                             + ")");
305         });
306     }
307 
assertOomAdjustment(String packageName, String processName, int oomAdj)308     private void assertOomAdjustment(String packageName, String processName, int oomAdj)
309             throws Exception {
310         final String output = runCommandAndExtract("dumpsys activity -a p " + packageName,
311                 "\\sProcessRecord\\{.*\\:" + Pattern.quote(processName) + "\\/", false,
312                 "^\\s*oom:", true);
313         /* Example:
314 ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
315   All known processes:
316   *APP* UID 10196 ProcessRecord{ef7dd8f 29993:com.android.cts.appbinding.app:persistent/u0a196}
317     user #0 uid=10196 gids={50196, 20196, 9997}
318     mRequiredAbi=arm64-v8a instructionSet=null
319     dir=/data/app/com.android.cts.appbinding.app-zvJ1Z44jYKxm-K0HLBRtLA==/base.apk publicDir=/da...
320     packageList={com.android.cts.appbinding.app}
321     compat={560dpi}
322     thread=android.app.IApplicationThread$Stub$Proxy@a5181c
323     pid=29993 starting=false
324     lastActivityTime=-14s282ms lastPssTime=-14s316ms pssStatType=0 nextPssTime=+5s718ms
325     adjSeq=35457 lruSeq=0 lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00 lastCachedSwapPss=0.00
326     procStateMemTracker: best=4 () / pending state=2 highest=2 1.0x
327     cached=false empty=true
328     oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
329     mCurSchedGroup=2 setSchedGroup=2 systemNoUi=false trimMemoryLevel=0
330     curProcState=4 mRepProcState=4 pssProcState=19 setProcState=4 lastStateTime=-14s282ms
331     reportedInteraction=true time=-14s284ms
332     startSeq=369
333     lastRequestedGc=-14s283ms lastLowMemory=-14s283ms reportLowMemory=false
334      Configuration={1.0 ?mcc?mnc [en_US] ldltr sw411dp w411dp h746dp 560dpi nrml long widecg ...
335      OverrideConfiguration={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ...
336      mLastReportedConfiguration={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ...
337     Services:
338       - ServiceRecord{383eb86 u0 com.android.cts.appbinding.app/.MyService}
339     Connected Providers:
340       - 54bfc25/com.android.providers.settings/.SettingsProvider->29993:com.android.cts....
341 
342   Process LRU list (sorted by oom_adj, 50 total, non-act at 4, non-svc at 4):
343     Proc #10: prcp  F/ /BFGS trm: 0 29993:com.android.cts.appbinding.app:persistent/u0a196 (service)
344         com.android.cts.appbinding.app/.MyService<=Proc{1332:system/1000}
345          */
346         final Pattern pat = Pattern.compile("\\soom:\\s.* set=(\\d+)$", Pattern.MULTILINE);
347         final Matcher m = pat.matcher(output);
348         if (!m.find()) {
349             fail("Unable to fild the oom: line for process " + processName);
350         }
351         final String oom = m.group(1);
352         assertEquals("Unexpected oom adjustment:", String.valueOf(oomAdj), oom);
353     }
354 
355     /**
356      * Install APK 1 and make it the default SMS app and make sure the service gets bound.
357      */
358     @Test
testSimpleBind1()359     public void testSimpleBind1() throws Throwable {
360         if (!isSmsCapable()) {
361             // device not supporting sms. cannot run the test.
362             return;
363         }
364 
365         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
366     }
367 
368     /**
369      * Install APK 2 and make it the default SMS app and make sure the service gets bound.
370      */
371     @Test
testSimpleBind2()372     public void testSimpleBind2() throws Throwable {
373         if (!isSmsCapable()) {
374             // device not supporting sms. cannot run the test.
375             return;
376         }
377 
378         installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, mCurrentUserId);
379     }
380 
381     /**
382      * Install APK B and make it the default SMS app and make sure the service gets bound.
383      */
384     @Test
testSimpleBindB()385     public void testSimpleBindB() throws Throwable {
386         if (!isSmsCapable()) {
387             // device not supporting sms. cannot run the test.
388             return;
389         }
390 
391         installAndCheckBound(APK_B, PACKAGE_B, SERVICE_1, mCurrentUserId);
392     }
393 
394     /**
395      * APK 3 doesn't have a valid service to be bound.
396      */
397     @Test
testSimpleNotBound3()398     public void testSimpleNotBound3() throws Throwable {
399         if (!isSmsCapable()) {
400             // device not supporting sms. cannot run the test.
401             return;
402         }
403 
404         installAndCheckNotBound(APK_3, PACKAGE_A, mCurrentUserId,
405                 "must be protected with android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE");
406     }
407 
408     /**
409      * APK 4 doesn't have a valid service to be bound.
410      */
411     @Test
testSimpleNotBound4()412     public void testSimpleNotBound4() throws Throwable {
413         if (!isSmsCapable()) {
414             // device not supporting sms. cannot run the test.
415             return;
416         }
417 
418         installAndCheckNotBound(APK_4, PACKAGE_A, mCurrentUserId, "More than one");
419     }
420 
421     /**
422      * APK 5 doesn't have a valid service to be bound.
423      */
424     @Test
testSimpleNotBound5()425     public void testSimpleNotBound5() throws Throwable {
426         if (!isSmsCapable()) {
427             // device not supporting sms. cannot run the test.
428             return;
429         }
430 
431         installAndCheckNotBound(APK_5, PACKAGE_A, mCurrentUserId,
432                 "Service with android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE not found");
433     }
434 
435     /**
436      * APK 6's service doesn't have android:process.
437      */
438     @Test
testSimpleNotBound6()439     public void testSimpleNotBound6() throws Throwable {
440         if (!isSmsCapable()) {
441             // device not supporting sms. cannot run the test.
442             return;
443         }
444 
445         installAndCheckNotBound(APK_6, PACKAGE_A, mCurrentUserId,
446                 "Service must not run on the main process");
447     }
448 
449     /**
450      * Make sure when the SMS app gets updated, the service still gets bound correctly.
451      */
452     @Test
testUpgrade()453     public void testUpgrade() throws Throwable {
454         if (!isSmsCapable()) {
455             // device not supporting sms. cannot run the test.
456             return;
457         }
458 
459         // Replace existing package without uninstalling.
460         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
461         installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, mCurrentUserId);
462         installAndCheckNotBound(APK_3, PACKAGE_A, mCurrentUserId,
463                 "must be protected with android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE");
464         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
465         installAndCheckNotBound(APK_4, PACKAGE_A, mCurrentUserId, "More than one");
466     }
467 
enableTargetService(boolean enable)468     private void enableTargetService(boolean enable) throws DeviceNotAvailableException {
469         runDeviceTests(new DeviceTestRunOptions(PACKAGE_A)
470                 .setTestClassName("com.android.cts.appbinding.app.MyEnabler")
471                 .setTestMethodName(enable ? "enableService" : "disableService")
472                 .setUserId(mCurrentUserId));
473     }
474 
475     /**
476      * Make sure the service responds to setComponentEnabled.
477      */
478     @Test
testServiceEnabledByDefault()479     public void testServiceEnabledByDefault() throws Throwable {
480         if (!isSmsCapable()) {
481             // device not supporting sms. cannot run the test.
482             return;
483         }
484 
485         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
486 
487         // Disable the component and now it should be unbound.
488 
489         enableTargetService(false);
490 
491         RunUtil.getDefault().sleep(2); // Technically not needed, but allow the system to handle the broadcast.
492 
493         checkNotBoundWithError(PACKAGE_A, mCurrentUserId,
494                 "Service with android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE not found");
495 
496         // Enable the component and now it should be bound.
497         enableTargetService(true);
498 
499         RunUtil.getDefault().sleep(2); // Technically not needed, but allow the system to handle the broadcast.
500 
501         checkBound(PACKAGE_A, SERVICE_1, mCurrentUserId);
502     }
503 
504     /**
505      * Make sure the service responds to setComponentEnabled.
506      */
507     @Test
testServiceDisabledByDefault()508     public void testServiceDisabledByDefault() throws Throwable {
509         if (!isSmsCapable()) {
510             // device not supporting sms. cannot run the test.
511             return;
512         }
513 
514         // The service is disabled by default, so not bound.
515         installAndCheckNotBound(APK_7, PACKAGE_A, mCurrentUserId,
516                 "Service with android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE not found");
517 
518         // Enable the component and now it should be bound.
519         enableTargetService(true);
520 
521         RunUtil.getDefault().sleep(2); // Technically not needed, but allow the system to handle the broadcast.
522 
523         checkBound(PACKAGE_A, SERVICE_1, mCurrentUserId);
524 
525         // Disable the component and now it should be unbound.
526 
527         enableTargetService(false);
528 
529         RunUtil.getDefault().sleep(2); // Technically not needed, but allow the system to handle the broadcast.
530 
531         checkNotBoundWithError(PACKAGE_A, mCurrentUserId,
532                 "Service with android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE not found");
533     }
534 
535     /**
536      * Make sure when the SMS app is uninstalled, the binding will be gone.
537      */
538     @Test
testUninstall()539     public void testUninstall() throws Throwable {
540         if (!isSmsCapable()) {
541             // device not supporting sms. cannot run the test.
542             return;
543         }
544 
545         // Replace existing package without uninstalling.
546         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
547         getDevice().uninstallPackage(PACKAGE_A);
548         checkPackageNotBound(PACKAGE_A, mCurrentUserId);
549 
550         // Try with different APKs, just to make sure.
551         installAndCheckBound(APK_B, PACKAGE_B, SERVICE_1, mCurrentUserId);
552         getDevice().uninstallPackage(PACKAGE_B);
553         checkPackageNotBound(PACKAGE_B, mCurrentUserId);
554 
555         installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, mCurrentUserId);
556         getDevice().uninstallPackage(PACKAGE_A);
557         checkPackageNotBound(PACKAGE_A, mCurrentUserId);
558     }
559 
560     /**
561      * Make sure when the SMS app changes, the service still gets bound correctly.
562      */
563     @Test
testSwitchDefaultApp()564     public void testSwitchDefaultApp() throws Throwable {
565         if (!isSmsCapable()) {
566             // device not supporting sms. cannot run the test.
567             return;
568         }
569 
570         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
571         installAndCheckBound(APK_B, PACKAGE_B, SERVICE_1, mCurrentUserId);
572         installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, mCurrentUserId);
573     }
574 
assertUserHasNoConnection(int userId)575     private void assertUserHasNoConnection(int userId) throws Throwable {
576         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
577             runCommandAndNotMatch("dumpsys app_binding -s",
578                     "^conn,\\[Default\\sSMS\\sapp\\]," + userId + ",");
579         });
580     }
581 
assertUserHasNoFinder(int userId)582     private void assertUserHasNoFinder(int userId) throws Throwable {
583         runWithRetries(DEFAULT_LONG_TIMEOUT_SEC, () -> {
584             runCommandAndNotMatch("dumpsys app_binding -s",
585                     "^finder,\\[Default\\sSMS\\sapp\\]," + userId + ",");
586         });
587     }
588 
589     @Test
testSecondaryUser()590     public void testSecondaryUser() throws Throwable {
591         if (!isSmsCapable()) {
592             // device not supporting sms. cannot run the test.
593             return;
594         }
595 
596         if (!getDevice().isMultiUserSupported()) {
597             // device do not support multi-user.
598             return;
599         }
600 
601         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
602 
603         final int userId = getDevice().createUser("test-user");
604         try {
605             getDevice().startUser(userId);
606 
607             // Install SMS app on the secondary user.
608             installAndCheckBound(APK_B, PACKAGE_B, SERVICE_1, userId);
609 
610             // Package A should still be bound on user-0.
611             checkBound(PACKAGE_A, SERVICE_1, mCurrentUserId);
612 
613             // Replace the app on the primary user with an invalid one.
614             installAndCheckNotBound(APK_3, PACKAGE_A, mCurrentUserId,
615                     "must be protected with android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE");
616 
617             // Secondary user should still have a valid connection.
618             checkBound(PACKAGE_B, SERVICE_1, userId);
619 
620             // Upgrade test: Try with apk 1, and then upgrade to apk 2.
621             installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, userId);
622             installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, userId);
623 
624             // Stop the secondary user, now the binding should be gone.
625             getDevice().stopUser(userId);
626 
627             // Now the connection should be removed.
628             assertUserHasNoConnection(userId);
629 
630             // Start the secondary user again.
631             getDevice().startUser(userId);
632 
633             // Now the binding should recover.
634             runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
635                 checkBound(PACKAGE_A, SERVICE_2, userId);
636             });
637 
638         } finally {
639             getDevice().removeUser(userId);
640         }
641         assertUserHasNoConnection(userId);
642         assertUserHasNoFinder(userId);
643     }
644 
645     @Test
testCrashAndAutoRebind()646     public void testCrashAndAutoRebind() throws Throwable {
647         if (!isSmsCapable()) {
648             // device not supporting sms. cannot run the test.
649             return;
650         }
651 
652         updateConstants(
653                 "service_reconnect_backoff_sec=5"
654                 + ",service_reconnect_backoff_increase=2"
655                 + ",service_reconnect_max_backoff_sec=1000"
656                 + ",service_stable_connection_threshold_sec=10");
657 
658         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
659 
660         // Ensure the expected status.
661         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
662             runCommand("dumpsys app_binding -s",
663                     "^conn,\\[Default\\sSMS\\sapp\\]," + mCurrentUserId + ",.*,bound,connected"
664                     + ",\\#con=1,\\#dis=0,\\#died=0,backoff=5000");
665         });
666 
667         // Let the service crash.
668         runCommand("dumpsys activity service " + PACKAGE_A + "/" + SERVICE_1 + " crash");
669 
670         // Now the connection disconnected and re-connected, so the counters increase.
671         // In this case, because binder-died isn't called, so backoff won't increase.
672         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
673             runCommand("dumpsys app_binding -s",
674                     "^conn,\\[Default\\sSMS\\sapp\\]," + mCurrentUserId + ",.*,bound,connected"
675                     + ",\\#con=2,\\#dis=1,\\#died=0,backoff=5000");
676         });
677 
678         // Force-stop the app.
679         runCommand("am force-stop " + PACKAGE_A);
680 
681         // Force-stop causes a disconnect and a binder-died. Then it doubles the backoff.
682         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
683             runCommand("dumpsys app_binding -s",
684                     "^conn,\\[Default\\sSMS\\sapp\\]," + mCurrentUserId + ",.*,not-bound,not-connected"
685                     + ",\\#con=2,\\#dis=2,\\#died=1,backoff=10000");
686         });
687 
688         RunUtil.getDefault().sleep(5000);
689 
690         // It should re-bind.
691         runWithRetries(10, () -> {
692             runCommand("dumpsys app_binding -s",
693                     "^conn,\\[Default\\sSMS\\sapp\\]," + mCurrentUserId + ",.*,bound,connected"
694                             + ",\\#con=3,\\#dis=2,\\#died=1,backoff=10000");
695         });
696 
697         // Force-stop again.
698         runCommand("am force-stop " + PACKAGE_A);
699 
700         runWithRetries(10, () -> {
701             runCommand("dumpsys app_binding -s",
702                     "^conn,\\[Default\\sSMS\\sapp\\]," + mCurrentUserId + ",.*,not-bound,not-connected"
703                             + ",\\#con=3,\\#dis=3,\\#died=2,backoff=20000");
704         });
705 
706         RunUtil.getDefault().sleep(10000);
707 
708         runWithRetries(10, () -> {
709             runCommand("dumpsys app_binding -s",
710                     "^conn,\\[Default\\sSMS\\sapp\\]," + mCurrentUserId + ",.*,bound,connected"
711                             + ",\\#con=4,\\#dis=3,\\#died=2,backoff=20000");
712         });
713 
714         // If the connection lasts more than service_stable_connection_threshold_sec seconds,
715         // the backoff resets.
716         RunUtil.getDefault().sleep(10000);
717 
718         runWithRetries(10, () -> {
719             runCommand("dumpsys app_binding -s",
720                     "^conn,\\[Default\\sSMS\\sapp\\]," + mCurrentUserId + ",.*,bound,connected"
721                             + ",\\#con=4,\\#dis=3,\\#died=2,backoff=5000");
722         });
723     }
724 
725     /**
726      * Test the feature flag.
727      */
728     @Test
testFeatureDisabled()729     public void testFeatureDisabled() throws Throwable {
730         if (!isSmsCapable()) {
731             // device not supporting sms. cannot run the test.
732             return;
733         }
734 
735         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
736 
737         updateConstants("sms_service_enabled=false");
738 
739         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
740             checkNotBoundWithError("null", mCurrentUserId, "feature disabled");
741         });
742 
743         updateConstants("sms_service_enabled=true");
744 
745         runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
746             checkBound(PACKAGE_A, SERVICE_1, mCurrentUserId);
747         });
748     }
749 
750     @Test
testOomAdjustment()751     public void testOomAdjustment() throws Throwable {
752         if (!isSmsCapable()) {
753             // device not supporting sms. cannot run the test.
754             return;
755         }
756 
757         installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, mCurrentUserId);
758         assertOomAdjustment(PACKAGE_A, PACKAGE_A_PROC, 200);
759     }
760 }
761