diff options
author | Dianne Hackborn <hackbod@google.com> | 2012-09-12 17:33:49 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2012-09-12 17:33:49 -0700 |
commit | 37df032e580ab6bed29eb40b72df1f49cefd2af1 (patch) | |
tree | ae3fa9a41b90a29467ad1fcbdae12244bf242c6a | |
parent | 8707cfaa0c8c111e7fe2ad81d0b8052d4550d4d4 (diff) | |
download | packages_apps_packageinstaller-37df032e580ab6bed29eb40b72df1f49cefd2af1.zip packages_apps_packageinstaller-37df032e580ab6bed29eb40b72df1f49cefd2af1.tar.gz packages_apps_packageinstaller-37df032e580ab6bed29eb40b72df1f49cefd2af1.tar.bz2 |
New permissions UI.
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | res/layout/install_confirm.xml | 71 | ||||
-rw-r--r-- | src/com/android/packageinstaller/PackageInstallerActivity.java | 227 |
3 files changed, 224 insertions, 76 deletions
@@ -5,7 +5,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) -#LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 +LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 LOCAL_PACKAGE_NAME := PackageInstaller LOCAL_CERTIFICATE := platform diff --git a/res/layout/install_confirm.xml b/res/layout/install_confirm.xml index 0eb8ba6..753a24b 100644 --- a/res/layout/install_confirm.xml +++ b/res/layout/install_confirm.xml @@ -25,9 +25,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="8dip" - android:paddingRight="8dip"> + android:layout_height="match_parent"> <TextView android:id="@+id/install_confirm_question" @@ -36,38 +34,50 @@ android:text="@string/install_confirm_question" android:textAppearance="?android:attr/textAppearanceMedium" style="@style/padded" - android:paddingTop="12dip" - android:paddingBottom="16dip"/> + android:paddingTop="12dip" /> - <ScrollView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:fillViewport="true" - android:layout_weight="1"> + <TabHost + android:id="@android:id/tabhost" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1"> - <!-- Security settings description. --> <LinearLayout - android:id="@+id/permissions_section" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginRight="?android:attr/scrollbarSize" - style="@style/padded" - android:orientation="vertical"> - <TextView - android:id="@+id/security_settings_desc" - android:text="@string/security_settings_desc" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - /> - <LinearLayout - android:id="@+id/security_settings_list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1.0" - android:orientation="vertical"/> + android:background="@*android:drawable/tab_unselected_holo" + android:fillViewport="true" + android:scrollbars="none"> + <FrameLayout android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TabWidget + android:id="@android:id/tabs" + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + </FrameLayout> + </HorizontalScrollView> + + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="0"/> + + <android.support.v4.view.ViewPager + android:id="@+id/pager" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + </LinearLayout> - </ScrollView> + </TabHost> <!-- OK confirm and cancel buttons. --> <LinearLayout @@ -75,8 +85,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:divider="?android:attr/dividerHorizontal" - android:showDividers="beginning" - android:paddingTop="16dip"> + android:showDividers="beginning"> <LinearLayout style="?android:attr/buttonBarStyle" diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java index 6069748..c082010 100644 --- a/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -32,6 +32,8 @@ 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 +41,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; @@ -81,27 +82,192 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen // 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) { + int msg = 0; + if (mPkgInfo != null) { + AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo); + if (mAppInfo != null) { + permVisible = true; + msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 + ? R.string.install_confirm_question_update_system + : R.string.install_confirm_question_update; + ScrollView scrollView = new CaffeinatedScrollView(this); + scrollView.setFillViewport(true); + if (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0) { + scrollView.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); + scrollView.addView(label); + } + adapter.addTab(tabHost.newTabSpec("new").setIndicator( + getText(R.string.newPerms)), scrollView); + } + if (perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL) > 0) { + permVisible = true; + ScrollView scrollView = new CaffeinatedScrollView(this); + scrollView.setFillViewport(true); + scrollView.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL)); + adapter.addTab(tabHost.newTabSpec("personal").setIndicator( + getText(R.string.privacyPerms)), scrollView); + } + if (perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE) > 0) { permVisible = true; - securityList.addView(asp.getPermissionsView()); + ScrollView scrollView = new CaffeinatedScrollView(this); + scrollView.setFillViewport(true); + scrollView.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE)); + adapter.addTab(tabHost.newTabSpec("device").setIndicator( + getText(R.string.devicePerms)), scrollView); } } - if(!permVisible){ - permsSection.setVisibility(View.INVISIBLE); + if (!permVisible) { + if (msg == 0) { + msg = R.string.install_confirm_question_no_perms; + } + tabHost.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); @@ -119,27 +285,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) @@ -263,13 +408,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen } 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) { |