1 /* 2 * Copyright (C) 2023 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 17 package com.android.server.pm.test.app; 18 19 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import android.app.PendingIntent; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.IBackgroundInstallControlService; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackageInstaller; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ParceledListSlice; 33 import android.os.Bundle; 34 import android.os.IRemoteCallback; 35 import android.os.Process; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.util.Pair; 39 40 import androidx.annotation.NonNull; 41 import androidx.test.platform.app.InstrumentationRegistry; 42 import androidx.test.runner.AndroidJUnit4; 43 44 import com.android.compatibility.common.util.ShellIdentityUtils; 45 import com.android.compatibility.common.util.ThrowingRunnable; 46 47 import org.junit.After; 48 import org.junit.Before; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 52 import java.io.FileInputStream; 53 import java.io.OutputStream; 54 import java.util.ArrayList; 55 import java.util.Set; 56 import java.util.concurrent.CountDownLatch; 57 import java.util.concurrent.TimeUnit; 58 import java.util.function.Supplier; 59 import java.util.stream.Collectors; 60 61 @RunWith(AndroidJUnit4.class) 62 public class BackgroundInstallControlServiceTest { 63 private static final String TAG = "BackgroundInstallControlServiceTest"; 64 private static final String ACTION_INSTALL_COMMIT = 65 "com.android.server.pm.test.app.BackgroundInstallControlServiceTest" 66 + ".ACTION_INSTALL_COMMIT"; 67 private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3"; 68 69 private static final String TEST_DATA_DIR = "/data/local/tmp/"; 70 71 private static final String MOCK_APK_FILE = "BackgroundInstallControlMockApp3.apk"; 72 private IBackgroundInstallControlService mIBics; 73 74 @Before setUp()75 public void setUp() { 76 mIBics = 77 IBackgroundInstallControlService.Stub.asInterface( 78 ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); 79 assertThat(mIBics).isNotNull(); 80 } 81 82 @After tearDown()83 public void tearDown() { 84 runShellCommand("pm uninstall " + MOCK_PACKAGE_NAME); 85 } 86 87 @Test testGetMockBackgroundInstalledPackages()88 public void testGetMockBackgroundInstalledPackages() throws RemoteException { 89 ParceledListSlice<PackageInfo> slice = 90 ShellIdentityUtils.invokeMethodWithShellPermissions( 91 mIBics, 92 (bics) -> { 93 try { 94 return bics.getBackgroundInstalledPackages( 95 PackageManager.MATCH_ALL, Process.myUserHandle() 96 .getIdentifier()); 97 } catch (RemoteException e) { 98 throw e.rethrowFromSystemServer(); 99 } 100 }); 101 assertThat(slice).isNotNull(); 102 103 var packageList = slice.getList(); 104 assertThat(packageList).isNotNull(); 105 assertThat(packageList).hasSize(2); 106 107 var expectedPackageNames = 108 Set.of( 109 "com.android.servicestests.apps.bicmockapp1", 110 "com.android.servicestests.apps.bicmockapp2"); 111 var actualPackageNames = 112 packageList.stream() 113 .map((packageInfo) -> packageInfo.packageName) 114 .collect(Collectors.toSet()); 115 assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames); 116 } 117 118 @Test testRegisterBackgroundInstallControlCallback()119 public void testRegisterBackgroundInstallControlCallback() 120 throws Exception { 121 String testPackageName = "test"; 122 int testUserId = 1; 123 ArrayList<Pair<String, Integer>> sharedResource = new ArrayList<>(); 124 IRemoteCallback testCallback = 125 new IRemoteCallback.Stub() { 126 private final ArrayList<Pair<String, Integer>> mArray = sharedResource; 127 128 @Override 129 public void sendResult(Bundle data) throws RemoteException { 130 mArray.add(new Pair(testPackageName, testUserId)); 131 } 132 }; 133 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn( 134 mIBics, 135 (bics) -> { 136 try { 137 bics.registerBackgroundInstallCallback(testCallback); 138 } catch (RemoteException e) { 139 throw e.rethrowFromSystemServer(); 140 } 141 }); 142 installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME); 143 144 assertUntil(() -> sharedResource.size() == 1, 2000); 145 assertThat(sharedResource.get(0).first).isEqualTo(testPackageName); 146 assertThat(sharedResource.get(0).second).isEqualTo(testUserId); 147 } 148 149 @Test testUnregisterBackgroundInstallControlCallback()150 public void testUnregisterBackgroundInstallControlCallback() { 151 String testValue = "test"; 152 ArrayList<String> sharedResource = new ArrayList<>(); 153 IRemoteCallback testCallback = 154 new IRemoteCallback.Stub() { 155 private final ArrayList<String> mArray = sharedResource; 156 157 @Override 158 public void sendResult(Bundle data) throws RemoteException { 159 mArray.add(testValue); 160 } 161 }; 162 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn( 163 mIBics, 164 (bics) -> { 165 try { 166 bics.registerBackgroundInstallCallback(testCallback); 167 bics.unregisterBackgroundInstallCallback(testCallback); 168 } catch (RemoteException e) { 169 throw e.rethrowFromSystemServer(); 170 } 171 }); 172 installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME); 173 174 assertUntil(sharedResource::isEmpty, 2000); 175 } 176 installPackage(String apkPath, String packageName)177 private static boolean installPackage(String apkPath, String packageName) { 178 Context context = InstrumentationRegistry.getInstrumentation().getContext(); 179 final CountDownLatch installLatch = new CountDownLatch(1); 180 final BroadcastReceiver installReceiver = 181 new BroadcastReceiver() { 182 public void onReceive(Context context, Intent intent) { 183 int packageInstallStatus = 184 intent.getIntExtra( 185 PackageInstaller.EXTRA_STATUS, 186 PackageInstaller.STATUS_FAILURE_INVALID); 187 if (packageInstallStatus == PackageInstaller.STATUS_SUCCESS) { 188 installLatch.countDown(); 189 } 190 } 191 }; 192 final IntentFilter intentFilter = new IntentFilter(ACTION_INSTALL_COMMIT); 193 context.registerReceiver(installReceiver, intentFilter, Context.RECEIVER_EXPORTED); 194 195 PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); 196 PackageInstaller.SessionParams params = 197 new PackageInstaller.SessionParams( 198 PackageInstaller.SessionParams.MODE_FULL_INSTALL); 199 params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED); 200 try { 201 int sessionId = packageInstaller.createSession(params); 202 PackageInstaller.Session session = packageInstaller.openSession(sessionId); 203 OutputStream out = session.openWrite(packageName, 0, -1); 204 FileInputStream fis = new FileInputStream(apkPath); 205 byte[] buffer = new byte[65536]; 206 int size; 207 while ((size = fis.read(buffer)) != -1) { 208 out.write(buffer, 0, size); 209 } 210 session.fsync(out); 211 fis.close(); 212 out.close(); 213 214 runWithShellPermissionIdentity( 215 () -> { 216 session.commit(createPendingIntent(context).getIntentSender()); 217 installLatch.await(5, TimeUnit.SECONDS); 218 }); 219 return true; 220 } catch (Exception e) { 221 throw new RuntimeException(e); 222 } 223 } 224 createPendingIntent(Context context)225 private static PendingIntent createPendingIntent(Context context) { 226 PendingIntent pendingIntent = 227 PendingIntent.getBroadcast( 228 context, 229 1, 230 new Intent(ACTION_INSTALL_COMMIT) 231 .setPackage( 232 BackgroundInstallControlServiceTest.class.getPackageName()), 233 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE); 234 return pendingIntent; 235 } 236 runWithShellPermissionIdentity(@onNull ThrowingRunnable command)237 private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command) 238 throws Exception { 239 InstrumentationRegistry.getInstrumentation() 240 .getUiAutomation() 241 .adoptShellPermissionIdentity(); 242 try { 243 command.run(); 244 } finally { 245 InstrumentationRegistry.getInstrumentation() 246 .getUiAutomation() 247 .dropShellPermissionIdentity(); 248 } 249 } 250 assertUntil(Supplier<Boolean> condition, int timeoutMs)251 private static void assertUntil(Supplier<Boolean> condition, int timeoutMs) { 252 long endTime = System.currentTimeMillis() + timeoutMs; 253 while (System.currentTimeMillis() <= endTime) { 254 if (condition.get()) return; 255 try { 256 Thread.sleep(10); 257 } catch (InterruptedException e) { 258 throw new RuntimeException(e); 259 } 260 } 261 assertThat(condition.get()).isTrue(); 262 } 263 }