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