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