1 /* 2 * Copyright (C) 2016 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.packageinstaller; 18 19 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_APP_SNIPPET; 20 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID; 21 22 import android.app.Activity; 23 import android.app.AlertDialog; 24 import android.app.PendingIntent; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageInstaller; 29 import android.content.pm.PackageManager; 30 import android.net.Uri; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.util.Log; 36 import android.view.View; 37 import android.widget.Button; 38 import androidx.annotation.Nullable; 39 import com.android.packageinstaller.common.EventResultPersister; 40 import com.android.packageinstaller.common.InstallEventReceiver; 41 import java.io.File; 42 import java.io.IOException; 43 44 /** 45 * Send package to the package manager and handle results from package manager. Once the 46 * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}. 47 * <p>This has two phases: First send the data to the package manager, then wait until the package 48 * manager processed the result.</p> 49 */ 50 public class InstallInstalling extends Activity { 51 private static final String LOG_TAG = InstallInstalling.class.getSimpleName(); 52 53 private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID"; 54 private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID"; 55 56 private static final String BROADCAST_ACTION = 57 "com.android.packageinstaller.ACTION_INSTALL_COMMIT"; 58 59 /** Task that sends the package to the package installer */ 60 private InstallingAsyncTask mInstallingTask; 61 62 /** Id of the session to install the package */ 63 private int mSessionId; 64 65 /** Id of the install event we wait for */ 66 private int mInstallId; 67 68 /** URI of package to install */ 69 private Uri mPackageURI; 70 71 /** The button that can cancel this dialog */ 72 private Button mCancelButton; 73 74 private AlertDialog mDialog; 75 76 @Override onCreate(@ullable Bundle savedInstanceState)77 protected void onCreate(@Nullable Bundle savedInstanceState) { 78 super.onCreate(savedInstanceState); 79 80 ApplicationInfo appInfo = getIntent() 81 .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 82 mPackageURI = getIntent().getData(); 83 84 if (PackageInstallerActivity.SCHEME_PACKAGE.equals(mPackageURI.getScheme())) { 85 try { 86 getPackageManager().installExistingPackage(appInfo.packageName); 87 launchSuccess(); 88 } catch (PackageManager.NameNotFoundException e) { 89 launchFailure(PackageInstaller.STATUS_FAILURE, 90 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 91 } 92 } else { 93 // ContentResolver.SCHEME_FILE 94 // STAGED_SESSION_ID extra contains an ID of a previously staged install session. 95 final File sourceFile = new File(mPackageURI.getPath()); 96 97 // Dialogs displayed while changing update-owner have a blank icon. To fix this, 98 // fetch the appSnippet from the source file again 99 PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile); 100 getIntent().putExtra(EXTRA_APP_SNIPPET, as); 101 102 AlertDialog.Builder builder = new AlertDialog.Builder(this); 103 104 builder.setIcon(as.icon); 105 builder.setTitle(as.label); 106 builder.setView(R.layout.install_content_view); 107 builder.setNegativeButton(getString(R.string.cancel), 108 (ignored, ignored2) -> { 109 if (mInstallingTask != null) { 110 mInstallingTask.cancel(true); 111 } 112 113 if (mSessionId > 0) { 114 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 115 mSessionId = 0; 116 } 117 118 setResult(RESULT_CANCELED); 119 finish(); 120 }); 121 builder.setCancelable(false); 122 mDialog = builder.create(); 123 mDialog.show(); 124 mDialog.requireViewById(R.id.installing).setVisibility(View.VISIBLE); 125 126 if (savedInstanceState != null) { 127 mSessionId = savedInstanceState.getInt(SESSION_ID); 128 mInstallId = savedInstanceState.getInt(INSTALL_ID); 129 130 // Reregister for result; might instantly call back if result was delivered while 131 // activity was destroyed 132 try { 133 InstallEventReceiver.addObserver(this, mInstallId, 134 this::launchFinishBasedOnResult); 135 } catch (EventResultPersister.OutOfIdsException e) { 136 // Does not happen 137 } 138 } else { 139 try { 140 mInstallId = InstallEventReceiver 141 .addObserver(this, EventResultPersister.GENERATE_NEW_ID, 142 this::launchFinishBasedOnResult); 143 } catch (EventResultPersister.OutOfIdsException e) { 144 launchFailure(PackageInstaller.STATUS_FAILURE, 145 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 146 } 147 148 mSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0); 149 // Try to open session previously staged in InstallStaging. 150 try (PackageInstaller.Session ignored = 151 getPackageManager().getPackageInstaller().openSession( 152 mSessionId)) { 153 Log.d(LOG_TAG, "Staged session is valid, proceeding with the install"); 154 } catch (IOException | SecurityException e) { 155 Log.e(LOG_TAG, "Invalid session id passed", e); 156 launchFailure(PackageInstaller.STATUS_FAILURE, 157 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 158 } 159 } 160 161 mCancelButton = mDialog.getButton(DialogInterface.BUTTON_NEGATIVE); 162 } 163 } 164 165 /** 166 * Launch the "success" version of the final package installer dialog 167 */ launchSuccess()168 private void launchSuccess() { 169 Intent successIntent = new Intent(getIntent()); 170 successIntent.setClass(this, InstallSuccess.class); 171 successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 172 173 startActivity(successIntent); 174 finish(); 175 } 176 177 /** 178 * Launch the "failure" version of the final package installer dialog 179 * 180 * @param statusCode The generic status code as returned by the package installer. 181 * @param legacyStatus The status as used internally in the package manager. 182 * @param statusMessage The status description. 183 */ launchFailure(int statusCode, int legacyStatus, String statusMessage)184 private void launchFailure(int statusCode, int legacyStatus, String statusMessage) { 185 Intent failureIntent = new Intent(getIntent()); 186 failureIntent.setClass(this, InstallFailed.class); 187 failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 188 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS, statusCode); 189 failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus); 190 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage); 191 192 startActivity(failureIntent); 193 finish(); 194 } 195 196 @Override onResume()197 protected void onResume() { 198 super.onResume(); 199 200 // This is the first onResume in a single life of the activity 201 if (mInstallingTask == null) { 202 PackageInstaller installer = getPackageManager().getPackageInstaller(); 203 PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); 204 205 if (sessionInfo != null && !sessionInfo.isActive()) { 206 mInstallingTask = new InstallingAsyncTask(); 207 mInstallingTask.execute(); 208 } else { 209 // we will receive a broadcast when the install is finished 210 mCancelButton.setEnabled(false); 211 setFinishOnTouchOutside(false); 212 } 213 } 214 } 215 216 @Override onSaveInstanceState(Bundle outState)217 protected void onSaveInstanceState(Bundle outState) { 218 super.onSaveInstanceState(outState); 219 220 outState.putInt(SESSION_ID, mSessionId); 221 outState.putInt(INSTALL_ID, mInstallId); 222 } 223 224 @Override onBackPressed()225 public void onBackPressed() { 226 if (mCancelButton.isEnabled()) { 227 super.onBackPressed(); 228 } 229 } 230 231 @Override onDestroy()232 protected void onDestroy() { 233 if (mInstallingTask != null) { 234 mInstallingTask.cancel(true); 235 synchronized (mInstallingTask) { 236 while (!mInstallingTask.isDone) { 237 try { 238 mInstallingTask.wait(); 239 } catch (InterruptedException e) { 240 Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel", 241 e); 242 } 243 } 244 } 245 } 246 247 InstallEventReceiver.removeObserver(this, mInstallId); 248 249 super.onDestroy(); 250 } 251 252 @Override finish()253 public void finish() { 254 if (mDialog != null) { 255 mDialog.dismiss(); 256 } 257 super.finish(); 258 } 259 260 /** 261 * Launch the appropriate finish activity (success or failed) for the installation result. 262 * 263 * @param statusCode The installation result. 264 * @param legacyStatus The installation as used internally in the package manager. 265 * @param statusMessage The detailed installation result. 266 * @param serviceId Id for PowerManager.WakeLock service. Used only by Wear devices 267 * during an uninstall. 268 */ launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage, int serviceId )269 private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage, 270 int serviceId /* ignore */) { 271 if (statusCode == PackageInstaller.STATUS_SUCCESS) { 272 launchSuccess(); 273 } else { 274 launchFailure(statusCode, legacyStatus, statusMessage); 275 } 276 } 277 278 /** 279 * Send the package to the package installer and then register a event result observer that 280 * will call {@link #launchFinishBasedOnResult(int, int, String, int)} 281 */ 282 private final class InstallingAsyncTask extends AsyncTask<Void, Void, 283 PackageInstaller.Session> { 284 volatile boolean isDone; 285 286 @Override doInBackground(Void... params)287 protected PackageInstaller.Session doInBackground(Void... params) { 288 try { 289 return getPackageManager().getPackageInstaller().openSession(mSessionId); 290 } catch (IOException e) { 291 return null; 292 } finally { 293 synchronized (this) { 294 isDone = true; 295 notifyAll(); 296 } 297 } 298 } 299 300 @Override onPostExecute(PackageInstaller.Session session)301 protected void onPostExecute(PackageInstaller.Session session) { 302 if (session != null) { 303 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 304 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 305 broadcastIntent.setPackage(getPackageName()); 306 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); 307 308 PendingIntent pendingIntent = PendingIntent.getBroadcast( 309 InstallInstalling.this, 310 mInstallId, 311 broadcastIntent, 312 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); 313 314 // Delay committing the session by 100ms to fix a UI glitch while displaying the 315 // Update-Owner change dialog on top of the Installing dialog 316 new Handler(Looper.getMainLooper()).postDelayed(() -> { 317 try { 318 session.commit(pendingIntent.getIntentSender()); 319 } catch (Exception e) { 320 Log.e(LOG_TAG, "Cannot install package: ", e); 321 launchFailure(PackageInstaller.STATUS_FAILURE, 322 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 323 return; 324 } 325 }, 100); 326 mCancelButton.setEnabled(false); 327 setFinishOnTouchOutside(false); 328 } else { 329 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 330 331 if (!isCancelled()) { 332 launchFailure(PackageInstaller.STATUS_FAILURE, 333 PackageManager.INSTALL_FAILED_INVALID_APK, null); 334 } 335 } 336 } 337 } 338 } 339