diff options
author | Dianne Hackborn <hackbod@google.com> | 2012-05-02 16:56:14 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2012-05-03 18:30:27 -0700 |
commit | 9762658939747166e3c40d817971aa5b17231ee7 (patch) | |
tree | 2bccf0cd8875f7babb07457b82c0835abe0c23a5 | |
parent | 7a39280f3a8e33b592a8b113ec0bc62638ad7247 (diff) | |
download | packages_apps_packageinstaller-9762658939747166e3c40d817971aa5b17231ee7.zip packages_apps_packageinstaller-9762658939747166e3c40d817971aa5b17231ee7.tar.gz packages_apps_packageinstaller-9762658939747166e3c40d817971aa5b17231ee7.tar.bz2 |
New permissions UI.
Change-Id: I5d4691f8a23e90265eaaaea15950affdcb8dc9b6
-rw-r--r-- | Android.mk | 4 | ||||
-rw-r--r-- | AndroidManifest.xml | 2 | ||||
-rw-r--r-- | proguard.flags | 5 | ||||
-rw-r--r-- | res/layout/install_confirm.xml | 63 | ||||
-rw-r--r-- | res/layout/label.xml | 20 | ||||
-rw-r--r-- | res/values/strings.xml | 25 | ||||
-rw-r--r-- | src/com/android/packageinstaller/PackageInstallerActivity.java | 223 |
7 files changed, 267 insertions, 75 deletions
@@ -5,7 +5,11 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 + LOCAL_PACKAGE_NAME := PackageInstaller LOCAL_CERTIFICATE := platform +LOCAL_PROGUARD_FLAG_FILES := proguard.flags + include $(BUILD_PACKAGE) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 44ec084..be348a9 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -12,7 +12,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:label="@string/app_name" android:allowBackup="false" - android:theme="@android:style/Theme.Holo.DialogWhenLarge.NoActionBar"> + android:theme="@android:style/Theme.Holo.Light.DialogWhenLarge.NoActionBar"> <activity android:name=".PackageInstallerActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:excludeFromRecents="true"> diff --git a/proguard.flags b/proguard.flags new file mode 100644 index 0000000..f0a0f47 --- /dev/null +++ b/proguard.flags @@ -0,0 +1,5 @@ +# The support library contains references to newer platform versions. +# Don't warn about those in case this app is linking against an older +# platform version. We know about them, and they are safe. + +-dontwarn android.support.v4.** diff --git a/res/layout/install_confirm.xml b/res/layout/install_confirm.xml index 0eb8ba6..5e4aced 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,46 @@ 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"> + + <FrameLayout 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:background="@*android:drawable/tab_unselected_holo"> + <TabWidget + android:id="@android:id/tabs" + android:orientation="horizontal" + android:measureWithLargestChild="false" 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:layout_gravity="center" /> + </FrameLayout> + + <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 +81,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/res/layout/label.xml b/res/layout/label.xml new file mode 100644 index 0000000..8d176fb --- /dev/null +++ b/res/layout/label.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index 694ba9b..9b20ab9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -24,7 +24,20 @@ <string name="unknown">Unknown</string> <string name="installing">Installing\u2026</string> <string name="install_done">App installed.</string> - <string name="install_confirm_question">Do you want to install this app?</string> + <!-- Message for installing a new app that requires some permissions [CHAR LIMIT=NONE] --> + <string name="install_confirm_question">Do you want to install this application? + It will get access to:</string> + <!-- Message for installing a new app that does not require permissions [CHAR LIMIT=NONE] --> + <string name="install_confirm_question_no_perms">Do you want to install this application? + It does not require any special access.</string> + <!-- Message for updating an existing app [CHAR LIMIT=NONE] --> + <string name="install_confirm_question_update">Do you want to install an update + to this existing application? Your existing data will not + be lost. The updated application will get access to:</string> + <!-- Message for updating an existing system app [CHAR LIMIT=NONE] --> + <string name="install_confirm_question_update_system">Do you want to install an update + to this built-in application? Your existing data will not + be lost. The updated application will get access to:</string> <string name="install_failed">App not installed.</string> <!-- Reason displayed when installation fails because the installation package itself is invalid in some way (e.g., corrupt) [CHAR LIMIT=100] --> @@ -106,4 +119,14 @@ <!-- Dialog attributes to indicate parse errors --> <string name="Parse_error_dlg_title">Parse error</string> <string name="Parse_error_dlg_text">There was a problem parsing the package.</string> + + <!-- Tab label for new permissions being added to an existing app [CHAR LIMIT=20] --> + <string name="newPerms">New</string> + <!-- Tab label for permissions related to user privacy [CHAR LIMIT=20] --> + <string name="privacyPerms">Privacy</string> + <!-- Tab label for permissions related to device behavior [CHAR LIMIT=20] --> + <string name="devicePerms">Device Access</string> + + <!-- Body text for new tab when there are no new permissions [CHAR LIMIT=NONE] --> + <string name="no_new_perms">This update requires no new permissions.</string> </resources> diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java index ab9bb5a..7051bdd 100644 --- a/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -31,14 +31,23 @@ import android.content.pm.PackageParser; 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.AttributeSet; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.View.OnClickListener; import android.widget.AppSecurityPermissions; import android.widget.Button; -import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TabHost; +import android.widget.TabWidget; +import android.widget.TextView; import java.io.File; +import java.util.ArrayList; /* * This activity is launched when a new application is installed via side loading @@ -71,27 +80,180 @@ 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>(); + + 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); + } + + @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 ScrollView(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 ScrollView(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 ScrollView(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); @@ -109,27 +271,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) @@ -253,13 +394,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) { |