Home | History | Annotate | Download | only in packageinstaller
      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