• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }