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