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