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 com.android.packageinstaller.R; 20 21 import java.io.File; 22 import android.app.Activity; 23 import android.app.AlertDialog; 24 import android.app.Dialog; 25 import android.app.PendingIntent; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.DialogInterface.OnCancelListener; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageManager; 34 import android.content.pm.PackageParser; 35 import android.content.pm.PackageManager.NameNotFoundException; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.os.Handler; 39 import android.os.Message; 40 import android.provider.Settings; 41 import android.util.Log; 42 import android.view.View; 43 import android.view.Window; 44 import android.view.View.OnClickListener; 45 import android.widget.AppSecurityPermissions; 46 import android.widget.Button; 47 import android.widget.LinearLayout; 48 49 /* 50 * This activity is launched when a new application is installed via side loading 51 * The package is first parsed and the user is notified of parse errors via a dialog. 52 * If the package is successfully parsed, the user is notified to turn on the install unknown 53 * applications setting. A memory check is made at this point and the user is notified of out 54 * of memory conditions if any. If the package is already existing on the device, 55 * a confirmation dialog (to replace the existing package) is presented to the user. 56 * Based on the user response the package is then installed by launching InstallAppConfirm 57 * sub activity. All state transitions are handled in this activity 58 */ 59 public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener { 60 private static final String TAG = "PackageInstaller"; 61 private Uri mPackageURI; 62 private boolean localLOGV = false; 63 PackageManager mPm; 64 private boolean mReplacing = false; 65 private PackageParser.Package mPkgInfo; 66 private static final int SUCCEEDED = 1; 67 private static final int FAILED = 0; 68 // Broadcast receiver for clearing cache 69 ClearCacheReceiver mClearCacheReceiver = null; 70 private static final int HANDLER_BASE_MSG_IDX = 0; 71 private static final int FREE_SPACE = HANDLER_BASE_MSG_IDX + 1; 72 73 // ApplicationInfo object primarily used for already existing applications 74 private ApplicationInfo mAppInfo = null; 75 76 // View for install progress 77 View mInstallConfirm; 78 // Buttons to indicate user acceptance 79 private Button mOk; 80 private Button mCancel; 81 82 // Dialog identifiers used in showDialog 83 private static final int DLG_BASE = 0; 84 private static final int DLG_REPLACE_APP = DLG_BASE + 1; 85 private static final int DLG_UNKNOWN_APPS = DLG_BASE + 2; 86 private static final int DLG_PACKAGE_ERROR = DLG_BASE + 3; 87 private static final int DLG_OUT_OF_SPACE = DLG_BASE + 4; 88 private static final int DLG_INSTALL_ERROR = DLG_BASE + 5; 89 90 private Handler mHandler = new Handler() { 91 public void handleMessage(Message msg) { 92 switch (msg.what) { 93 case FREE_SPACE: 94 if (mClearCacheReceiver != null) { 95 unregisterReceiver(mClearCacheReceiver); 96 } 97 if(msg.arg1 == SUCCEEDED) { 98 makeTempCopyAndInstall(); 99 } else { 100 showDialogInner(DLG_OUT_OF_SPACE); 101 } 102 break; 103 default: 104 break; 105 } 106 } 107 }; 108 startInstallConfirm()109 private void startInstallConfirm() { 110 LinearLayout permsSection = (LinearLayout) mInstallConfirm.findViewById(R.id.permissions_section); 111 LinearLayout securityList = (LinearLayout) permsSection.findViewById( 112 R.id.security_settings_list); 113 boolean permVisible = false; 114 if(mPkgInfo != null) { 115 AppSecurityPermissions asp = new AppSecurityPermissions(this, mPkgInfo); 116 if(asp.getPermissionCount() > 0) { 117 permVisible = true; 118 securityList.addView(asp.getPermissionsView()); 119 } 120 } 121 if(!permVisible){ 122 permsSection.setVisibility(View.INVISIBLE); 123 } 124 mInstallConfirm.setVisibility(View.VISIBLE); 125 mOk = (Button)findViewById(R.id.ok_button); 126 mCancel = (Button)findViewById(R.id.cancel_button); 127 mOk.setOnClickListener(this); 128 mCancel.setOnClickListener(this); 129 } 130 showDialogInner(int id)131 private void showDialogInner(int id) { 132 // TODO better fix for this? Remove dialog so that it gets created again 133 removeDialog(id); 134 showDialog(id); 135 } 136 137 @Override onCreateDialog(int id)138 public Dialog onCreateDialog(int id) { 139 switch (id) { 140 case DLG_REPLACE_APP: 141 int msgId = R.string.dlg_app_replacement_statement; 142 // Customized text for system apps 143 if ((mAppInfo != null) && (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 144 msgId = R.string.dlg_sys_app_replacement_statement; 145 } 146 return new AlertDialog.Builder(this) 147 .setTitle(R.string.dlg_app_replacement_title) 148 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 149 public void onClick(DialogInterface dialog, int which) { 150 startInstallConfirm(); 151 mReplacing = true; 152 }}) 153 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 154 public void onClick(DialogInterface dialog, int which) { 155 Log.i(TAG, "Canceling installation"); 156 finish(); 157 }}) 158 .setMessage(msgId) 159 .setOnCancelListener(this) 160 .create(); 161 case DLG_UNKNOWN_APPS: 162 return new AlertDialog.Builder(this) 163 .setTitle(R.string.unknown_apps_dlg_title) 164 .setMessage(R.string.unknown_apps_dlg_text) 165 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 166 public void onClick(DialogInterface dialog, int which) { 167 Log.i(TAG, "Finishing off activity so that user can navigate to settings manually"); 168 finish(); 169 }}) 170 .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { 171 public void onClick(DialogInterface dialog, int which) { 172 Log.i(TAG, "Launching settings"); 173 launchSettingsAppAndFinish(); 174 } 175 }) 176 .setOnCancelListener(this) 177 .create(); 178 case DLG_PACKAGE_ERROR : 179 return new AlertDialog.Builder(this) 180 .setTitle(R.string.Parse_error_dlg_title) 181 .setMessage(R.string.Parse_error_dlg_text) 182 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 183 public void onClick(DialogInterface dialog, int which) { 184 finish(); 185 } 186 }) 187 .setOnCancelListener(this) 188 .create(); 189 case DLG_OUT_OF_SPACE: 190 // Guaranteed not to be null. will default to package name if not set by app 191 CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 192 String dlgText = getString(R.string.out_of_space_dlg_text, 193 appTitle.toString()); 194 return new AlertDialog.Builder(this) 195 .setTitle(R.string.out_of_space_dlg_title) 196 .setMessage(dlgText) 197 .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() { 198 public void onClick(DialogInterface dialog, int which) { 199 //launch manage applications 200 Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); 201 startActivity(intent); 202 finish(); 203 } 204 }) 205 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 206 public void onClick(DialogInterface dialog, int which) { 207 Log.i(TAG, "Canceling installation"); 208 finish(); 209 } 210 }) 211 .setOnCancelListener(this) 212 .create(); 213 case DLG_INSTALL_ERROR : 214 // Guaranteed not to be null. will default to package name if not set by app 215 CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 216 String dlgText1 = getString(R.string.install_failed_msg, 217 appTitle1.toString()); 218 return new AlertDialog.Builder(this) 219 .setTitle(R.string.install_failed) 220 .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() { 221 public void onClick(DialogInterface dialog, int which) { 222 finish(); 223 } 224 }) 225 .setMessage(dlgText1) 226 .setOnCancelListener(this) 227 .create(); 228 } 229 return null; 230 } 231 232 private class ClearCacheReceiver extends BroadcastReceiver { 233 public static final String INTENT_CLEAR_CACHE = 234 "com.android.packageinstaller.CLEAR_CACHE"; 235 @Override 236 public void onReceive(Context context, Intent intent) { 237 Message msg = mHandler.obtainMessage(FREE_SPACE); 238 msg.arg1 = (getResultCode() ==1) ? SUCCEEDED : FAILED; 239 mHandler.sendMessage(msg); 240 } 241 } 242 243 private void checkOutOfSpace(long size) { 244 if(localLOGV) Log.i(TAG, "Checking for "+size+" number of bytes"); 245 if (mClearCacheReceiver == null) { 246 mClearCacheReceiver = new ClearCacheReceiver(); 247 } 248 registerReceiver(mClearCacheReceiver, 249 new IntentFilter(ClearCacheReceiver.INTENT_CLEAR_CACHE)); 250 PendingIntent pi = PendingIntent.getBroadcast(this, 251 0, new Intent(ClearCacheReceiver.INTENT_CLEAR_CACHE), 0); 252 mPm.freeStorage(size, pi.getIntentSender()); 253 } 254 255 private void launchSettingsAppAndFinish() { 256 //Create an intent to launch SettingsTwo activity 257 Intent launchSettingsIntent = new Intent(Settings.ACTION_APPLICATION_SETTINGS); 258 startActivity(launchSettingsIntent); 259 finish(); 260 } 261 262 private boolean isInstallingUnknownAppsAllowed() { 263 return Settings.Secure.getInt(getContentResolver(), 264 Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0; 265 } 266 267 private void makeTempCopyAndInstall() { 268 // Check if package is already installed. display confirmation dialog if replacing pkg 269 try { 270 mAppInfo = mPm.getApplicationInfo(mPkgInfo.packageName, 271 PackageManager.GET_UNINSTALLED_PACKAGES); 272 } catch (NameNotFoundException e) { 273 mAppInfo = null; 274 } 275 if (mAppInfo == null) { 276 startInstallConfirm(); 277 } else { 278 if(localLOGV) Log.i(TAG, "Replacing existing package:"+ 279 mPkgInfo.applicationInfo.packageName); 280 showDialogInner(DLG_REPLACE_APP); 281 } 282 } 283 284 @Override 285 protected void onCreate(Bundle icicle) { 286 super.onCreate(icicle); 287 //get intent information 288 final Intent intent = getIntent(); 289 mPackageURI = intent.getData(); 290 mPm = getPackageManager(); 291 mPkgInfo = PackageUtil.getPackageInfo(mPackageURI); 292 293 // Check for parse errors 294 if(mPkgInfo == null) { 295 Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); 296 showDialogInner(DLG_PACKAGE_ERROR); 297 return; 298 } 299 300 //set view 301 requestWindowFeature(Window.FEATURE_NO_TITLE); 302 setContentView(R.layout.install_start); 303 mInstallConfirm = findViewById(R.id.install_confirm_panel); 304 mInstallConfirm.setVisibility(View.INVISIBLE); 305 PackageUtil.initSnippetForNewApp(this, mPkgInfo.applicationInfo, 306 R.id.app_snippet, mPackageURI); 307 //check setting 308 if(!isInstallingUnknownAppsAllowed()) { 309 //ask user to enable setting first 310 showDialogInner(DLG_UNKNOWN_APPS); 311 return; 312 } 313 //compute the size of the application. just an estimate 314 long size; 315 String apkPath = mPackageURI.getPath(); 316 File apkFile = new File(apkPath); 317 //TODO? DEVISE BETTER HEAURISTIC 318 size = 4*apkFile.length(); 319 checkOutOfSpace(size); 320 } 321 322 // Generic handling when pressing back key 323 public void onCancel(DialogInterface dialog) { 324 finish(); 325 } 326 327 public void onClick(View v) { 328 if(v == mOk) { 329 // Start subactivity to actually install the application 330 Intent newIntent = new Intent(); 331 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, 332 mPkgInfo.applicationInfo); 333 newIntent.setData(mPackageURI); 334 newIntent.setClass(this, InstallAppProgress.class); 335 String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); 336 if (installerPackageName != null) { 337 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName); 338 } 339 if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); 340 startActivity(newIntent); 341 finish(); 342 } else if(v == mCancel) { 343 // Cancel and finish 344 finish(); 345 } 346 } 347 } 348