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 android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.Dialog; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.DialogInterface.OnCancelListener; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageParser; 30 import android.content.pm.PackageManager.NameNotFoundException; 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.Window; 37 import android.view.View.OnClickListener; 38 import android.widget.AppSecurityPermissions; 39 import android.widget.Button; 40 import android.widget.LinearLayout; 41 42 /* 43 * This activity is launched when a new application is installed via side loading 44 * The package is first parsed and the user is notified of parse errors via a dialog. 45 * If the package is successfully parsed, the user is notified to turn on the install unknown 46 * applications setting. A memory check is made at this point and the user is notified of out 47 * of memory conditions if any. If the package is already existing on the device, 48 * a confirmation dialog (to replace the existing package) is presented to the user. 49 * Based on the user response the package is then installed by launching InstallAppConfirm 50 * sub activity. All state transitions are handled in this activity 51 */ 52 public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener { 53 private static final String TAG = "PackageInstaller"; 54 private Uri mPackageURI; 55 private boolean localLOGV = false; 56 PackageManager mPm; 57 private PackageParser.Package mPkgInfo; 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 // Dialog identifiers used in showDialog 69 private static final int DLG_BASE = 0; 70 private static final int DLG_REPLACE_APP = DLG_BASE + 1; 71 private static final int DLG_UNKNOWN_APPS = DLG_BASE + 2; 72 private static final int DLG_PACKAGE_ERROR = DLG_BASE + 3; 73 private static final int DLG_OUT_OF_SPACE = DLG_BASE + 4; 74 private static final int DLG_INSTALL_ERROR = DLG_BASE + 5; 75 startInstallConfirm()76 private void startInstallConfirm() { 77 LinearLayout permsSection = (LinearLayout) mInstallConfirm.findViewById(R.id.permissions_section); 78 LinearLayout securityList = (LinearLayout) permsSection.findViewById( 79 R.id.security_settings_list); 80 boolean permVisible = false; 81 if(mPkgInfo != null) { 82 AppSecurityPermissions asp = new AppSecurityPermissions(this, mPkgInfo); 83 if(asp.getPermissionCount() > 0) { 84 permVisible = true; 85 securityList.addView(asp.getPermissionsView()); 86 } 87 } 88 if(!permVisible){ 89 permsSection.setVisibility(View.INVISIBLE); 90 } 91 mInstallConfirm.setVisibility(View.VISIBLE); 92 mOk = (Button)findViewById(R.id.ok_button); 93 mCancel = (Button)findViewById(R.id.cancel_button); 94 mOk.setOnClickListener(this); 95 mCancel.setOnClickListener(this); 96 } 97 showDialogInner(int id)98 private void showDialogInner(int id) { 99 // TODO better fix for this? Remove dialog so that it gets created again 100 removeDialog(id); 101 showDialog(id); 102 } 103 104 @Override onCreateDialog(int id, Bundle bundle)105 public Dialog onCreateDialog(int id, Bundle bundle) { 106 switch (id) { 107 case DLG_REPLACE_APP: 108 int msgId = R.string.dlg_app_replacement_statement; 109 // Customized text for system apps 110 if ((mAppInfo != null) && (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 111 msgId = R.string.dlg_sys_app_replacement_statement; 112 } 113 return new AlertDialog.Builder(this) 114 .setTitle(R.string.dlg_app_replacement_title) 115 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 116 public void onClick(DialogInterface dialog, int which) { 117 startInstallConfirm(); 118 }}) 119 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 120 public void onClick(DialogInterface dialog, int which) { 121 Log.i(TAG, "Canceling installation"); 122 finish(); 123 }}) 124 .setMessage(msgId) 125 .setOnCancelListener(this) 126 .create(); 127 case DLG_UNKNOWN_APPS: 128 return new AlertDialog.Builder(this) 129 .setTitle(R.string.unknown_apps_dlg_title) 130 .setMessage(R.string.unknown_apps_dlg_text) 131 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 132 public void onClick(DialogInterface dialog, int which) { 133 Log.i(TAG, "Finishing off activity so that user can navigate to settings manually"); 134 finish(); 135 }}) 136 .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { 137 public void onClick(DialogInterface dialog, int which) { 138 Log.i(TAG, "Launching settings"); 139 launchSettingsAppAndFinish(); 140 } 141 }) 142 .setOnCancelListener(this) 143 .create(); 144 case DLG_PACKAGE_ERROR : 145 return new AlertDialog.Builder(this) 146 .setTitle(R.string.Parse_error_dlg_title) 147 .setMessage(R.string.Parse_error_dlg_text) 148 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 149 public void onClick(DialogInterface dialog, int which) { 150 finish(); 151 } 152 }) 153 .setOnCancelListener(this) 154 .create(); 155 case DLG_OUT_OF_SPACE: 156 // Guaranteed not to be null. will default to package name if not set by app 157 CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 158 String dlgText = getString(R.string.out_of_space_dlg_text, 159 appTitle.toString()); 160 return new AlertDialog.Builder(this) 161 .setTitle(R.string.out_of_space_dlg_title) 162 .setMessage(dlgText) 163 .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() { 164 public void onClick(DialogInterface dialog, int which) { 165 //launch manage applications 166 Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); 167 startActivity(intent); 168 finish(); 169 } 170 }) 171 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 172 public void onClick(DialogInterface dialog, int which) { 173 Log.i(TAG, "Canceling installation"); 174 finish(); 175 } 176 }) 177 .setOnCancelListener(this) 178 .create(); 179 case DLG_INSTALL_ERROR : 180 // Guaranteed not to be null. will default to package name if not set by app 181 CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 182 String dlgText1 = getString(R.string.install_failed_msg, 183 appTitle1.toString()); 184 return new AlertDialog.Builder(this) 185 .setTitle(R.string.install_failed) 186 .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() { 187 public void onClick(DialogInterface dialog, int which) { 188 finish(); 189 } 190 }) 191 .setMessage(dlgText1) 192 .setOnCancelListener(this) 193 .create(); 194 } 195 return null; 196 } 197 198 private void launchSettingsAppAndFinish() { 199 //Create an intent to launch SettingsTwo activity 200 Intent launchSettingsIntent = new Intent(Settings.ACTION_APPLICATION_SETTINGS); 201 startActivity(launchSettingsIntent); 202 finish(); 203 } 204 205 private boolean isInstallingUnknownAppsAllowed() { 206 return Settings.Secure.getInt(getContentResolver(), 207 Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0; 208 } 209 210 private void initiateInstall() { 211 String pkgName = mPkgInfo.packageName; 212 // Check if there is already a package on the device with this name 213 // but it has been renamed to something else. 214 String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName }); 215 if (oldName != null && oldName.length > 0 && oldName[0] != null) { 216 pkgName = oldName[0]; 217 mPkgInfo.setPackageName(pkgName); 218 } 219 // Check if package is already installed. display confirmation dialog if replacing pkg 220 try { 221 mAppInfo = mPm.getApplicationInfo(pkgName, 222 PackageManager.GET_UNINSTALLED_PACKAGES); 223 } catch (NameNotFoundException e) { 224 mAppInfo = null; 225 } 226 if (mAppInfo == null) { 227 startInstallConfirm(); 228 } else { 229 if(localLOGV) Log.i(TAG, "Replacing existing package:"+ 230 mPkgInfo.applicationInfo.packageName); 231 showDialogInner(DLG_REPLACE_APP); 232 } 233 } 234 235 @Override 236 protected void onCreate(Bundle icicle) { 237 super.onCreate(icicle); 238 //get intent information 239 final Intent intent = getIntent(); 240 mPackageURI = intent.getData(); 241 mPm = getPackageManager(); 242 mPkgInfo = PackageUtil.getPackageInfo(mPackageURI); 243 244 // Check for parse errors 245 if(mPkgInfo == null) { 246 Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); 247 showDialogInner(DLG_PACKAGE_ERROR); 248 return; 249 } 250 251 //set view 252 requestWindowFeature(Window.FEATURE_NO_TITLE); 253 setContentView(R.layout.install_start); 254 mInstallConfirm = findViewById(R.id.install_confirm_panel); 255 mInstallConfirm.setVisibility(View.INVISIBLE); 256 PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, 257 mPkgInfo.applicationInfo, mPackageURI); 258 PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet); 259 //check setting 260 if(!isInstallingUnknownAppsAllowed()) { 261 //ask user to enable setting first 262 showDialogInner(DLG_UNKNOWN_APPS); 263 return; 264 } 265 initiateInstall(); 266 } 267 268 // Generic handling when pressing back key 269 public void onCancel(DialogInterface dialog) { 270 finish(); 271 } 272 273 public void onClick(View v) { 274 if(v == mOk) { 275 // Start subactivity to actually install the application 276 Intent newIntent = new Intent(); 277 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, 278 mPkgInfo.applicationInfo); 279 newIntent.setData(mPackageURI); 280 newIntent.setClass(this, InstallAppProgress.class); 281 String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); 282 if (installerPackageName != null) { 283 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName); 284 } 285 if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); 286 startActivity(newIntent); 287 finish(); 288 } else if(v == mCancel) { 289 // Cancel and finish 290 finish(); 291 } 292 } 293 } 294