summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Kralevich <nnk@google.com>2013-03-26 23:53:17 (GMT)
committerNick Kralevich <nnk@google.com>2013-03-27 22:35:03 (GMT)
commit292e4dabe97f77ea91945b2f520c0c680fabb145 (patch)
tree1f6b8d3f5a4ce4727dedec9782d3af0dd786667b
parent2055eecd55b944643111c4987e507f4a650f95e4 (diff)
downloadandroid_packages_apps_PackageInstaller-292e4dabe97f77ea91945b2f520c0c680fabb145.zip
android_packages_apps_PackageInstaller-292e4dabe97f77ea91945b2f520c0c680fabb145.tar.gz
android_packages_apps_PackageInstaller-292e4dabe97f77ea91945b2f520c0c680fabb145.tar.bz2
PackageInstaller: add permission granting support
Add support to PackageInstaller for allowing the user to grant permissions to other apps. The user is involved in the decision, and can approve or reject permissions. Applications can make a request to PackageInstaller using something like the following code: private void onPromptPermissionsClicked(String... permissions) { Intent i = getActivity().getApplication() .getPackageManager().requestPermission(permissions); startActivityForResult(i, 0); } This code reuses the sideloading upgrade flow when adding permissions, making the UI very familiar to someone who's sideloaded applications. Conceptually, we are treating a permission grant as a reinstall of the application with new permissions. Change-Id: Ia37f761e5574a490d1d37b9eb40cf5e77c928a40
-rw-r--r--AndroidManifest.xml10
-rw-r--r--res/values/strings.xml4
-rw-r--r--src/com/android/packageinstaller/GrantActivity.java229
3 files changed, 243 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b135b3f..8b04c7f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.GRANT_REVOKE_PERMISSIONS" />
<application android:label="@string/app_name"
android:allowBackup="false"
android:theme="@android:style/Theme.DeviceDefault.DialogWhenLarge.NoActionBar"
@@ -49,6 +50,15 @@
<activity android:name=".UninstallAppProgress"
android:configChanges="orientation|keyboardHidden|screenSize">
</activity>
+ <activity android:name=".GrantActivity"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
+ <intent-filter>
+ <action android:name="android.content.pm.action.REQUEST_PERMISSION" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<!--
<receiver android:name=".RemoveReceiver">
<intent-filter>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index db43792..a43af79 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -142,4 +142,8 @@
<!-- 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>
+
+ <string name="grant_confirm_question">Do you want to grant the following permissions?
+ It will get access to:</string>
+
</resources>
diff --git a/src/com/android/packageinstaller/GrantActivity.java b/src/com/android/packageinstaller/GrantActivity.java
new file mode 100644
index 0000000..a3f7f5c
--- /dev/null
+++ b/src/com/android/packageinstaller/GrantActivity.java
@@ -0,0 +1,229 @@
+/*
+** Copyright 2013, 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.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionInfo;
+import android.os.Bundle;
+import android.support.v4.view.ViewPager;
+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.util.ArrayList;
+import java.util.List;
+
+/*
+ * The activity which is responsible for asking the user to grant permissions
+ * to applications.
+ */
+public class GrantActivity extends Activity implements OnClickListener {
+ private Button mOk;
+ private Button mCancel;
+ private PackageManager mPm;
+ private String mRequestingPackage;
+ private String[] requested_permissions;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mPm = getPackageManager();
+ mRequestingPackage = this.getCallingPackage();
+
+ requested_permissions = getRequestedPermissions();
+ if (requested_permissions.length == 0) {
+ // The grant request was empty. Return success
+ setResult(RESULT_OK);
+ finish();
+ return;
+ }
+
+ PackageInfo pkgInfo = getUpdatedPackageInfo();
+ AppSecurityPermissions perms = new AppSecurityPermissions(this, pkgInfo);
+ if (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) == 0) {
+ // The updated permissions dialog said there are no new permissions.
+ // This should never occur if requested_permissions.length > 0,
+ // but we check for it anyway, just in case.
+ setResult(RESULT_OK);
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.install_start);
+ ((TextView)findViewById(R.id.install_confirm_question)).setText(R.string.grant_confirm_question);
+ PackageUtil.AppSnippet as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(pkgInfo.applicationInfo),
+ mPm.getApplicationIcon(pkgInfo.applicationInfo));
+ PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
+ mOk = (Button)findViewById(R.id.ok_button);
+ mOk.setText(R.string.ok);
+ mCancel = (Button)findViewById(R.id.cancel_button);
+ mOk.setOnClickListener(this);
+ mCancel.setOnClickListener(this);
+
+ TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
+ tabHost.setup();
+ ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
+ TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
+
+ View newTab = perms.getPermissionsView(AppSecurityPermissions.WHICH_NEW);
+ View allTab = getPermissionList(perms);
+
+ adapter.addTab(tabHost.newTabSpec("new").setIndicator(
+ getText(R.string.newPerms)), newTab);
+ adapter.addTab(tabHost.newTabSpec("all").setIndicator(
+ getText(R.string.allPerms)), allTab);
+ }
+
+ /**
+ * Returns a PackageInfo object representing the results of adding all the permissions
+ * in {@code requested_permissions} to {@code mRequestingPackage}. This is the package
+ * permissions the user will have if they accept the grant request.
+ */
+ private PackageInfo getUpdatedPackageInfo() {
+ try {
+ PackageInfo pkgInfo = mPm.getPackageInfo(mRequestingPackage, PackageManager.GET_PERMISSIONS);
+ for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) {
+ for (String requested_permission : requested_permissions) {
+ if (requested_permission.equals(pkgInfo.requestedPermissions[i])) {
+ pkgInfo.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
+ }
+ }
+ }
+
+ return pkgInfo;
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e); // will never occur
+ }
+ }
+
+ private View getPermissionList(AppSecurityPermissions perms) {
+ LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View root = inflater.inflate(R.layout.permissions_list, null);
+ View personalPermissions = perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL);
+ View devicePermissions = perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE);
+
+ ((ViewGroup)root.findViewById(R.id.privacylist)).addView(personalPermissions);
+ ((ViewGroup)root.findViewById(R.id.devicelist)).addView(devicePermissions);
+
+ return root;
+ }
+
+ /**
+ * Return an array of permissions requested by the caller, filtered to exclude
+ * irrelevant or otherwise malicious permission requests from untrusted callers.
+ */
+ private String[] getRequestedPermissions() {
+ String[] permissions = getIntent()
+ .getStringArrayExtra(PackageManager.EXTRA_REQUEST_PERMISSION_PERMISSION_LIST);
+ if (permissions == null) {
+ return new String[0];
+ }
+ permissions = keepNormalDangerousPermissions(permissions);
+ permissions = keepRequestingPackagePermissions(permissions);
+ return permissions;
+
+ }
+
+ /**
+ * Remove any permissions in {@code permissions} which are not present
+ * in {@code mRequestingPackage} and return the result. We also filter out
+ * permissions which are required by {@code mRequestingPackage}, and permissions
+ * which have already been granted to {@code mRequestingPackage}, as those permissions
+ * are useless to change.
+ */
+ private String[] keepRequestingPackagePermissions(String[] permissions) {
+ List<String> result = new ArrayList<String>();
+ try {
+ PackageInfo pkgInfo = mPm.getPackageInfo(mRequestingPackage, PackageManager.GET_PERMISSIONS);
+ if (pkgInfo.requestedPermissions == null) {
+ return new String[0];
+ }
+ for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) {
+ for (String permission : permissions) {
+ final boolean isRequired =
+ ((pkgInfo.requestedPermissionsFlags[i]
+ & PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
+ final boolean isGranted =
+ ((pkgInfo.requestedPermissionsFlags[i]
+ & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
+
+ /*
+ * We ignore required permissions, and permissions which have already
+ * been granted, as it's useless to grant those permissions.
+ */
+ if (permission.equals(pkgInfo.requestedPermissions[i])
+ && !isRequired && !isGranted) {
+ result.add(permission);
+ break;
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e); // should never happen
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
+ /**
+ * Filter the permissions in {@code permissions}, keeping only the NORMAL or DANGEROUS
+ * permissions.
+ *
+ * @param permissions the permissions to filter
+ * @return A subset of {@code permissions} with only the
+ * NORMAL or DANGEROUS permissions kept
+ */
+ private String[] keepNormalDangerousPermissions(String[] permissions) {
+ List<String> result = new ArrayList<String>();
+ for (String permission : permissions) {
+ try {
+ PermissionInfo pInfo = mPm.getPermissionInfo(permission, 0);
+ final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ if ((base != PermissionInfo.PROTECTION_NORMAL)
+ && (base != PermissionInfo.PROTECTION_DANGEROUS)) {
+ continue;
+ }
+ result.add(permission);
+ } catch (NameNotFoundException e) {
+ // ignore
+ }
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mOk) {
+ for (String permission : requested_permissions) {
+ mPm.grantPermission(mRequestingPackage, permission);
+ }
+ setResult(RESULT_OK);
+ }
+ if (v == mCancel) {
+ setResult(RESULT_CANCELED);
+ }
+ finish();
+ }
+}