diff options
8 files changed, 939 insertions, 2 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 781d60b3..3273ddef 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -62,7 +62,7 @@ android:configChanges="orientation|keyboardHidden|screenSize" android:exported="false" /> - <activity android:name=".GrantActivity" + <activity android:name=".permission.GrantPermissionsActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:excludeFromRecents="true" android:theme="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> @@ -72,5 +72,16 @@ </intent-filter> </activity> + <activity android:name=".permission.ManagePermissionsActivity" + android:configChanges="orientation|keyboardHidden|screenSize" + android:excludeFromRecents="true" + android:permission="android.permission.GRANT_REVOKE_PERMISSIONS"> + <intent-filter> + <action android:name="android.intent.action.MANAGE_APP_PERMISSIONS" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> -</manifest> + +</manifest> diff --git a/res/layout/grant_permissions.xml b/res/layout/grant_permissions.xml new file mode 100644 index 00000000..7c6bfb7a --- /dev/null +++ b/res/layout/grant_permissions.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:paddingTop="16dip" + android:paddingBottom="12dip" + android:paddingStart="16dip" + android:paddingEnd="16dip" + android:minHeight="108dip"> + + <ImageView + android:id="@+id/permission_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="6dip" + android:paddingEnd="10dip"> + </ImageView> + + <TextView + android:id="@+id/permission_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/permission_icon" + style="?android:attr/textAppearanceMedium"> + </TextView> + + <CheckBox + android:id="@+id/do_not_ask_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"> + </CheckBox> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@id/permission_message" + android:orientation="horizontal" + android:paddingTop="4dip"> + + <TextView + android:id="@+id/current_page_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:visibility="invisible"> + </TextView> + + <Button + android:id="@+id/permission_deny_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:attr/buttonBarButtonStyle" + android:text="@string/grant_dialog_button_deny"> + </Button> + + <Button + android:id="@+id/permission_allow_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:attr/buttonBarButtonStyle" + android:text="@string/grant_dialog_button_allow"> + </Button> + + </LinearLayout> + +</RelativeLayout> diff --git a/res/values/strings.xml b/res/values/strings.xml index c4327fbe..e807392f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -170,4 +170,31 @@ <string name="grant_confirm_question">Do you want to grant the following permissions? It will get access to:</string> + + <!-- Title for the dialog button to allow a permission grant. --> + <string name="grant_dialog_button_allow">Allow</string> + + <!-- Title for the dialog button to deny a permission grant. --> + <string name="grant_dialog_button_deny">Deny</string> + + <!-- Template for the current permission from the total number of permissions. --> + <string name="current_permission_template"> + <xliff:g id="current_permission_index" example="1">%1$s</xliff:g> of + <xliff:g id="permission_count" example="2">%2$s</xliff:g></string> + + <!-- Template for the warning message when an app requests a permission. --> + <string name="permission_warning_template">Allow + <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> to + <xliff:g id="action" example="do something">%2$s</xliff:g>?</string> + + <!-- Permissions --> + + <!-- Title of an application permission, listed so the user can choose whether they want + to allow the application to do this. --> + <string name="permlab_togglePermissions" translatable="false">toggle permissions</string> + <!-- Description of an application permission, listed so the user can choose whether + they want to allow the application to do this. --> + <string name="permdesc_togglePermissions" translatable="false">Allows the holder to launch + the UI for toggling app permissions. Should never be needed for normal apps.</string> + </resources> diff --git a/src/com/android/packageinstaller/permission/AppPermissions.java b/src/com/android/packageinstaller/permission/AppPermissions.java new file mode 100644 index 00000000..658accc1 --- /dev/null +++ b/src/com/android/packageinstaller/permission/AppPermissions.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2015 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.permission; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.os.Build; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Log; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.List; + +public final class AppPermissions { + private static final String LOG_TAG = "AppPermissions"; + + private final ArrayMap<String, PermissionGroup> mGroups = new ArrayMap<>(); + + private final Context mContext; + + private final PackageInfo mPackageInfo; + + private final String[] mFilterPermissions; + + private final CharSequence mAppLabel; + + public AppPermissions(Context context, PackageInfo packageInfo, String[] permissions) { + mContext = context; + mPackageInfo = packageInfo; + mFilterPermissions = permissions; + mAppLabel = packageInfo.applicationInfo.loadLabel(context.getPackageManager()); + loadPermissionGroups(); + } + + public void refresh() { + loadPermissionGroups(); + } + + public CharSequence getAppLabel() { + return mAppLabel; + } + + public PermissionGroup getPermissionGroup(String name) { + return mGroups.get(name); + } + + public List<PermissionGroup> getPermissionGroups() { + return new ArrayList<>(mGroups.values()); + } + + private void loadPermissionGroups() { + mGroups.clear(); + + final boolean appSupportsRuntimePermissions = mPackageInfo.applicationInfo.targetSdkVersion + > Build.VERSION_CODES.LOLLIPOP_MR1; + + for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { + String requestedPerm = mPackageInfo.requestedPermissions[i]; + + final PermissionInfo permInfo; + try { + permInfo = mContext.getPackageManager().getPermissionInfo(requestedPerm, 0); + } catch (NameNotFoundException e) { + Log.w(LOG_TAG, "Unknown permission: " + requestedPerm); + continue; + } + + String permName = permInfo.name; + String groupName = permInfo.group != null ? permInfo.group : permName; + + PermissionGroup group = mGroups.get(groupName); + if (group == null) { + PermissionGroupInfo groupInfo = null; + if (permInfo.group != null) { + try { + groupInfo = mContext.getPackageManager().getPermissionGroupInfo( + permInfo.group, 0); + } catch (NameNotFoundException e) { + Log.w(LOG_TAG, "Unknown group: " + permInfo.group); + } + } + + CharSequence groupLabel = (groupInfo != null) + ? groupInfo.loadLabel(mContext.getPackageManager()) + : permInfo.loadLabel(mContext.getPackageManager()); + + if (groupLabel == null) { + Log.w(LOG_TAG, "Neither permission nor group have name." + + " Ignoring permission: " + permInfo.name); + continue; + } + + final int iconResId = (groupInfo != null) ? groupInfo.icon : permInfo.icon; + + group = new PermissionGroup(mContext, mPackageInfo.packageName, + groupName, groupLabel, iconResId); + mGroups.put(groupName, group); + } + + final boolean runtime = appSupportsRuntimePermissions + && permInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS; + final boolean granted = (mPackageInfo.requestedPermissionsFlags[i] + & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; + + Permission permission = new Permission(permName, runtime, granted); + group.addPermission(permission); + } + + if (ArrayUtils.isEmpty(mFilterPermissions)) { + return; + } + + final int groupCount = mGroups.size(); + for (int i = groupCount - 1; i >= 0; i--) { + PermissionGroup group = mGroups.valueAt(i); + boolean groupHasPermission = false; + for (String filterPerm : mFilterPermissions) { + if (group.mPermissions.containsKey(filterPerm)) { + groupHasPermission = true; + break; + } + } + if (!groupHasPermission) { + mGroups.removeAt(i); + } + } + } + + public static final class PermissionGroup { + private final Context mContext; + private final String mPackageName; + + private final String mName; + private final CharSequence mLabel; + private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>(); + private final int mIconResId; + + private boolean mHasRuntimePermissions; + + public String getName() { + return mName; + } + + public int getIconResId() { + return mIconResId; + } + + public CharSequence getLabel() { + return mLabel; + } + + public PermissionGroup(Context context, String packageName, + String name, CharSequence label, int iconResId) { + mPackageName = packageName; + mContext = context; + mName = name; + mLabel = label; + mIconResId = iconResId; + } + + public boolean hasRuntimePermissions() { + return mHasRuntimePermissions; + } + + public boolean areRuntimePermissionsGranted() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (permission.mRuntime && !permission.mGranted) { + return false; + } + } + return true; + } + + public boolean grantRuntimePermissions() { + for (Permission permission : mPermissions.values()) { + if (permission.mRuntime && !permission.mGranted) { + mContext.getPackageManager().grantPermission(mPackageName, + permission.mName, new UserHandle(mContext.getUserId())); + permission.mGranted = true; + } + } + return true; + } + + public boolean revokeRuntimePermissions() { + for (Permission permission : mPermissions.values()) { + if (permission.mRuntime && permission.mGranted) { + mContext.getPackageManager().revokePermission(mPackageName, + permission.mName, new UserHandle(mContext.getUserId())); + permission.mGranted = false; + } + } + return true; + } + + public List<Permission> getPermissions() { + return new ArrayList<>(mPermissions.values()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + PermissionGroup other = (PermissionGroup) obj; + + if (mName == null) { + if (other.mName != null) { + return false; + } + } else if (!mName.equals(other.mName)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return mName != null ? mName.hashCode() : 0; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + builder.append("{name=").append(mName); + if (!mPermissions.isEmpty()) { + builder.append(", <has permissions>}"); + } else { + builder.append('}'); + } + return builder.toString(); + } + + void addPermission(Permission permission) { + mPermissions.put(permission.mName, permission); + if (permission.mRuntime) { + mHasRuntimePermissions = true; + } + } + } + + public static final class Permission { + private final String mName; + private final boolean mRuntime; + private boolean mGranted; + + public Permission(String name, boolean runtime, boolean granted) { + mName = name; + mRuntime = runtime; + mGranted = granted; + } + + public String getName() { + return mName; + } + + public boolean isGranted() { + return mGranted; + } + } +} diff --git a/src/com/android/packageinstaller/permission/GrantPermissionFragment.java b/src/com/android/packageinstaller/permission/GrantPermissionFragment.java new file mode 100644 index 00000000..65b60687 --- /dev/null +++ b/src/com/android/packageinstaller/permission/GrantPermissionFragment.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2015 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.permission; + +import android.app.DialogFragment; +import android.os.Bundle; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.packageinstaller.R; + +public final class GrantPermissionFragment extends DialogFragment { + public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME"; + public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT"; + public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX"; + public static final String ARG_GROUP_ICON_RES_ID = "ARG_GROUP_ICON"; + public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"; + + public interface OnRequestGrantPermissionGroupResult { + public void onRequestGrantPermissionGroupResult(String name, boolean granted); + } + + public static GrantPermissionFragment newInstance(String groupName, + int groupCount, int groupIndex, int iconResId, CharSequence message) { + GrantPermissionFragment instance = new GrantPermissionFragment(); + instance.setStyle(STYLE_NORMAL, + android.R.style.Theme_DeviceDefault_Light_Dialog_NoActionBar); + + Bundle arguments = new Bundle(); + arguments.putString(ARG_GROUP_NAME, groupName); + arguments.putInt(ARG_GROUP_COUNT, groupCount); + arguments.putInt(ARG_GROUP_INDEX, groupIndex); + arguments.putInt(ARG_GROUP_ICON_RES_ID, iconResId); + arguments.putCharSequence(ARG_GROUP_MESSAGE, message); + instance.setArguments(arguments); + + return instance; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View content = inflater.inflate(R.layout.grant_permissions, container, false); + + final String groupName = getArguments().getString(ARG_GROUP_NAME); + final String message = getArguments().getString(ARG_GROUP_MESSAGE); + final int iconResId = getArguments().getInt(ARG_GROUP_ICON_RES_ID); + final int groupCount = getArguments().getInt(ARG_GROUP_COUNT); + final int groupIndex = getArguments().getInt(ARG_GROUP_INDEX); + + final ImageView iconView = (ImageView) content.findViewById(R.id.permission_icon); + final View allowButton = content.findViewById(R.id.permission_allow_button); + final View denyButton = content.findViewById(R.id.permission_deny_button); + final View doNotAskCheckbox = content.findViewById(R.id.do_not_ask_checkbox); + final TextView currentGroupView = (TextView) content.findViewById(R.id.current_page_text); + final TextView messageView = (TextView) content.findViewById(R.id.permission_message); + + OnClickListener clickListener = new OnClickListener() { + @Override + public void onClick(View view) { + if (view == allowButton) { + ((OnRequestGrantPermissionGroupResult) getActivity()) + .onRequestGrantPermissionGroupResult(groupName, true); + } else if (view == denyButton) { + ((OnRequestGrantPermissionGroupResult) getActivity()) + .onRequestGrantPermissionGroupResult(groupName, false); + } else if (view == doNotAskCheckbox) { + //TODO: Implement me. + } + } + }; + + iconView.setImageResource(iconResId); + + messageView.setText(message); + + allowButton.setOnClickListener(clickListener); + denyButton.setOnClickListener(clickListener); + doNotAskCheckbox.setOnClickListener(clickListener); + + if (groupCount > 1) { + currentGroupView.setVisibility(View.VISIBLE); + currentGroupView.setText(getString(R.string.current_permission_template, + groupIndex + 1, groupCount)); + } else { + currentGroupView.setVisibility(View.INVISIBLE); + } + + return content; + } +} diff --git a/src/com/android/packageinstaller/permission/GrantPermissionsActivity.java b/src/com/android/packageinstaller/permission/GrantPermissionsActivity.java new file mode 100644 index 00000000..4c3eb247 --- /dev/null +++ b/src/com/android/packageinstaller/permission/GrantPermissionsActivity.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2015 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.permission; + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PermissionInfo; +import android.hardware.camera2.utils.ArrayUtils; +import android.os.Bundle; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; + +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.AppPermissions.Permission; +import com.android.packageinstaller.permission.AppPermissions.PermissionGroup; + +public class GrantPermissionsActivity extends Activity implements + GrantPermissionFragment.OnRequestGrantPermissionGroupResult { + private static final String LOG_TAG = "GrantPermissionsActivity"; + + private static final String TAG_GRANT_PERMISSION_GROUP_FRAGMENT = + "TAG_GRANT_PERMISSION_GROUP_FRAGMENT"; + + private static final int PERMISSION_GRANTED = 1; + private static final int PERMISSION_DENIED = 2; + private static final int PERMISSION_DENIED_RUNTIME = 3; + + private String[] mRequestedPermissions; + private int[] mGrantResults; + private final SparseArray<String> mRequestedRuntimePermissions = new SparseArray<>(); + + private ArrayMap<String, GroupState> mRequestGrantPermissionGroups = new ArrayMap<>(); + + private AppPermissions mAppPermissions; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mRequestedPermissions = getIntent().getStringArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES); + if (mRequestedPermissions == null) { + mRequestedPermissions = new String[0]; + } + + mGrantResults = new int[mRequestedPermissions.length]; + + final int requestedPermCount = mRequestedPermissions.length; + if (requestedPermCount == 0) { + setResultAndFinish(); + return; + } + + PackageInfo callingPackageInfo = getCallingPackageInfo(); + if (callingPackageInfo == null) { + setResultAndFinish(); + return; + } + + updateDefaultResults(callingPackageInfo); + + mAppPermissions = new AppPermissions(this, callingPackageInfo, mRequestedPermissions); + + for (PermissionGroup group : mAppPermissions.getPermissionGroups()) { + if (group.hasRuntimePermissions() && !group.areRuntimePermissionsGranted()) { + mRequestGrantPermissionGroups.put(group.getName(), new GroupState(group)); + } + } + + if (!showNextPermissionGroupFragment()) { + setResultAndFinish(); + } + } + + private boolean showNextPermissionGroupFragment() { + final int groupCount = mRequestGrantPermissionGroups.size(); + + for (int i = 0; i < groupCount; i++) { + GroupState groupState = mRequestGrantPermissionGroups.valueAt(i); + if (groupState.mGroup.hasRuntimePermissions() + && !groupState.mGroup.areRuntimePermissionsGranted() + && groupState.mState == GroupState.STATE_UNKNOWN) { + // Make sure adding the fragment we will remove is not in flight. + getFragmentManager().executePendingTransactions(); + + // Remove old grant fragment if such exists. + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + Fragment oldFragment = getFragmentManager().findFragmentByTag( + TAG_GRANT_PERMISSION_GROUP_FRAGMENT); + if (oldFragment != null) { + transaction.remove(oldFragment); + } + transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE); + transaction.commit(); + + CharSequence message = getString(R.string.permission_warning_template, + mAppPermissions.getAppLabel(), groupState.mGroup.getLabel()); + + // Add the new grant fragment. + // TODO: Use a real message for the action and a real icon. We need group action APIs + DialogFragment newFragment = GrantPermissionFragment + .newInstance(groupState.mGroup.getName(), groupCount, i, + android.R.drawable.ic_dialog_info, message); + + newFragment.show(getFragmentManager(), TAG_GRANT_PERMISSION_GROUP_FRAGMENT); + return true; + } + } + + return false; + } + + @Override + public void onRequestGrantPermissionGroupResult(String name, boolean granted) { + GroupState groupState = mRequestGrantPermissionGroups.get(name); + if (groupState.mGroup != null) { + if (granted) { + groupState.mGroup.grantRuntimePermissions(); + groupState.mState = GroupState.STATE_ALLOWED; + updateGrantResults(groupState.mGroup); + } else { + groupState.mState = GroupState.STATE_DENIED; + } + } + if (!showNextPermissionGroupFragment()) { + setResultAndFinish(); + } + } + + private void updateGrantResults(PermissionGroup group) { + for (Permission permission : group.getPermissions()) { + if (permission.isGranted()) { + final int index = ArrayUtils.getArrayIndex( + mRequestedPermissions, permission.getName()); + if (index >= 0) { + mGrantResults[index] = PackageManager.PERMISSION_GRANTED; + } + } + } + } + + private int computePermissionGrantState(PackageInfo callingPackageInfo, String permission) { + boolean permissionRequested = false; + + for (int i = 0; i < callingPackageInfo.requestedPermissions.length; i++) { + if (permission.equals(callingPackageInfo.requestedPermissions[i])) { + permissionRequested = true; + if ((callingPackageInfo.requestedPermissionsFlags[i] + & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { + return PERMISSION_GRANTED; + } + break; + } + } + + if (!permissionRequested) { + return PERMISSION_DENIED; + } + + try { + PermissionInfo pInfo = getPackageManager().getPermissionInfo(permission, 0); + if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) + != PermissionInfo.PROTECTION_DANGEROUS) { + return PERMISSION_DENIED; + } + } catch (NameNotFoundException e) { + /* ignore */ + } + + return PERMISSION_DENIED_RUNTIME; + } + + private PackageInfo getCallingPackageInfo() { + try { + return getPackageManager().getPackageInfo(getCallingPackage(), + PackageManager.GET_PERMISSIONS); + } catch (NameNotFoundException e) { + Log.i(LOG_TAG, "No package:" + getCallingPackage(), e); + return null; + } + } + + private void updateDefaultResults(PackageInfo callingPackageInfo) { + final int requestedPermCount = mRequestedPermissions.length; + for (int i = 0; i < requestedPermCount; i++) { + String permission = mRequestedPermissions[i]; + final int state = computePermissionGrantState(callingPackageInfo, permission); + switch (state) { + case PERMISSION_GRANTED: { + mGrantResults[i] = PackageManager.PERMISSION_GRANTED; + } break; + + case PERMISSION_DENIED: { + mGrantResults[i] = PackageManager.PERMISSION_DENIED; + } break; + + case PERMISSION_DENIED_RUNTIME: { + mGrantResults[i] = PackageManager.PERMISSION_DENIED; + mRequestedRuntimePermissions.put(i, permission); + } break; + } + } + } + + private void setResultAndFinish() { + Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS); + result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions); + result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, mGrantResults); + setResult(RESULT_OK, result); + finish(); + } + + private static final class GroupState { + public static final int STATE_UNKNOWN = 0; + public static final int STATE_ALLOWED = 1; + public static final int STATE_DENIED = 2; + + public final PermissionGroup mGroup; + public int mState = STATE_UNKNOWN; + + public GroupState(PermissionGroup group) { + mGroup = group; + } + } +} diff --git a/src/com/android/packageinstaller/permission/ManagePermissionsActivity.java b/src/com/android/packageinstaller/permission/ManagePermissionsActivity.java new file mode 100644 index 00000000..598f5e02 --- /dev/null +++ b/src/com/android/packageinstaller/permission/ManagePermissionsActivity.java @@ -0,0 +1,41 @@ +/* +* Copyright (C) 2015 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.permission; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +public final class ManagePermissionsActivity extends Activity { + private static final String LOG_TAG = "ManagePermissionsActivity"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); + if (packageName == null) { + Log.i(LOG_TAG, "Missing mandatory argument ARG_PACKAGE_NAME"); + finish(); + return; + } + + ManagePermissionsFragment fragment = ManagePermissionsFragment.newInstance(packageName); + getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit(); + } +} diff --git a/src/com/android/packageinstaller/permission/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ManagePermissionsFragment.java new file mode 100644 index 00000000..75e30aef --- /dev/null +++ b/src/com/android/packageinstaller/permission/ManagePermissionsFragment.java @@ -0,0 +1,128 @@ +/* +* Copyright (C) 2015 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.permission; + +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.util.Log; + +public final class ManagePermissionsFragment extends PreferenceFragment { + private static final String LOG_TAG = "ManagePermissionsFragment"; + + private AppPermissions mAppPermissions; + + public static ManagePermissionsFragment newInstance(String packageName) { + ManagePermissionsFragment instance = new ManagePermissionsFragment(); + Bundle arguments = new Bundle(); + arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + instance.setArguments(arguments); + return instance; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + bindUi(); + } + + @Override + public void onResume() { + super.onResume(); + updateUi(); + } + + private void bindUi() { + String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + + final PackageInfo packageInfo = getPackageInfo(packageName); + if (packageInfo == null) { + getActivity().finish(); + return; + } + + PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getActivity()); + mAppPermissions = new AppPermissions(getActivity(), + packageInfo, null); + + Preference.OnPreferenceChangeListener changeListener = + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String groupName = preference.getKey(); + AppPermissions.PermissionGroup group = mAppPermissions + .getPermissionGroup(groupName); + + if (group == null) { + return false; + } + + if (newValue == Boolean.TRUE) { + group.grantRuntimePermissions(); + } else { + group.revokeRuntimePermissions(); + } + + return true; + } + }; + + for (AppPermissions.PermissionGroup group : mAppPermissions.getPermissionGroups()) { + if (group.hasRuntimePermissions()) { + SwitchPreference preference = new SwitchPreference(getActivity()); + preference.setOnPreferenceChangeListener(changeListener); + preference.setKey(group.getName()); + preference.setIcon(group.getIconResId()); + preference.setTitle(group.getLabel()); + preference.setPersistent(false); + screen.addPreference(preference); + } + } + + setPreferenceScreen(screen); + } + + private void updateUi() { + mAppPermissions.refresh(); + + final int preferenceCount = getPreferenceScreen().getPreferenceCount(); + for (int i = 0; i < preferenceCount; i++) { + SwitchPreference preference = (SwitchPreference) + getPreferenceScreen().getPreference(i); + AppPermissions.PermissionGroup group = mAppPermissions + .getPermissionGroup(preference.getKey()); + if (group != null) { + preference.setChecked(group.areRuntimePermissionsGranted()); + } + } + } + + private PackageInfo getPackageInfo(String packageName) { + try { + return getActivity().getPackageManager().getPackageInfo( + packageName, PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + Log.i(LOG_TAG, "No package:" + getActivity().getCallingPackage(), e); + return null; + } + } +}
\ No newline at end of file |