1 /*
2  * Copyright (C) 2021 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 android.content.pm;
18 
19 import android.Manifest;
20 import android.app.UiAutomation;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentSender;
24 import android.os.HandlerThread;
25 import android.os.ParcelFileDescriptor;
26 import android.perftests.utils.BenchmarkState;
27 import android.perftests.utils.PerfStatusReporter;
28 import android.util.Log;
29 
30 import androidx.annotation.NonNull;
31 import androidx.test.platform.app.InstrumentationRegistry;
32 
33 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
34 import com.android.cts.install.lib.Install;
35 import com.android.cts.install.lib.InstallUtils;
36 import com.android.cts.install.lib.LocalIntentSender;
37 import com.android.cts.install.lib.TestApp;
38 
39 import org.junit.After;
40 import org.junit.Before;
41 import org.junit.Rule;
42 import org.junit.Test;
43 
44 import java.io.ByteArrayOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.OutputStream;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.concurrent.CountDownLatch;
51 import java.util.concurrent.TimeUnit;
52 
53 public class PackageInstallerBenchmark {
54     private static final String TAG = "PackageInstallerBenchmark";
55 
56     @Rule
57     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
58 
59     /**
60      * This rule adopts the Shell process permissions, needed because INSTALL_PACKAGES
61      * and DELETE_PACKAGES are privileged permission.
62      */
63     @Rule
64     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
65             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
66             Manifest.permission.INSTALL_PACKAGES,
67             Manifest.permission.DELETE_PACKAGES);
68 
69     private static class SessionCallback extends PackageInstaller.SessionCallback {
70         private final List<Integer> mExpectedSessions;
71         private final CountDownLatch mCountDownLatch;
72         private final boolean mExpectedSuccess;
73 
SessionCallback(boolean expectedSuccess, List<Integer> expectedSessions, @NonNull CountDownLatch countDownLatch)74         SessionCallback(boolean expectedSuccess, List<Integer> expectedSessions,
75                 @NonNull CountDownLatch countDownLatch) {
76             mExpectedSuccess = expectedSuccess;
77             mCountDownLatch = countDownLatch;
78             mExpectedSessions = expectedSessions;
79         }
80 
81         @Override
onCreated(int sessionId)82         public void onCreated(int sessionId) { }
83 
84         @Override
onBadgingChanged(int sessionId)85         public void onBadgingChanged(int sessionId) { }
86 
87         @Override
onActiveChanged(int sessionId, boolean active)88         public void onActiveChanged(int sessionId, boolean active) { }
89 
90         @Override
onProgressChanged(int sessionId, float progress)91         public void onProgressChanged(int sessionId, float progress) { }
92 
93         @Override
onFinished(int sessionId, boolean success)94         public void onFinished(int sessionId, boolean success) {
95             if (success == mExpectedSuccess && mExpectedSessions.contains(sessionId)) {
96                 mCountDownLatch.countDown();
97             }
98         }
99     }
100 
101     private CountDownLatch mCountDownLatch;
102     private SessionCallback mSessionCallback;
103     private PackageInstaller mPackageInstaller;
104     private Install mInstall;
105     private HandlerThread mHandlerThread;
106     private List<PackageInstaller.Session> mExpectedSessions;
107     private List<Integer> mExpectedSessionIds;
108     final LocalIntentSender mLocalIntentSender = new LocalIntentSender();
109     private IntentSender mIntentSender;
110 
111     @Before
setUp()112     public void setUp() throws IOException {
113         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
114         mPackageInstaller =  context.getPackageManager().getPackageInstaller();
115         mHandlerThread = new HandlerThread("PackageInstallerBenchmark");
116         mHandlerThread.start();
117 
118         mIntentSender = mLocalIntentSender.getIntentSender();
119     }
120 
121     @After
tearDown()122     public void tearDown() throws InterruptedException {
123         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
124         context.unregisterReceiver(mLocalIntentSender);
125 
126         uninstall(false /* stop at fail */, TestApp.A, TestApp.B, TestApp.C);
127         mHandlerThread.quitSafely();
128     }
129 
createSinglePackageSessions( BenchmarkState state, boolean expectedResult, TestApp...testApps)130     private List<PackageInstaller.Session> createSinglePackageSessions(
131             BenchmarkState state, boolean expectedResult, TestApp...testApps)
132             throws IOException, InterruptedException {
133         state.pauseTiming();
134         uninstall(false /* stop at fail */, testApps);
135 
136         mExpectedSessions = new ArrayList<>();
137         mExpectedSessionIds = new ArrayList<>();
138         for (TestApp testApp : testApps) {
139             mInstall = Install.single(testApp);
140             final int expectedSessionId = mInstall.createSession();
141             PackageInstaller.Session session =
142                     InstallUtils.openPackageInstallerSession(expectedSessionId);
143             Log.d(TAG, "createNewSession: session expectedSessionId = " + expectedSessionId);
144             mExpectedSessions.add(session);
145             mExpectedSessionIds.add(expectedSessionId);
146         }
147 
148         mCountDownLatch = new CountDownLatch(mExpectedSessions.size());
149         mSessionCallback = new SessionCallback(expectedResult, mExpectedSessionIds,
150                 mCountDownLatch);
151         mPackageInstaller.registerSessionCallback(mSessionCallback,
152                 mHandlerThread.getThreadHandler());
153         state.resumeTiming();
154         return mExpectedSessions;
155     }
156 
createMultiplePackageSessions(BenchmarkState state, boolean expectedSuccess, List<TestApp[]> testAppsList)157     private List<PackageInstaller.Session> createMultiplePackageSessions(BenchmarkState state,
158             boolean expectedSuccess, List<TestApp[]> testAppsList)
159             throws IOException, InterruptedException {
160         state.pauseTiming();
161         mExpectedSessions = new ArrayList<>();
162         mExpectedSessionIds = new ArrayList<>();
163         for (TestApp[] testApps : testAppsList) {
164             uninstall(false /* stop at fail */, testApps);
165 
166             mInstall = Install.multi(testApps);
167             final int expectedSessionId = mInstall.createSession();
168             PackageInstaller.Session session =
169                     InstallUtils.openPackageInstallerSession(expectedSessionId);
170             mExpectedSessions.add(session);
171             mExpectedSessionIds.add(expectedSessionId);
172         }
173 
174         mCountDownLatch = new CountDownLatch(mExpectedSessions.size());
175         mSessionCallback = new SessionCallback(expectedSuccess, mExpectedSessionIds,
176                 mCountDownLatch);
177         mPackageInstaller.registerSessionCallback(mSessionCallback,
178                 mHandlerThread.getThreadHandler());
179         state.resumeTiming();
180         return mExpectedSessions;
181     }
182 
uninstall(boolean stopAtFail, TestApp...testApps)183     private void uninstall(boolean stopAtFail, TestApp...testApps) throws InterruptedException {
184         String[] packageNames = new String[testApps.length];
185         for (int i = 0; i < testApps.length; i++) {
186             packageNames[i] = testApps[i].getPackageName();
187         }
188         uninstall(stopAtFail, packageNames);
189     }
190 
uninstall(boolean stopAtFail, String...packageNames)191     private void uninstall(boolean stopAtFail, String...packageNames) throws InterruptedException {
192         LocalIntentSender localIntentSender = new LocalIntentSender();
193         IntentSender intentSender = localIntentSender.getIntentSender();
194         for (String packageName : packageNames) {
195             try {
196                 mPackageInstaller.uninstall(packageName, intentSender);
197             } catch (IllegalArgumentException e) {
198                 continue;
199             }
200             Intent intent = localIntentSender.getResult();
201             if (stopAtFail) {
202                 InstallUtils.assertStatusSuccess(intent);
203             }
204         }
205 
206         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
207         context.unregisterReceiver(localIntentSender);
208     }
209 
uninstallSession(BenchmarkState state, String...packageNames)210     private void uninstallSession(BenchmarkState state, String...packageNames)
211             throws Exception {
212         state.pauseTiming();
213         uninstall(true /* stop at fail */, packageNames);
214         mPackageInstaller.unregisterSessionCallback(mSessionCallback);
215         executeShellCommand("pm gc");
216         state.resumeTiming();
217     }
218 
executeShellCommand(String command)219     private static String executeShellCommand(String command) throws IOException {
220         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
221         final ParcelFileDescriptor stdout = uiAutomation.executeShellCommand(command);
222         try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout);
223              ByteArrayOutputStream result = new ByteArrayOutputStream()) {
224             writeFullStream(inputStream, result);
225             return result.toString("UTF-8");
226         }
227     }
228 
writeFullStream(InputStream inputStream, OutputStream outputStream)229     private static void writeFullStream(InputStream inputStream, OutputStream outputStream)
230             throws IOException {
231         final byte[] buffer = new byte[1024];
232         int length;
233         while ((length = inputStream.read(buffer)) != -1) {
234             outputStream.write(buffer, 0, length);
235         }
236     }
237 
238     @Test(timeout = 600_000L)
commit_aSingleApkSession_untilFinishBenchmark()239     public void commit_aSingleApkSession_untilFinishBenchmark() throws Exception {
240         uninstall(false /* stop at fail */, TestApp.A);
241 
242         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
243         while (state.keepRunning()) {
244             List<PackageInstaller.Session> sessions =
245                     createSinglePackageSessions(state, true, TestApp.A1);
246 
247             for (PackageInstaller.Session session : sessions) {
248                 session.commit(mIntentSender);
249             }
250             mCountDownLatch.await(1, TimeUnit.MINUTES);
251 
252             uninstallSession(state, TestApp.A);
253         }
254     }
255 
256     @Test(timeout = 600_000L)
commit_threeSingleApkSessions_untilFinishBenchmark()257     public void commit_threeSingleApkSessions_untilFinishBenchmark() throws Exception {
258         uninstall(false /* stop at fail */, TestApp.A, TestApp.B, TestApp.C);
259 
260         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
261         while (state.keepRunning()) {
262             List<PackageInstaller.Session> sessions = createSinglePackageSessions(
263                     state, true, TestApp.A1, TestApp.B1, TestApp.C1);
264 
265             for (PackageInstaller.Session session : sessions) {
266                 session.commit(mIntentSender);
267             }
268             mCountDownLatch.await(1, TimeUnit.MINUTES);
269 
270             uninstallSession(state, TestApp.A, TestApp.B, TestApp.C);
271         }
272     }
273 
274     @Test(timeout = 600_000L)
commit_aMultiplePackagesSession_untilFinishBenchmark()275     public void commit_aMultiplePackagesSession_untilFinishBenchmark() throws Exception {
276         uninstall(false /* stop at fail */, TestApp.A, TestApp.B, TestApp.C);
277 
278         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
279         final List<TestApp[]> multiPackageApps = new ArrayList<>();
280         multiPackageApps.add(new TestApp[] {TestApp.A1, TestApp.B1, TestApp.C1});
281 
282         while (state.keepRunning()) {
283             List<PackageInstaller.Session> sessions = createMultiplePackageSessions(
284                     state, true, multiPackageApps);
285 
286             for (PackageInstaller.Session session : sessions) {
287                 session.commit(mIntentSender);
288             }
289             mCountDownLatch.await(1, TimeUnit.MINUTES);
290 
291             uninstallSession(state, TestApp.A, TestApp.B, TestApp.C);
292         }
293     }
294 
295     @Test(timeout = 600_000L)
commit_threeMultiplePackageSessions_untilFinishBenchmark()296     public void commit_threeMultiplePackageSessions_untilFinishBenchmark() throws Exception {
297         uninstall(false /* stop at fail */, TestApp.A, TestApp.B, TestApp.C);
298 
299         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
300         final List<TestApp[]> multiPackageApps = new ArrayList<>();
301         multiPackageApps.add(new TestApp[] {TestApp.A1});
302         multiPackageApps.add(new TestApp[] {TestApp.B1});
303         multiPackageApps.add(new TestApp[] {TestApp.C1});
304 
305         while (state.keepRunning()) {
306             List<PackageInstaller.Session> sessions = createMultiplePackageSessions(
307                     state, true, multiPackageApps);
308 
309             for (PackageInstaller.Session session : sessions) {
310                 session.commit(mIntentSender);
311             }
312             mCountDownLatch.await(1, TimeUnit.MINUTES);
313 
314             uninstallSession(state, TestApp.A, TestApp.B, TestApp.C);
315         }
316     }
317 
318     @Test(timeout = 600_000L)
commit_aMultipleApksSession_untilFinishBenchmark()319     public void commit_aMultipleApksSession_untilFinishBenchmark() throws Exception {
320         uninstall(false /* stop at fail */, TestApp.A);
321 
322         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
323         while (state.keepRunning()) {
324             List<PackageInstaller.Session> sessions = createSinglePackageSessions(
325                     state, true, TestApp.ASplit1);
326 
327             for (PackageInstaller.Session session : sessions) {
328                 session.commit(mIntentSender);
329             }
330             mCountDownLatch.await(1, TimeUnit.MINUTES);
331 
332             uninstallSession(state, TestApp.A);
333         }
334     }
335 }
336