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