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