diff options
Diffstat (limited to 'src/com/android/packageinstaller/PackageInstallerActivity.java')
-rw-r--r-- | src/com/android/packageinstaller/PackageInstallerActivity.java | 478 |
1 files changed, 384 insertions, 94 deletions
diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java index 99631035..4a6db210 100644 --- a/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -17,6 +17,7 @@ package com.android.packageinstaller; import android.app.Activity; +import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; @@ -25,13 +26,18 @@ import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageUserState; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageParser; +import android.content.pm.VerificationParams; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -39,7 +45,6 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AppSecurityPermissions; import android.widget.Button; -import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TabHost; import android.widget.TabWidget; @@ -61,9 +66,13 @@ import java.util.ArrayList; 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 boolean localLOGV = false; PackageManager mPm; - PackageParser.Package mPkgInfo; + PackageInfo mPkgInfo; ApplicationInfo mSourceInfo; // ApplicationInfo object primarily used for already existing applications @@ -74,38 +83,244 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen // 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_REPLACE_APP = DLG_BASE + 1; - private static final int DLG_UNKNOWN_APPS = DLG_BASE + 2; - private static final int DLG_PACKAGE_ERROR = DLG_BASE + 3; - private static final int DLG_OUT_OF_SPACE = DLG_BASE + 4; - private static final int DLG_INSTALL_ERROR = DLG_BASE + 5; - private static final int DLG_ALLOW_SOURCE = DLG_BASE + 6; + 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; + + /** + * This is a helper class that implements the management of tabs and all + * details of connecting a ViewPager with associated TabHost. It relies on a + * trick. Normally a tab host has a simple API for supplying a View or + * Intent that each tab will show. This is not sufficient for switching + * between pages. So instead we make the content part of the tab host + * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy + * view to show as the tab content. It listens to changes in tabs, and takes + * care of switch to the correct paged in the ViewPager whenever the selected + * tab changes. + */ + public static class TabsAdapter extends PagerAdapter + implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener { + private final Context mContext; + private final TabHost mTabHost; + private final ViewPager mViewPager; + private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + private final Rect mTempRect = new Rect(); + + static final class TabInfo { + private final String tag; + private final View view; + + TabInfo(String _tag, View _view) { + tag = _tag; + view = _view; + } + } + + static class DummyTabFactory implements TabHost.TabContentFactory { + private final Context mContext; + + public DummyTabFactory(Context context) { + mContext = context; + } + + @Override + public View createTabContent(String tag) { + View v = new View(mContext); + v.setMinimumWidth(0); + v.setMinimumHeight(0); + return v; + } + } + + public TabsAdapter(Activity activity, TabHost tabHost, ViewPager pager) { + mContext = activity; + mTabHost = tabHost; + mViewPager = pager; + mTabHost.setOnTabChangedListener(this); + mViewPager.setAdapter(this); + mViewPager.setOnPageChangeListener(this); + } + + public void addTab(TabHost.TabSpec tabSpec, View view) { + tabSpec.setContent(new DummyTabFactory(mContext)); + String tag = tabSpec.getTag(); + + TabInfo info = new TabInfo(tag, view); + mTabs.add(info); + mTabHost.addTab(tabSpec); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mTabs.size(); + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + View view = mTabs.get(position).view; + container.addView(view); + return view; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView((View)object); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public void onTabChanged(String tabId) { + int position = mTabHost.getCurrentTab(); + mViewPager.setCurrentItem(position); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + // Unfortunately when TabHost changes the current tab, it kindly + // also takes care of putting focus on it when not in touch mode. + // The jerk. + // This hack tries to prevent this from pulling focus out of our + // ViewPager. + TabWidget widget = mTabHost.getTabWidget(); + int oldFocusability = widget.getDescendantFocusability(); + widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + mTabHost.setCurrentTab(position); + widget.setDescendantFocusability(oldFocusability); + + // Scroll the current tab into visibility if needed. + View tab = widget.getChildTabViewAt(position); + mTempRect.set(tab.getLeft(), tab.getTop(), tab.getRight(), tab.getBottom()); + widget.requestRectangleOnScreen(mTempRect, false); + + // Make sure the scrollbars are visible for a moment after selection + final View contentView = mTabs.get(position).view; + if (contentView instanceof CaffeinatedScrollView) { + ((CaffeinatedScrollView) contentView).awakenScrollBars(); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + } + } private void startInstallConfirm() { - LinearLayout permsSection = (LinearLayout) mInstallConfirm.findViewById(R.id.permissions_section); - LinearLayout securityList = (LinearLayout) permsSection.findViewById( - R.id.security_settings_list); + 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; - if(mPkgInfo != null) { - AppSecurityPermissions asp = new AppSecurityPermissions(this, mPkgInfo); - if(asp.getPermissionCount() > 0) { + 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; - securityList.addView(asp.getPermissionsView()); + 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(!permVisible){ - permsSection.setVisibility(View.INVISIBLE); + 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) { @@ -117,27 +332,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen @Override public Dialog onCreateDialog(int id, Bundle bundle) { switch (id) { - case DLG_REPLACE_APP: - int msgId = R.string.dlg_app_replacement_statement; - // Customized text for system apps - if ((mAppInfo != null) && (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - msgId = R.string.dlg_sys_app_replacement_statement; - } - return new AlertDialog.Builder(this) - .setTitle(R.string.dlg_app_replacement_title) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - startInstallConfirm(); - }}) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - Log.i(TAG, "Canceling installation"); - setResult(RESULT_CANCELED); - finish(); - }}) - .setMessage(msgId) - .setOnCancelListener(this) - .create(); case DLG_UNKNOWN_APPS: return new AlertDialog.Builder(this) .setTitle(R.string.unknown_apps_dlg_title) @@ -241,8 +435,8 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen } private boolean isInstallingUnknownAppsAllowed() { - return Settings.Secure.getInt(getContentResolver(), - Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0; + return Settings.Global.getInt(getContentResolver(), + Settings.Global.INSTALL_NON_MARKET_APPS, 0) > 0; } private void initiateInstall() { @@ -252,22 +446,23 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName }); if (oldName != null && oldName.length > 0 && oldName[0] != null) { pkgName = oldName[0]; - mPkgInfo.setPackageName(pkgName); + 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; } - if (mAppInfo == null || getIntent().getBooleanExtra(Intent.EXTRA_ALLOW_REPLACE, false)) { - startInstallConfirm(); - } else { - if(localLOGV) Log.i(TAG, "Replacing existing package:"+ - mPkgInfo.applicationInfo.packageName); - showDialogInner(DLG_REPLACE_APP); - } + startInstallConfirm(); } void setPmResult(int pmResult) { @@ -284,32 +479,58 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen // 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)) { - throw new IllegalArgumentException("unexpected scheme " + scheme); + if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) { + Log.w(TAG, "Unsupported scheme " + scheme); + setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); + return; } - final File sourceFile = new File(mPackageURI.getPath()); - mPkgInfo = PackageUtil.getPackageInfo(sourceFile); + 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 (mPkgInfo == null) { - Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); - showDialogInner(DLG_PACKAGE_ERROR); - setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); - return; + // 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()); + 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); - final PackageUtil.AppSnippet as = PackageUtil.getAppSnippet( - this, mPkgInfo.applicationInfo, sourceFile); PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet); + mOriginatingUid = getOriginatingUid(intent); + // Deal with install source. String callerPackage = getCallingPackage(); if (callerPackage != null && intent.getBooleanExtra( @@ -349,7 +570,78 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen } 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(); @@ -357,44 +649,42 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen public void onClick(View v) { if(v == mOk) { - // 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); - String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); - 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 (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); + 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); } - if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); - startActivity(newIntent); - finish(); } else if(v == mCancel) { // Cancel and finish setResult(RESULT_CANCELED); finish(); } } - - /** - * It's a ScrollView that knows how to stay awake. - */ - static class CaffeinatedScrollView extends ScrollView { - public CaffeinatedScrollView(Context context) { - super(context); - } - - /** - * Make this visible so we can call it - */ - @Override - public boolean awakenScrollBars() { - return super.awakenScrollBars(); - } - } } |