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