• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.packageinstaller.cts.cuj.installer;
18 
19 import static android.app.PendingIntent.FLAG_MUTABLE;
20 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
21 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
22 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
23 import static android.content.pm.PackageInstaller.EXTRA_STATUS;
24 import static android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID;
25 import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
26 
27 import android.app.Activity;
28 import android.app.PendingIntent;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.IntentSender;
34 import android.content.pm.PackageInstaller;
35 import android.content.pm.ResolveInfo;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.text.TextUtils;
39 import android.util.Log;
40 
41 import androidx.annotation.NonNull;
42 import androidx.core.content.FileProvider;
43 
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.OutputStream;
50 import java.util.List;
51 
52 public class MainActivity extends Activity {
53 
54     private static final String TAG = "CtsPIACujTestInstaller";
55     private static final String INSTALLER_APK_V2_NAME = "CtsInstallerCujTestInstallerV2.apk";
56     private static final String TEST_APP_PACKAGE_NAME =
57             "android.packageinstaller.cts.cuj.app";
58     private static final String TEST_APK_NAME = "CtsInstallerCujTestApp.apk";
59     private static final String TEST_APK_V2_NAME = "CtsInstallerCujTestAppV2.apk";
60     public static final String TEST_NO_LAUNCHER_ACTIVITY_APK_NAME =
61             "CtsInstallerCujTestNoLauncherActivityApp.apk";
62     public static final String TEST_NO_LAUNCHER_ACTIVITY_APK_V2_NAME =
63             "CtsInstallerCujTestNoLauncherActivityAppV2.apk";
64 
65     private static final String CONTENT_AUTHORITY =
66             "android.packageinstaller.cts.cuj.installer.fileprovider";
67 
68     private static final String TEST_PACKAGE_NAME =
69             "android.packageinstaller.criticaluserjourney.cts";
70     private static final String ACTION_LAUNCH_INSTALLER =
71             "android.packageinstaller.cts.cuj.installer.action.LAUNCH_INSTALLER";
72     private static final String ACTION_REQUEST_INSTALLER =
73             "android.packageinstaller.cts.cuj.installer.action.REQUEST_INSTALLER";
74     private static final String ACTION_RESPONSE_INSTALLER =
75             "android.packageinstaller.cts.cuj.installer.action.RESPONSE_INSTALLER";
76     private static final String ACTION_INSTALL_RESULT =
77             "android.packageinstaller.cts.cuj.installer.action.INSTALL_RESULT";
78     private static final String EXTRA_EVENT = "extra_event";
79     private static final String EXTRA_INSTALLER_APK_V2_URI = "extra_installer_apk_v2_uri";
80     private static final String EXTRA_TEST_APK_URI = "extra_test_apk_uri";
81     private static final String EXTRA_TEST_APK_V2_URI = "extra_test_apk_v2_uri";
82     private static final String EXTRA_TEST_NO_LAUNCHER_ACTIVITY_APK_URI =
83             "extra_test_no_launcher_activity_apk_uri";
84     private static final String EXTRA_TEST_NO_LAUNCHER_ACTIVITY_APK_V2_URI =
85             "extra_test_no_launcher_activity_apk_v2_uri";
86     private static final String EXTRA_TEST_PACKAGE_NAME = "extra_test_package_name";
87 
88     private static final String EXTRA_IS_UPDATE = "extra_is_update";
89     private static final String EXTRA_NO_LAUNCHER_ACTIVITY_TEST_APP =
90             "extra_no_launcher_activity_test_app";
91     private static final String EXTRA_USE_TEST_APP = "extra_use_test_app";
92 
93     private static final String APK_MIME_TYPE = "application/vnd.android.package-archive";
94 
95     private static final int STATUS_CUJ_INSTALLER_READY = 1000;
96     private static final int STATUS_CUJ_INSTALLER_START_ACTIVITY_READY = 1001;
97     private static final int EVENT_REQUEST_INSTALLER_CLEAN_UP = -1;
98     private static final int EVENT_REQUEST_INSTALLER_SESSION = 0;
99     private static final int EVENT_REQUEST_INSTALLER_INTENT = 1;
100     private static final int EVENT_REQUEST_INSTALLER_INTENT_FOR_RESULT = 2;
101     private static final int EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI = 3;
102     private static final int EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI_FOR_RESULT = 4;
103     private static final int EVENT_REQUEST_INSTALLER_INTENT_WITH_ACTION_VIEW = 5;
104     private static final int REQUEST_CODE = 311;
105     private static String sTestPackageName;
106     private static String sSystemPackageInstallerPackageName;
107 
108     private PackageInstaller mPackageInstaller;
109     private RequestInstallerReceiver mRequestInstallerReceiver;
110     private boolean mNotifyReady = true;
111 
112     @Override
onCreate(Bundle savedInstanceState)113     protected void onCreate(Bundle savedInstanceState) {
114         super.onCreate(savedInstanceState);
115 
116         if (TextUtils.equals(getIntent().getAction(), ACTION_LAUNCH_INSTALLER)) {
117             mPackageInstaller = getPackageManager().getPackageInstaller();
118             mRequestInstallerReceiver = new RequestInstallerReceiver();
119             getApplicationContext().registerReceiver(mRequestInstallerReceiver,
120                     new IntentFilter(ACTION_REQUEST_INSTALLER), Context.RECEIVER_EXPORTED);
121             sTestPackageName = getIntent().getStringExtra(EXTRA_TEST_PACKAGE_NAME);
122             copyTestFiles();
123         }
124     }
125 
126     @Override
onResume()127     protected void onResume() {
128         super.onResume();
129         if (mNotifyReady) {
130             mNotifyReady = false;
131             sendInstallerResponseBroadcast(getApplicationContext(), STATUS_CUJ_INSTALLER_READY);
132         }
133     }
134 
135     @Override
onDestroy()136     protected void onDestroy() {
137         super.onDestroy();
138         if (mRequestInstallerReceiver != null) {
139             getApplicationContext().unregisterReceiver(mRequestInstallerReceiver);
140             mRequestInstallerReceiver = null;
141         }
142         sTestPackageName = null;
143     }
144 
cleanUp()145     private void cleanUp() {
146         cleanUpSessions();
147         getApplicationContext().unregisterReceiver(mRequestInstallerReceiver);
148     }
149 
cleanUpSessions()150     private void cleanUpSessions() {
151         List<PackageInstaller.SessionInfo> sessionInfoList = mPackageInstaller.getMySessions();
152         Log.d(TAG, "cleanUpSessions size = " + sessionInfoList.size());
153         for (int i = 0; i < sessionInfoList.size(); i++) {
154             try {
155                 mPackageInstaller.abandonSession(sessionInfoList.get(i).getSessionId());
156             } catch (Exception ignored) {
157                 // do nothing
158             }
159         }
160     }
161 
getIntentSender(Context context)162     private static IntentSender getIntentSender(Context context) {
163         Intent intent = new Intent(ACTION_INSTALL_RESULT).setPackage(context.getPackageName())
164                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
165         PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent,
166                 FLAG_UPDATE_CURRENT | FLAG_MUTABLE);
167         return pending.getIntentSender();
168     }
169 
170     @Override
onActivityResult(int requestCode, int resultCode, Intent data)171     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
172         Log.d(TAG, "onActivityResult requestCode: " + requestCode + ", resultCode: " + resultCode
173                 + ", data: " + data);
174 
175         if (requestCode == REQUEST_CODE) {
176             sendInstallerResponseBroadcast(getApplicationContext(), resultCode);
177         }
178     }
179 
sendInstallerResponseBroadcast(Context context, int status)180     private static void sendInstallerResponseBroadcast(Context context, int status) {
181         final Intent intent = new Intent(ACTION_RESPONSE_INSTALLER);
182         intent.setPackage(sTestPackageName);
183         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
184         intent.putExtra(EXTRA_STATUS, status);
185         context.sendBroadcast(intent);
186     }
187 
copyTestFile(@onNull String extraKey, @NonNull String testApkName)188     private void copyTestFile(@NonNull String extraKey, @NonNull String testApkName)
189             throws Exception {
190         Uri testApkUri = Uri.parse(getIntent().getStringExtra(extraKey));
191         copyApkFromUri(testApkUri, testApkName);
192     }
193 
copyTestFiles()194     private void copyTestFiles() {
195         try {
196             copyTestFile(EXTRA_INSTALLER_APK_V2_URI, INSTALLER_APK_V2_NAME);
197             copyTestFile(EXTRA_TEST_APK_URI, TEST_APK_NAME);
198             copyTestFile(EXTRA_TEST_APK_V2_URI, TEST_APK_V2_NAME);
199             copyTestFile(EXTRA_TEST_NO_LAUNCHER_ACTIVITY_APK_URI,
200                     TEST_NO_LAUNCHER_ACTIVITY_APK_NAME);
201             copyTestFile(EXTRA_TEST_NO_LAUNCHER_ACTIVITY_APK_V2_URI,
202                     TEST_NO_LAUNCHER_ACTIVITY_APK_V2_NAME);
203         } catch (Exception ex) {
204             Log.e(TAG, "Copy test apks from uri failed." , ex);
205             mNotifyReady = false;
206         }
207     }
208 
copyApkFromUri(Uri uri, String apkName)209     private void copyApkFromUri(Uri uri, String apkName) throws Exception {
210         File file = new File(getFilesDir(), apkName);
211         try (InputStream source = getContentResolver().openInputStream(uri);
212                 OutputStream target = new FileOutputStream(file)) {
213 
214             byte[] buffer = new byte[1024];
215             for (int len = source.read(buffer); len > 0; len = source.read(buffer)) {
216                 target.write(buffer, 0, len);
217             }
218         }
219     }
220 
startInstallationViaPackageInstallerSession(String apkName, String packageName)221     private void startInstallationViaPackageInstallerSession(String apkName, String packageName)
222             throws Exception {
223         final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
224                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
225         params.setAppPackageName(packageName);
226 
227         final int sessionId = mPackageInstaller.createSession(params);
228 
229         final PackageInstaller.Session session = mPackageInstaller.openSession(sessionId);
230         final File apkFile = new File(getFilesDir(), apkName);
231         try (OutputStream os = session.openWrite("base.apk", 0, apkFile.length());
232                 InputStream is = new FileInputStream(apkFile)) {
233             writeFullStream(is, os);
234         }
235 
236         session.commit(getIntentSender(getApplicationContext()));
237     }
238 
startInstallationViaIntentWithActionView(String apkName)239     private void startInstallationViaIntentWithActionView(String apkName) {
240         final File apkFile = new File(getFilesDir(), apkName);
241         final Intent intent = new Intent(Intent.ACTION_VIEW);
242         intent.setDataAndType(FileProvider.getUriForFile(this, CONTENT_AUTHORITY, apkFile),
243                 APK_MIME_TYPE);
244         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
245         intent.setPackage(getPackageInstallerPackageName(this));
246         startActivity(intent);
247         sendInstallerResponseBroadcast(getApplicationContext(),
248                 STATUS_CUJ_INSTALLER_START_ACTIVITY_READY);
249     }
250 
startInstallationViaIntent(boolean getResult, String apkName)251     private void startInstallationViaIntent(boolean getResult, String apkName) {
252         final File apkFile = new File(getFilesDir(), apkName);
253         final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
254         intent.setData(FileProvider.getUriForFile(this, CONTENT_AUTHORITY, apkFile));
255         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
256         intent.putExtra(Intent.EXTRA_RETURN_RESULT, getResult);
257         if (getResult) {
258             startActivityForResult(intent, REQUEST_CODE);
259         } else {
260             startActivity(intent);
261         }
262         sendInstallerResponseBroadcast(getApplicationContext(),
263                 STATUS_CUJ_INSTALLER_START_ACTIVITY_READY);
264     }
265 
startInstallationViaIntentWithPackageUri(boolean getResult)266     private void startInstallationViaIntentWithPackageUri(boolean getResult) {
267         final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
268         intent.setData(Uri.fromParts("package", TEST_APP_PACKAGE_NAME, null));
269         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
270         intent.putExtra(Intent.EXTRA_RETURN_RESULT, getResult);
271         if (getResult) {
272             startActivityForResult(intent, REQUEST_CODE);
273         } else {
274             startActivity(intent);
275         }
276         sendInstallerResponseBroadcast(getApplicationContext(),
277                 STATUS_CUJ_INSTALLER_START_ACTIVITY_READY);
278     }
279 
writeFullStream(InputStream inputStream, OutputStream outputStream)280     private static void writeFullStream(InputStream inputStream, OutputStream outputStream)
281             throws IOException {
282         byte[] buffer = new byte[1024];
283         int length;
284         while ((length = inputStream.read(buffer)) != -1) {
285             outputStream.write(buffer, 0, length);
286         }
287     }
288 
getPackageInstallerPackageName(Context context)289     private static String getPackageInstallerPackageName(Context context) {
290         if (sSystemPackageInstallerPackageName != null) {
291             return sSystemPackageInstallerPackageName;
292         }
293 
294         final Intent intent =
295                 new Intent(Intent.ACTION_INSTALL_PACKAGE).setData(Uri.parse("content:"));
296         final ResolveInfo ri = context.getPackageManager().resolveActivity(intent, /* flags= */ 0);
297         sSystemPackageInstallerPackageName = ri != null ? ri.activityInfo.packageName : null;
298         Log.d(TAG, "packageInstallerPackageName = " + sSystemPackageInstallerPackageName);
299         return sSystemPackageInstallerPackageName;
300     }
301 
302     public static class InstallResultReceiver extends BroadcastReceiver {
303 
304         @Override
onReceive(Context context, Intent intent)305         public void onReceive(Context context, Intent intent) {
306             Log.i(TAG, "InstallResultReceiver Received intent " + prettyPrint(intent));
307             final int status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);
308             sendInstallerResponseBroadcast(context, status);
309             if (status == STATUS_PENDING_USER_ACTION) {
310                 Intent extraIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
311                 extraIntent.addFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
312                 context.startActivity(extraIntent);
313             }
314         }
315 
prettyPrint(Intent intent)316         private static String prettyPrint(Intent intent) {
317             int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
318             int status = intent.getIntExtra(EXTRA_STATUS,
319                     PackageInstaller.STATUS_FAILURE);
320             String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
321             return String.format("%s: {\n"
322                     + "sessionId = %d\n"
323                     + "status = %d\n"
324                     + "message = %s\n"
325                     + "}", intent, sessionId, status, message);
326         }
327     }
328 
329     private class RequestInstallerReceiver extends BroadcastReceiver {
330         @Override
onReceive(Context context, Intent intent)331         public void onReceive(Context context, Intent intent) {
332             final int event = intent.getIntExtra(EXTRA_EVENT, /* defaultValue= */ -1);
333             final boolean isNoLauncherActivityTestApp = intent.getBooleanExtra(
334                     EXTRA_NO_LAUNCHER_ACTIVITY_TEST_APP, /* defaultValue= */ false);
335             final boolean isUpdate = intent.getBooleanExtra(EXTRA_IS_UPDATE,
336                     /* defaultValue= */ false);
337             final boolean useTestApp = intent.getBooleanExtra(EXTRA_USE_TEST_APP,
338                     /* defaultValue= */ false);
339             Log.i(TAG, "RequestInstallerReceiver Received intent " + intent
340                     + ", event: " + event + ", isUpdate:" + isUpdate
341                     + ", useTestApp:" + useTestApp
342                     + ", isNoLauncherActivityTestApp: " + isNoLauncherActivityTestApp);
343 
344             final String testApkName = getTestApkName(isNoLauncherActivityTestApp,
345                     isUpdate, useTestApp);
346 
347             if (event == EVENT_REQUEST_INSTALLER_CLEAN_UP) {
348                 cleanUp();
349             } else if (event == EVENT_REQUEST_INSTALLER_SESSION) {
350                 final String packageName = useTestApp ? TEST_APP_PACKAGE_NAME : getPackageName();
351                 try {
352                     startInstallationViaPackageInstallerSession(testApkName, packageName);
353                 } catch (Exception ex) {
354                     Log.e(TAG, "Exception event:" + event, ex);
355                 }
356             } else if (event == EVENT_REQUEST_INSTALLER_INTENT) {
357                 try {
358                     startInstallationViaIntent(/* getResult= */ false, testApkName);
359                 } catch (Exception ex) {
360                     Log.e(TAG, "Exception event:" + event, ex);
361                 }
362             } else if (event == EVENT_REQUEST_INSTALLER_INTENT_FOR_RESULT) {
363                 try {
364                     startInstallationViaIntent(/* getResult= */ true, testApkName);
365                 } catch (Exception ex) {
366                     Log.e(TAG, "Exception event:" + event, ex);
367                 }
368             } else if (event == EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI) {
369                 try {
370                     startInstallationViaIntentWithPackageUri(/* getResult= */ false);
371                 } catch (Exception ex) {
372                     Log.e(TAG, "Exception event:" + event, ex);
373                 }
374             } else if (event == EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI_FOR_RESULT) {
375                 try {
376                     startInstallationViaIntentWithPackageUri(/* getResult= */ true);
377                 } catch (Exception ex) {
378                     Log.e(TAG, "Exception event:" + event, ex);
379                 }
380             } else if (event == EVENT_REQUEST_INSTALLER_INTENT_WITH_ACTION_VIEW) {
381                 try {
382                     startInstallationViaIntentWithActionView(testApkName);
383                 } catch (Exception ex) {
384                     Log.e(TAG, "Exception event:" + event, ex);
385                 }
386             }
387         }
388 
getTestApkName(boolean isNoLauncherActivityTestApp, boolean isUpdate, boolean useTestApp)389         private static @NonNull String getTestApkName(boolean isNoLauncherActivityTestApp,
390                 boolean isUpdate, boolean useTestApp) {
391             final String testApkName;
392             if (isNoLauncherActivityTestApp) {
393                 testApkName = isUpdate ? TEST_NO_LAUNCHER_ACTIVITY_APK_V2_NAME
394                         : TEST_NO_LAUNCHER_ACTIVITY_APK_NAME;
395             } else {
396                 // If useTestApp is false, update the INSTALLER_APK_V2_NAME.
397                 // Otherwise, if isUpdate is true, update the TEST_APK_V2_NAME
398                 //            otherwise, install the TEST_APK_NAME
399                 testApkName = !useTestApp ? INSTALLER_APK_V2_NAME
400                         : isUpdate ? TEST_APK_V2_NAME : TEST_APK_NAME;
401             }
402             return testApkName;
403         }
404     }
405 }
406