1 /* 2 ** 3 ** Copyright 2007, The Android Open Source Project 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 package com.android.packageinstaller; 18 19 import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN; 20 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.Dialog; 24 import android.app.PendingIntent; 25 import android.content.ActivityNotFoundException; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.DialogInterface.OnCancelListener; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageInfo; 34 import android.content.pm.PackageInstaller; 35 import android.content.pm.PackageManager; 36 import android.content.pm.PackageManager.NameNotFoundException; 37 import android.content.pm.PackageParser; 38 import android.content.pm.ResolveInfo; 39 import android.net.Uri; 40 import android.os.Bundle; 41 import android.os.Handler; 42 import android.os.HandlerThread; 43 import android.os.Message; 44 import android.util.Log; 45 import android.view.View; 46 import android.widget.Button; 47 import android.widget.ImageView; 48 import android.widget.ProgressBar; 49 import android.widget.TextView; 50 import com.android.packageinstaller.permission.utils.IoUtils; 51 52 import java.io.File; 53 import java.io.FileInputStream; 54 import java.io.IOException; 55 import java.io.InputStream; 56 import java.io.OutputStream; 57 import java.util.List; 58 59 /** 60 * This activity corresponds to a download progress screen that is displayed 61 * when the user tries 62 * to install an application bundled as an apk file. The result of the application install 63 * is indicated in the result code that gets set to the corresponding installation status 64 * codes defined in PackageManager. If the package being installed already exists, 65 * the existing package is replaced with the new one. 66 */ 67 public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener { 68 private final String TAG="InstallAppProgress"; 69 private static final String BROADCAST_ACTION = 70 "com.android.packageinstaller.ACTION_INSTALL_COMMIT"; 71 private static final String BROADCAST_SENDER_PERMISSION = 72 "android.permission.INSTALL_PACKAGES"; 73 private ApplicationInfo mAppInfo; 74 private Uri mPackageURI; 75 private ProgressBar mProgressBar; 76 private View mOkPanel; 77 private TextView mStatusTextView; 78 private TextView mExplanationTextView; 79 private Button mDoneButton; 80 private Button mLaunchButton; 81 private final int INSTALL_COMPLETE = 1; 82 private Intent mLaunchIntent; 83 private static final int DLG_OUT_OF_SPACE = 1; 84 private CharSequence mLabel; 85 private HandlerThread mInstallThread; 86 private Handler mInstallHandler; 87 88 private Handler mHandler = new Handler() { 89 public void handleMessage(Message msg) { 90 switch (msg.what) { 91 case INSTALL_COMPLETE: 92 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 93 Intent result = new Intent(); 94 result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1); 95 setResult(msg.arg1 == PackageInstaller.STATUS_SUCCESS 96 ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER, 97 result); 98 clearCachedApkIfNeededAndFinish(); 99 return; 100 } 101 // Update the status text 102 mProgressBar.setVisibility(View.GONE); 103 // Show the ok button 104 int centerTextLabel; 105 int centerExplanationLabel = -1; 106 if (msg.arg1 == PackageInstaller.STATUS_SUCCESS) { 107 mLaunchButton.setVisibility(View.VISIBLE); 108 ((ImageView)findViewById(R.id.center_icon)) 109 .setImageDrawable(getDrawable(R.drawable.ic_done_92)); 110 centerTextLabel = R.string.install_done; 111 // Enable or disable launch button 112 mLaunchIntent = getPackageManager().getLaunchIntentForPackage( 113 mAppInfo.packageName); 114 boolean enabled = false; 115 if(mLaunchIntent != null) { 116 List<ResolveInfo> list = getPackageManager(). 117 queryIntentActivities(mLaunchIntent, 0); 118 if (list != null && list.size() > 0) { 119 enabled = true; 120 } 121 } 122 if (enabled) { 123 mLaunchButton.setOnClickListener(InstallAppProgress.this); 124 } else { 125 mLaunchButton.setEnabled(false); 126 } 127 } else if (msg.arg1 == PackageInstaller.STATUS_FAILURE_STORAGE){ 128 showDialogInner(DLG_OUT_OF_SPACE); 129 return; 130 } else { 131 // Generic error handling for all other error codes. 132 ((ImageView)findViewById(R.id.center_icon)) 133 .setImageDrawable(getDrawable(R.drawable.ic_report_problem_92)); 134 centerExplanationLabel = getExplanationFromErrorCode(msg.arg1); 135 centerTextLabel = R.string.install_failed; 136 mLaunchButton.setVisibility(View.GONE); 137 } 138 if (centerExplanationLabel != -1) { 139 mExplanationTextView.setText(centerExplanationLabel); 140 findViewById(R.id.center_view).setVisibility(View.GONE); 141 ((TextView)findViewById(R.id.explanation_status)).setText(centerTextLabel); 142 findViewById(R.id.explanation_view).setVisibility(View.VISIBLE); 143 } else { 144 ((TextView)findViewById(R.id.center_text)).setText(centerTextLabel); 145 findViewById(R.id.center_view).setVisibility(View.VISIBLE); 146 findViewById(R.id.explanation_view).setVisibility(View.GONE); 147 } 148 mDoneButton.setOnClickListener(InstallAppProgress.this); 149 mOkPanel.setVisibility(View.VISIBLE); 150 break; 151 default: 152 break; 153 } 154 } 155 }; 156 157 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 158 @Override 159 public void onReceive(Context context, Intent intent) { 160 final int statusCode = intent.getIntExtra( 161 PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); 162 if (statusCode == PackageInstaller.STATUS_PENDING_USER_ACTION) { 163 context.startActivity((Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT)); 164 } else { 165 onPackageInstalled(statusCode); 166 } 167 } 168 }; 169 getExplanationFromErrorCode(int errCode)170 private int getExplanationFromErrorCode(int errCode) { 171 Log.d(TAG, "Installation error code: " + errCode); 172 switch (errCode) { 173 case PackageInstaller.STATUS_FAILURE_BLOCKED: 174 return R.string.install_failed_blocked; 175 case PackageInstaller.STATUS_FAILURE_CONFLICT: 176 return R.string.install_failed_conflict; 177 case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE: 178 return R.string.install_failed_incompatible; 179 case PackageInstaller.STATUS_FAILURE_INVALID: 180 return R.string.install_failed_invalid_apk; 181 default: 182 return -1; 183 } 184 } 185 186 @Override onCreate(Bundle icicle)187 public void onCreate(Bundle icicle) { 188 super.onCreate(icicle); 189 Intent intent = getIntent(); 190 mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 191 mPackageURI = intent.getData(); 192 193 final String scheme = mPackageURI.getScheme(); 194 if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) { 195 throw new IllegalArgumentException("unexpected scheme " + scheme); 196 } 197 198 mInstallThread = new HandlerThread("InstallThread"); 199 mInstallThread.start(); 200 mInstallHandler = new Handler(mInstallThread.getLooper()); 201 202 IntentFilter intentFilter = new IntentFilter(); 203 intentFilter.addAction(BROADCAST_ACTION); 204 registerReceiver( 205 mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/); 206 207 initView(); 208 } 209 210 @Override onBackPressed()211 public void onBackPressed() { 212 clearCachedApkIfNeededAndFinish(); 213 } 214 215 @SuppressWarnings("deprecation") 216 @Override onCreateDialog(int id, Bundle bundle)217 public Dialog onCreateDialog(int id, Bundle bundle) { 218 switch (id) { 219 case DLG_OUT_OF_SPACE: 220 String dlgText = getString(R.string.out_of_space_dlg_text, mLabel); 221 return new AlertDialog.Builder(this) 222 .setTitle(R.string.out_of_space_dlg_title) 223 .setMessage(dlgText) 224 .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() { 225 public void onClick(DialogInterface dialog, int which) { 226 //launch manage applications 227 Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); 228 startActivity(intent); 229 clearCachedApkIfNeededAndFinish(); 230 } 231 }) 232 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 233 public void onClick(DialogInterface dialog, int which) { 234 Log.i(TAG, "Canceling installation"); 235 clearCachedApkIfNeededAndFinish(); 236 } 237 }) 238 .setOnCancelListener(this) 239 .create(); 240 } 241 return null; 242 } 243 244 @SuppressWarnings("deprecation") 245 private void showDialogInner(int id) { 246 removeDialog(id); 247 showDialog(id); 248 } 249 250 void onPackageInstalled(int statusCode) { 251 Message msg = mHandler.obtainMessage(INSTALL_COMPLETE); 252 msg.arg1 = statusCode; 253 mHandler.sendMessage(msg); 254 } 255 256 int getInstallFlags(String packageName) { 257 PackageManager pm = getPackageManager(); 258 try { 259 PackageInfo pi = 260 pm.getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES); 261 if (pi != null) { 262 return PackageManager.INSTALL_REPLACE_EXISTING; 263 } 264 } catch (NameNotFoundException e) { 265 } 266 return 0; 267 } 268 269 private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) { 270 final PackageInstaller packageInstaller = pm.getPackageInstaller(); 271 PackageInstaller.Session session = null; 272 try { 273 final String packageLocation = mPackageURI.getPath(); 274 final File file = new File(packageLocation); 275 final int sessionId = packageInstaller.createSession(params); 276 final byte[] buffer = new byte[65536]; 277 278 session = packageInstaller.openSession(sessionId); 279 280 final InputStream in = new FileInputStream(file); 281 final long sizeBytes = file.length(); 282 final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes); 283 try { 284 int c; 285 while ((c = in.read(buffer)) != -1) { 286 out.write(buffer, 0, c); 287 if (sizeBytes > 0) { 288 final float fraction = ((float) c / (float) sizeBytes); 289 session.addProgress(fraction); 290 } 291 } 292 session.fsync(out); 293 } finally { 294 IoUtils.closeQuietly(in); 295 IoUtils.closeQuietly(out); 296 } 297 298 // Create a PendingIntent and use it to generate the IntentSender 299 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 300 PendingIntent pendingIntent = PendingIntent.getBroadcast( 301 InstallAppProgress.this /*context*/, 302 sessionId, 303 broadcastIntent, 304 PendingIntent.FLAG_UPDATE_CURRENT); 305 session.commit(pendingIntent.getIntentSender()); 306 } catch (IOException e) { 307 onPackageInstalled(PackageInstaller.STATUS_FAILURE); 308 } finally { 309 IoUtils.closeQuietly(session); 310 } 311 } 312 313 void initView() { 314 setContentView(R.layout.op_progress); 315 316 final PackageUtil.AppSnippet as; 317 final PackageManager pm = getPackageManager(); 318 final int installFlags = getInstallFlags(mAppInfo.packageName); 319 320 if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) { 321 Log.w(TAG, "Replacing package:" + mAppInfo.packageName); 322 } 323 if ("package".equals(mPackageURI.getScheme())) { 324 as = new PackageUtil.AppSnippet(pm.getApplicationLabel(mAppInfo), 325 pm.getApplicationIcon(mAppInfo)); 326 } else { 327 final File sourceFile = new File(mPackageURI.getPath()); 328 as = PackageUtil.getAppSnippet(this, mAppInfo, sourceFile); 329 } 330 mLabel = as.label; 331 PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet); 332 mStatusTextView = (TextView)findViewById(R.id.center_text); 333 mExplanationTextView = (TextView) findViewById(R.id.explanation); 334 mProgressBar = (ProgressBar) findViewById(R.id.progress_bar); 335 mProgressBar.setIndeterminate(true); 336 // Hide button till progress is being displayed 337 mOkPanel = findViewById(R.id.buttons_panel); 338 mDoneButton = (Button)findViewById(R.id.done_button); 339 mLaunchButton = (Button)findViewById(R.id.launch_button); 340 mOkPanel.setVisibility(View.INVISIBLE); 341 342 if ("package".equals(mPackageURI.getScheme())) { 343 try { 344 pm.installExistingPackage(mAppInfo.packageName); 345 onPackageInstalled(PackageInstaller.STATUS_SUCCESS); 346 } catch (PackageManager.NameNotFoundException e) { 347 onPackageInstalled(PackageInstaller.STATUS_FAILURE_INVALID); 348 } 349 } else { 350 final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 351 PackageInstaller.SessionParams.MODE_FULL_INSTALL); 352 params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER); 353 params.originatingUri = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); 354 params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, 355 UID_UNKNOWN); 356 357 File file = new File(mPackageURI.getPath()); 358 try { 359 params.setInstallLocation(PackageParser.parsePackageLite(file, 0).installLocation); 360 } catch (PackageParser.PackageParserException e) { 361 Log.e(TAG, "Cannot parse package " + file + ". Assuming defaults."); 362 } 363 364 mInstallHandler.post(new Runnable() { 365 @Override 366 public void run() { 367 doPackageStage(pm, params); 368 } 369 }); 370 } 371 } 372 373 @Override 374 protected void onDestroy() { 375 super.onDestroy(); 376 unregisterReceiver(mBroadcastReceiver); 377 mInstallThread.getLooper().quitSafely(); 378 } 379 380 public void onClick(View v) { 381 if(v == mDoneButton) { 382 if (mAppInfo.packageName != null) { 383 Log.i(TAG, "Finished installing "+mAppInfo.packageName); 384 } 385 clearCachedApkIfNeededAndFinish(); 386 } else if(v == mLaunchButton) { 387 try { 388 startActivity(mLaunchIntent); 389 } catch (ActivityNotFoundException e) { 390 Log.e(TAG, "Could not start activity", e); 391 } 392 clearCachedApkIfNeededAndFinish(); 393 } 394 } 395 396 public void onCancel(DialogInterface dialog) { 397 clearCachedApkIfNeededAndFinish(); 398 } 399 400 private void clearCachedApkIfNeededAndFinish() { 401 // If we are installing from a content:// the apk is copied in the cache 402 // dir and passed in here. As we aren't started for a result because our 403 // caller needs to be able to forward the result, here we make sure the 404 // staging file in the cache dir is removed. 405 if ("file".equals(mPackageURI.getScheme()) && mPackageURI.getPath() != null 406 && mPackageURI.getPath().startsWith(getCacheDir().toString())) { 407 File file = new File(mPackageURI.getPath()); 408 file.delete(); 409 } 410 finish(); 411 } 412 } 413