1 /* 2 * Copyright (C) 2017 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.devicepolicy; 17 18 import static org.junit.Assert.fail; 19 20 import com.android.tradefed.log.LogUtil.CLog; 21 22 import org.junit.Test; 23 24 import java.util.concurrent.TimeUnit; 25 26 public abstract class BaseDeviceAdminServiceTest extends BaseDevicePolicyTest { 27 protected static final String OWNER_PKG = "com.android.cts.deviceadminservice"; 28 protected static final String OWNER_PKG_B = "com.android.cts.deviceadminserviceb"; 29 30 protected static final String OWNER_APK_1 = "CtsDeviceAdminService1.apk"; 31 protected static final String OWNER_APK_2 = "CtsDeviceAdminService2.apk"; 32 protected static final String OWNER_APK_3 = "CtsDeviceAdminService3.apk"; 33 protected static final String OWNER_APK_4 = "CtsDeviceAdminService4.apk"; 34 35 protected static final String OWNER_APK_B = "CtsDeviceAdminServiceB.apk"; 36 37 protected static final String ADMIN_RECEIVER_TEST_CLASS = ".MyOwner"; 38 39 protected static final String OWNER_COMPONENT = OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS; 40 protected static final String OWNER_SERVICE = OWNER_PKG + "/.MyService"; 41 protected static final String OWNER_SERVICE2 = OWNER_PKG + "/.MyService2"; 42 43 protected static final String OWNER_COMPONENT_B = OWNER_PKG_B + "/" 44 + OWNER_PKG + ADMIN_RECEIVER_TEST_CLASS; 45 protected static final String OWNER_SERVICE_B = OWNER_PKG_B + "/" 46 + OWNER_PKG + ".MyService"; 47 48 private static final int TIMEOUT_SECONDS = 3 * 60; 49 50 @Override tearDown()51 public void tearDown() throws Exception { 52 removeAdmin(OWNER_COMPONENT, getUserId()); 53 removeAdmin(OWNER_COMPONENT_B, getUserId()); 54 getDevice().uninstallPackage(OWNER_PKG); 55 getDevice().uninstallPackage(OWNER_PKG_B); 56 57 super.tearDown(); 58 } 59 getUserId()60 protected abstract int getUserId() throws Exception; 61 executeDeviceTestMethod(String className, String testName)62 protected void executeDeviceTestMethod(String className, String testName) throws Exception { 63 runDeviceTestsAsUser(OWNER_PKG, className, testName, getUserId()); 64 } 65 66 protected interface RunnableWithThrowable { run()67 void run() throws Throwable; 68 } 69 withRetry(RunnableWithThrowable test)70 protected void withRetry(RunnableWithThrowable test) throws Throwable { 71 final long until = System.nanoTime() + TimeUnit.SECONDS.toNanos(TIMEOUT_SECONDS); 72 73 sleep(500); 74 75 Throwable lastThrowable = null; 76 while (System.nanoTime() < until) { 77 try { 78 test.run(); 79 // Pass, return. 80 return; 81 } catch (Throwable th) { 82 lastThrowable = th; 83 } 84 sleep(3000); 85 } 86 if (lastThrowable != null) { 87 throw lastThrowable; 88 } 89 fail("Internal error: test " + test + " didn't run, exception not thrown."); 90 } 91 installOwnerApp(String apk)92 protected abstract void installOwnerApp(String apk) throws Exception; 93 removeAdmin(String ownerComponent)94 protected abstract void removeAdmin(String ownerComponent) throws Exception; 95 setAsOwnerOrFail(String component)96 protected abstract void setAsOwnerOrFail(String component) throws Exception; 97 98 @Test testAll()99 public void testAll() throws Throwable { 100 // Install 101 CLog.i("Installing apk1 (%s)...", OWNER_APK_1); 102 installOwnerApp(OWNER_APK_1); 103 104 CLog.i("Making it (%s) a device/profile owner...", OWNER_COMPONENT); 105 setAsOwnerOrFail(OWNER_COMPONENT); 106 107 withRetry(() -> assertServiceBound(OWNER_SERVICE)); 108 109 // Remove admin. 110 CLog.i("Removing admin..."); 111 removeAdmin(OWNER_COMPONENT); 112 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 113 114 // Overwrite -> update. 115 CLog.i("Re-installing apk1..."); 116 installOwnerApp(OWNER_APK_1); 117 118 CLog.i("Making it a device/profile owner..."); 119 setAsOwnerOrFail(OWNER_COMPONENT); 120 withRetry(() -> assertServiceBound(OWNER_SERVICE)); 121 122 CLog.i("Installing apk2 (%s)...", OWNER_APK_2); 123 installOwnerApp(OWNER_APK_2); 124 withRetry(() -> assertServiceBound(OWNER_SERVICE)); // Should still be bound. 125 126 // Service exported -> not bound. 127 CLog.i("Installing apk3 (%s)...", OWNER_APK_3); 128 installOwnerApp(OWNER_APK_3); 129 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 130 131 // Recover. 132 CLog.i("Installing apk2 again..."); 133 installOwnerApp(OWNER_APK_2); 134 withRetry(() -> assertServiceBound(OWNER_SERVICE)); 135 136 // Multiple service found -> not bound. 137 CLog.i("Installing apk4 (%s)...", OWNER_APK_4); 138 installOwnerApp(OWNER_APK_4); 139 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 140 withRetry(() -> assertServiceNotBound(OWNER_SERVICE2)); 141 142 // Disable service1 -> now there's only one service, so should be bound. 143 CLog.i("Running testDisableService1..."); 144 executeDeviceTestMethod(".ComponentController", "testDisableService1"); 145 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 146 // There's a rare flake which occurs here - will fix with migration 147 // withRetry(() -> assertServiceBound(OWNER_SERVICE2)); 148 149 CLog.i("Running testDisableService2..."); 150 executeDeviceTestMethod(".ComponentController", "testDisableService2"); 151 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 152 withRetry(() -> assertServiceNotBound(OWNER_SERVICE2)); 153 154 CLog.i("Running testEnableService1..."); 155 executeDeviceTestMethod(".ComponentController", "testEnableService1"); 156 withRetry(() -> assertServiceBound(OWNER_SERVICE)); 157 withRetry(() -> assertServiceNotBound(OWNER_SERVICE2)); 158 159 CLog.i("Running testEnableService2..."); 160 executeDeviceTestMethod(".ComponentController", "testEnableService2"); 161 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 162 withRetry(() -> assertServiceNotBound(OWNER_SERVICE2)); 163 164 // Remove admin. 165 CLog.i("Removing admin again..."); 166 removeAdmin(OWNER_COMPONENT); 167 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 168 169 // Retry with package 1 and remove admin. 170 CLog.i("Installing apk1 again..."); 171 installOwnerApp(OWNER_APK_1); 172 173 CLog.i("Making it a device/profile owner again..."); 174 setAsOwnerOrFail(OWNER_COMPONENT); 175 withRetry(() -> assertServiceBound(OWNER_SERVICE)); 176 177 CLog.i("Removing admin again..."); 178 removeAdmin(OWNER_COMPONENT); 179 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 180 181 // Now install package B and make it the owner. OWNER_APK_1 still exists, but it shouldn't 182 // interfere. 183 CLog.i("Installing apk B (%s)...", OWNER_APK_B); 184 installOwnerApp(OWNER_APK_B); 185 186 CLog.i("Making it a device/profile owner..."); 187 setAsOwnerOrFail(OWNER_COMPONENT_B); 188 withRetry(() -> assertServiceNotBound(OWNER_SERVICE)); 189 withRetry(() -> assertServiceBound(OWNER_SERVICE_B)); 190 } 191 rumpDumpSysService(String component)192 private String rumpDumpSysService(String component) throws Exception { 193 final String command = "dumpsys activity services " + component; 194 final String commandOutput = getDevice().executeShellCommand(command); 195 CLog.d("Output for command %s: \n%s", command, commandOutput); 196 return commandOutput; 197 } 198 assertServiceBound(String component)199 private void assertServiceBound(String component) throws Exception { 200 final String commandOutput = rumpDumpSysService(component); 201 for (String line : commandOutput.split("\r*\n")) { 202 if (line.contains("ConnectionRecord") && line.contains(component)) { 203 return; 204 } 205 } 206 fail("Service " + OWNER_SERVICE + " not bound. Output was:\n" + commandOutput); 207 } 208 assertServiceNotBound(String component)209 private void assertServiceNotBound(String component) throws Exception { 210 final String commandOutput = rumpDumpSysService(component); 211 for (String line : commandOutput.split("\r*\n")) { 212 if (line.contains("ConnectionRecord") && line.contains(component)) { 213 fail("Service " + OWNER_SERVICE + " is bound. Output was:\n" + commandOutput); 214 } 215 } 216 } 217 218 /* When the service is bound, "dumpsys activity services" shows something like this: 219 * ServiceRecord{1525afe u0 com.android.cts.deviceadminservice/.MyService} 220 intent={cmp=com.android.cts.deviceadminservice/.MyService} 221 packageName=com.android.cts.deviceadminservice 222 processName=com.android.cts.deviceadminservice 223 baseDir=/data/app/com.android.cts.deviceadminservice-kXKTlCILmDfib2P76FI75A==/base.apk 224 dataDir=/data/user/0/com.android.cts.deviceadminservice 225 app=ProcessRecord{205d686 22751:com.android.cts.deviceadminservice/u0a143} 226 createTime=-3s957ms startingBgTimeout=-- 227 lastActivity=-3s927ms restartTime=-3s927ms createdFromFg=true 228 Bindings: 229 * IntentBindRecord{57d5b47 CREATE}: 230 intent={cmp=com.android.cts.deviceadminservice/.MyService} 231 binder=android.os.BinderProxy@819af74 232 requested=true received=true hasBound=true doRebind=false 233 * Client AppBindRecord{e1d3c9d ProcessRecord{a8162c0 757:system/1000}} 234 Per-process Connections: 235 ConnectionRecord{10ab6b9 u0 CR FGS com.android.cts.deviceadminservice/.MyService:@51e9080} 236 All Connections: 237 ConnectionRecord{10ab6b9 u0 CR FGS com.android.cts.deviceadminservice/.MyService:@51e9080} 238 239 * ConnectionRecord{10ab6b9 u0 CR FGS com.android.cts.deviceadminservice/.MyService:@51e9080} 240 binding=AppBindRecord{e1d3c9d com.android.cts.deviceadminservice/.MyService:system} 241 conn=android.app.LoadedApk$ServiceDispatcher$InnerConnection@51e9080 flags=0x4000001 242 */ 243 } 244