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