From 292e4dabe97f77ea91945b2f520c0c680fabb145 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Tue, 26 Mar 2013 16:53:17 -0700 Subject: 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 --- AndroidManifest.xml | 10 + res/values/strings.xml | 4 + .../android/packageinstaller/GrantActivity.java | 229 +++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 src/com/android/packageinstaller/GrantActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b135b3f6..8b04c7fa 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -11,6 +11,7 @@ + + + + + + + This update requires no new permissions. + + Do you want to grant the following permissions? + It will get access to: + diff --git a/src/com/android/packageinstaller/GrantActivity.java b/src/com/android/packageinstaller/GrantActivity.java new file mode 100644 index 00000000..a3f7f5c9 --- /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 result = new ArrayList(); + 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 result = new ArrayList(); + 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(); + } +} -- cgit v1.2.3