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