| /* |
| ** |
| ** Copyright 2007, The Android Open Source Project |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| package com.android.packageinstaller; |
| |
| import android.app.Activity; |
| import android.app.ActivityManagerNative; |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.DialogInterface.OnCancelListener; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.ManifestDigest; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.PackageParser; |
| import android.content.pm.PackageUserState; |
| import android.content.pm.VerificationParams; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.Settings; |
| import android.support.v4.view.ViewPager; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.widget.AppSecurityPermissions; |
| import android.widget.Button; |
| import android.widget.TabHost; |
| import android.widget.TextView; |
| |
| import java.io.File; |
| |
| /* |
| * This activity is launched when a new application is installed via side loading |
| * The package is first parsed and the user is notified of parse errors via a dialog. |
| * If the package is successfully parsed, the user is notified to turn on the install unknown |
| * applications setting. A memory check is made at this point and the user is notified of out |
| * of memory conditions if any. If the package is already existing on the device, |
| * a confirmation dialog (to replace the existing package) is presented to the user. |
| * Based on the user response the package is then installed by launching InstallAppConfirm |
| * sub activity. All state transitions are handled in this activity |
| */ |
| public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener { |
| private static final String TAG = "PackageInstaller"; |
| private Uri mPackageURI; |
| private Uri mOriginatingURI; |
| private Uri mReferrerURI; |
| private int mOriginatingUid = VerificationParams.NO_UID; |
| private ManifestDigest mPkgDigest; |
| |
| private boolean localLOGV = false; |
| PackageManager mPm; |
| PackageInfo mPkgInfo; |
| ApplicationInfo mSourceInfo; |
| |
| // ApplicationInfo object primarily used for already existing applications |
| private ApplicationInfo mAppInfo = null; |
| |
| // View for install progress |
| View mInstallConfirm; |
| // Buttons to indicate user acceptance |
| private Button mOk; |
| private Button mCancel; |
| CaffeinatedScrollView mScrollView = null; |
| private boolean mOkCanInstall = false; |
| |
| static final String PREFS_ALLOWED_SOURCES = "allowed_sources"; |
| |
| // Dialog identifiers used in showDialog |
| private static final int DLG_BASE = 0; |
| private static final int DLG_UNKNOWN_APPS = DLG_BASE + 1; |
| private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2; |
| private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3; |
| private static final int DLG_INSTALL_ERROR = DLG_BASE + 4; |
| private static final int DLG_ALLOW_SOURCE = DLG_BASE + 5; |
| |
| private void startInstallConfirm() { |
| TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost); |
| tabHost.setup(); |
| ViewPager viewPager = (ViewPager)findViewById(R.id.pager); |
| TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager); |
| |
| boolean permVisible = false; |
| mScrollView = null; |
| mOkCanInstall = false; |
| int msg = 0; |
| if (mPkgInfo != null) { |
| AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo); |
| final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL); |
| final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE); |
| if (mAppInfo != null) { |
| msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 |
| ? R.string.install_confirm_question_update_system |
| : R.string.install_confirm_question_update; |
| mScrollView = new CaffeinatedScrollView(this); |
| mScrollView.setFillViewport(true); |
| if (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0) { |
| permVisible = true; |
| mScrollView.addView(perms.getPermissionsView( |
| AppSecurityPermissions.WHICH_NEW)); |
| } else { |
| LayoutInflater inflater = (LayoutInflater)getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| TextView label = (TextView)inflater.inflate(R.layout.label, null); |
| label.setText(R.string.no_new_perms); |
| mScrollView.addView(label); |
| } |
| adapter.addTab(tabHost.newTabSpec("new").setIndicator( |
| getText(R.string.newPerms)), mScrollView); |
| } else { |
| findViewById(R.id.tabscontainer).setVisibility(View.GONE); |
| findViewById(R.id.divider).setVisibility(View.VISIBLE); |
| } |
| if (NP > 0 || ND > 0) { |
| permVisible = true; |
| LayoutInflater inflater = (LayoutInflater)getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| View root = inflater.inflate(R.layout.permissions_list, null); |
| if (mScrollView == null) { |
| mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview); |
| } |
| if (NP > 0) { |
| ((ViewGroup)root.findViewById(R.id.privacylist)).addView( |
| perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL)); |
| } else { |
| root.findViewById(R.id.privacylist).setVisibility(View.GONE); |
| } |
| if (ND > 0) { |
| ((ViewGroup)root.findViewById(R.id.devicelist)).addView( |
| perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE)); |
| } else { |
| root.findViewById(R.id.devicelist).setVisibility(View.GONE); |
| } |
| adapter.addTab(tabHost.newTabSpec("all").setIndicator( |
| getText(R.string.allPerms)), root); |
| } |
| } |
| if (!permVisible) { |
| if (mAppInfo != null) { |
| // This is an update to an application, but there are no |
| // permissions at all. |
| msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 |
| ? R.string.install_confirm_question_update_system_no_perms |
| : R.string.install_confirm_question_update_no_perms; |
| } else { |
| // This is a new application with no permissions. |
| msg = R.string.install_confirm_question_no_perms; |
| } |
| tabHost.setVisibility(View.GONE); |
| findViewById(R.id.filler).setVisibility(View.VISIBLE); |
| findViewById(R.id.divider).setVisibility(View.GONE); |
| mScrollView = null; |
| } |
| if (msg != 0) { |
| ((TextView)findViewById(R.id.install_confirm_question)).setText(msg); |
| } |
| mInstallConfirm.setVisibility(View.VISIBLE); |
| mOk = (Button)findViewById(R.id.ok_button); |
| mCancel = (Button)findViewById(R.id.cancel_button); |
| mOk.setOnClickListener(this); |
| mCancel.setOnClickListener(this); |
| if (mScrollView == null) { |
| // There is nothing to scroll view, so the ok button is immediately |
| // set to install. |
| mOk.setText(R.string.install); |
| mOkCanInstall = true; |
| } else { |
| mScrollView.setFullScrollAction(new Runnable() { |
| @Override |
| public void run() { |
| mOk.setText(R.string.install); |
| mOkCanInstall = true; |
| } |
| }); |
| } |
| } |
| |
| private void showDialogInner(int id) { |
| // TODO better fix for this? Remove dialog so that it gets created again |
| removeDialog(id); |
| showDialog(id); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(int id, Bundle bundle) { |
| switch (id) { |
| case DLG_UNKNOWN_APPS: |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.unknown_apps_dlg_title) |
| .setMessage(R.string.unknown_apps_dlg_text) |
| .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| Log.i(TAG, "Finishing off activity so that user can navigate to settings manually"); |
| finish(); |
| }}) |
| .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| Log.i(TAG, "Launching settings"); |
| launchSettingsAppAndFinish(); |
| } |
| }) |
| .setOnCancelListener(this) |
| .create(); |
| case DLG_PACKAGE_ERROR : |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.Parse_error_dlg_title) |
| .setMessage(R.string.Parse_error_dlg_text) |
| .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| finish(); |
| } |
| }) |
| .setOnCancelListener(this) |
| .create(); |
| case DLG_OUT_OF_SPACE: |
| // Guaranteed not to be null. will default to package name if not set by app |
| CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo); |
| String dlgText = getString(R.string.out_of_space_dlg_text, |
| appTitle.toString()); |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.out_of_space_dlg_title) |
| .setMessage(dlgText) |
| .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| //launch manage applications |
| Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startActivity(intent); |
| finish(); |
| } |
| }) |
| .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| Log.i(TAG, "Canceling installation"); |
| finish(); |
| } |
| }) |
| .setOnCancelListener(this) |
| .create(); |
| case DLG_INSTALL_ERROR : |
| // Guaranteed not to be null. will default to package name if not set by app |
| CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo); |
| String dlgText1 = getString(R.string.install_failed_msg, |
| appTitle1.toString()); |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.install_failed) |
| .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| finish(); |
| } |
| }) |
| .setMessage(dlgText1) |
| .setOnCancelListener(this) |
| .create(); |
| case DLG_ALLOW_SOURCE: |
| CharSequence appTitle2 = mPm.getApplicationLabel(mSourceInfo); |
| String dlgText2 = getString(R.string.allow_source_dlg_text, |
| appTitle2.toString()); |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.allow_source_dlg_title) |
| .setMessage(dlgText2) |
| .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| setResult(RESULT_CANCELED); |
| finish(); |
| }}) |
| .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES, |
| Context.MODE_PRIVATE); |
| prefs.edit().putBoolean(mSourceInfo.packageName, true).apply(); |
| startInstallConfirm(); |
| } |
| }) |
| .setOnCancelListener(this) |
| .create(); |
| } |
| return null; |
| } |
| |
| private void launchSettingsAppAndFinish() { |
| // Create an intent to launch SettingsTwo activity |
| Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS); |
| launchSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startActivity(launchSettingsIntent); |
| finish(); |
| } |
| |
| private boolean isInstallingUnknownAppsAllowed() { |
| return Settings.Global.getInt(getContentResolver(), |
| Settings.Global.INSTALL_NON_MARKET_APPS, 0) > 0; |
| } |
| |
| private void initiateInstall() { |
| String pkgName = mPkgInfo.packageName; |
| // Check if there is already a package on the device with this name |
| // but it has been renamed to something else. |
| String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName }); |
| if (oldName != null && oldName.length > 0 && oldName[0] != null) { |
| pkgName = oldName[0]; |
| mPkgInfo.packageName = pkgName; |
| mPkgInfo.applicationInfo.packageName = pkgName; |
| } |
| // Check if package is already installed. display confirmation dialog if replacing pkg |
| try { |
| // This is a little convoluted because we want to get all uninstalled |
| // apps, but this may include apps with just data, and if it is just |
| // data we still want to count it as "installed". |
| mAppInfo = mPm.getApplicationInfo(pkgName, |
| PackageManager.GET_UNINSTALLED_PACKAGES); |
| if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { |
| mAppInfo = null; |
| } |
| } catch (NameNotFoundException e) { |
| mAppInfo = null; |
| } |
| startInstallConfirm(); |
| } |
| |
| void setPmResult(int pmResult) { |
| Intent result = new Intent(); |
| result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult); |
| setResult(pmResult == PackageManager.INSTALL_SUCCEEDED |
| ? RESULT_OK : RESULT_FIRST_USER, result); |
| } |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| // get intent information |
| final Intent intent = getIntent(); |
| mPackageURI = intent.getData(); |
| mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); |
| mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER); |
| mPm = getPackageManager(); |
| |
| final String scheme = mPackageURI.getScheme(); |
| if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) { |
| Log.w(TAG, "Unsupported scheme " + scheme); |
| setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); |
| return; |
| } |
| |
| final PackageUtil.AppSnippet as; |
| if ("package".equals(mPackageURI.getScheme())) { |
| try { |
| mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(), |
| PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES); |
| } catch (NameNotFoundException e) { |
| } |
| if (mPkgInfo == null) { |
| Log.w(TAG, "Requested package " + mPackageURI.getScheme() |
| + " not available. Discontinuing installation"); |
| showDialogInner(DLG_PACKAGE_ERROR); |
| setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); |
| return; |
| } |
| as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo), |
| mPm.getApplicationIcon(mPkgInfo.applicationInfo)); |
| } else { |
| final File sourceFile = new File(mPackageURI.getPath()); |
| PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile); |
| |
| // Check for parse errors |
| if (parsed == null) { |
| Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); |
| showDialogInner(DLG_PACKAGE_ERROR); |
| setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); |
| return; |
| } |
| mPkgInfo = PackageParser.generatePackageInfo(parsed, null, |
| PackageManager.GET_PERMISSIONS, 0, 0, null, |
| new PackageUserState()); |
| mPkgDigest = parsed.manifestDigest; |
| as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); |
| } |
| |
| //set view |
| setContentView(R.layout.install_start); |
| mInstallConfirm = findViewById(R.id.install_confirm_panel); |
| mInstallConfirm.setVisibility(View.INVISIBLE); |
| PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet); |
| |
| mOriginatingUid = getOriginatingUid(intent); |
| |
| // Deal with install source. |
| String callerPackage = getCallingPackage(); |
| if (callerPackage != null && intent.getBooleanExtra( |
| Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) { |
| try { |
| mSourceInfo = mPm.getApplicationInfo(callerPackage, 0); |
| if (mSourceInfo != null) { |
| if ((mSourceInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { |
| // System apps don't need to be approved. |
| initiateInstall(); |
| return; |
| } |
| /* for now this is disabled, since the user would need to |
| * have enabled the global "unknown sources" setting in the |
| * first place in order to get here. |
| SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES, |
| Context.MODE_PRIVATE); |
| if (prefs.getBoolean(mSourceInfo.packageName, false)) { |
| // User has already allowed this one. |
| initiateInstall(); |
| return; |
| } |
| //ask user to enable setting first |
| showDialogInner(DLG_ALLOW_SOURCE); |
| return; |
| */ |
| } |
| } catch (NameNotFoundException e) { |
| } |
| } |
| |
| // Check unknown sources. |
| if (!isInstallingUnknownAppsAllowed()) { |
| //ask user to enable setting first |
| showDialogInner(DLG_UNKNOWN_APPS); |
| return; |
| } |
| initiateInstall(); |
| } |
| |
| /** Get the ApplicationInfo for the calling package, if available */ |
| private ApplicationInfo getSourceInfo() { |
| String callingPackage = getCallingPackage(); |
| if (callingPackage != null) { |
| try { |
| return mPm.getApplicationInfo(callingPackage, 0); |
| } catch (NameNotFoundException ex) { |
| // ignore |
| } |
| } |
| return null; |
| } |
| |
| |
| /** Get the originating uid if possible, or VerificationParams.NO_UID if not available */ |
| private int getOriginatingUid(Intent intent) { |
| // The originating uid from the intent. We only trust/use this if it comes from a |
| // system application |
| int uidFromIntent = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, |
| VerificationParams.NO_UID); |
| |
| // Get the source info from the calling package, if available. This will be the |
| // definitive calling package, but it only works if the intent was started using |
| // startActivityForResult, |
| ApplicationInfo sourceInfo = getSourceInfo(); |
| if (sourceInfo != null) { |
| if (uidFromIntent != VerificationParams.NO_UID && |
| (mSourceInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { |
| return uidFromIntent; |
| |
| } |
| // We either didn't get a uid in the intent, or we don't trust it. Use the |
| // uid of the calling package instead. |
| return sourceInfo.uid; |
| } |
| |
| // We couldn't get the specific calling package. Let's get the uid instead |
| int callingUid; |
| try { |
| callingUid = ActivityManagerNative.getDefault() |
| .getLaunchedFromUid(getActivityToken()); |
| } catch (android.os.RemoteException ex) { |
| Log.w(TAG, "Could not determine the launching uid."); |
| // nothing else we can do |
| return VerificationParams.NO_UID; |
| } |
| |
| // If we got a uid from the intent, we need to verify that the caller is a |
| // system package before we use it |
| if (uidFromIntent != VerificationParams.NO_UID) { |
| String[] callingPackages = mPm.getPackagesForUid(callingUid); |
| if (callingPackages != null) { |
| for (String packageName: callingPackages) { |
| try { |
| ApplicationInfo applicationInfo = |
| mPm.getApplicationInfo(packageName, 0); |
| |
| if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { |
| return uidFromIntent; |
| } |
| } catch (NameNotFoundException ex) { |
| // ignore it, and try the next package |
| } |
| } |
| } |
| } |
| // We either didn't get a uid from the intent, or we don't trust it. Use the |
| // calling uid instead. |
| return callingUid; |
| } |
| |
| // Generic handling when pressing back key |
| public void onCancel(DialogInterface dialog) { |
| finish(); |
| } |
| |
| public void onClick(View v) { |
| if(v == mOk) { |
| if (mOkCanInstall || mScrollView == null) { |
| // Start subactivity to actually install the application |
| Intent newIntent = new Intent(); |
| newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, |
| mPkgInfo.applicationInfo); |
| newIntent.setData(mPackageURI); |
| newIntent.setClass(this, InstallAppProgress.class); |
| newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest); |
| String installerPackageName = getIntent().getStringExtra( |
| Intent.EXTRA_INSTALLER_PACKAGE_NAME); |
| if (mOriginatingURI != null) { |
| newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI); |
| } |
| if (mReferrerURI != null) { |
| newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI); |
| } |
| if (mOriginatingUid != VerificationParams.NO_UID) { |
| newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid); |
| } |
| if (installerPackageName != null) { |
| newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, |
| installerPackageName); |
| } |
| if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { |
| newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); |
| newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); |
| } |
| if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); |
| startActivity(newIntent); |
| finish(); |
| } else { |
| mScrollView.pageScroll(View.FOCUS_DOWN); |
| } |
| } else if(v == mCancel) { |
| // Cancel and finish |
| setResult(RESULT_CANCELED); |
| finish(); |
| } |
| } |
| } |