diff options
Diffstat (limited to 'src/com/android/packageinstaller/permission/model')
4 files changed, 828 insertions, 0 deletions
diff --git a/src/com/android/packageinstaller/permission/model/AppPermissions.java b/src/com/android/packageinstaller/permission/model/AppPermissions.java new file mode 100644 index 00000000..9480ce90 --- /dev/null +++ b/src/com/android/packageinstaller/permission/model/AppPermissions.java @@ -0,0 +1,125 @@ +/* + * 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.model; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.util.ArrayMap; + +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class 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 PackageInfo getPackageInfo() { + return mPackageInfo; + } + + 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() { + List<PermissionGroup> groups = new ArrayList<>(); + + if (mPackageInfo.requestedPermissions == null) { + return; + } + + for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { + String requestedPerm = mPackageInfo.requestedPermissions[i]; + + if (hasGroupForPermission(requestedPerm, groups)) { + continue; + } + + PermissionGroup group = PermissionGroup.create(mContext, + mPackageInfo, requestedPerm); + if (group == null) { + continue; + } + + groups.add(group); + } + + if (!ArrayUtils.isEmpty(mFilterPermissions)) { + final int groupCount = groups.size(); + for (int i = groupCount - 1; i >= 0; i--) { + PermissionGroup group = groups.get(i); + boolean groupHasPermission = false; + for (String filterPerm : mFilterPermissions) { + if (group.hasPermission(filterPerm)) { + groupHasPermission = true; + break; + } + } + if (!groupHasPermission) { + groups.remove(i); + } + } + } + + Collections.sort(groups); + + mGroups.clear(); + for (PermissionGroup group : groups) { + mGroups.put(group.getName(), group); + } + } + + private static boolean hasGroupForPermission(String permission, + List<PermissionGroup> groups) { + for (PermissionGroup group : groups) { + if (group.hasPermission(permission)) { + return true; + } + } + return false; + } +} diff --git a/src/com/android/packageinstaller/permission/model/Permission.java b/src/com/android/packageinstaller/permission/model/Permission.java new file mode 100644 index 00000000..1f51fea9 --- /dev/null +++ b/src/com/android/packageinstaller/permission/model/Permission.java @@ -0,0 +1,57 @@ +/* + * 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.model; + +public final class Permission { + private final String mName; + private final int mAppOp; + + private boolean mGranted; + private boolean mAppOpAllowed; + + public Permission(String name, boolean granted, + int appOp, boolean appOpAllowed) { + mName = name; + mGranted = granted; + mAppOp = appOp; + mAppOpAllowed = appOpAllowed; + } + + public String getName() { + return mName; + } + + public int getAppOp() { + return mAppOp; + } + + public boolean isGranted() { + return mGranted; + } + + public void setGranted(boolean mGranted) { + this.mGranted = mGranted; + } + + public boolean isAppOpAllowed() { + return mAppOpAllowed; + } + + public void setAppOpAllowed(boolean mAppOpAllowed) { + this.mAppOpAllowed = mAppOpAllowed; + } +}
\ No newline at end of file diff --git a/src/com/android/packageinstaller/permission/model/PermissionApps.java b/src/com/android/packageinstaller/permission/model/PermissionApps.java new file mode 100644 index 00000000..8f28433b --- /dev/null +++ b/src/com/android/packageinstaller/permission/model/PermissionApps.java @@ -0,0 +1,266 @@ +/* + * 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.model; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PermissionInfo; +import android.graphics.LightingColorFilter; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.ArrayMap; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class PermissionApps { + private static final String LOG_TAG = "PermissionApps"; + + private final Context mContext; + private final String mGroupName; + private final PackageManager mPm; + private final Callback mCallback; + + private CharSequence mLabel; + private Drawable mIcon; + private List<PermissionApp> mPermApps; + // Map (pkg|uid) -> AppPermission + private ArrayMap<String, PermissionApp> mAppLookup; + + public PermissionApps(Context context, String groupName, Callback callback) { + mContext = context; + mPm = mContext.getPackageManager(); + mGroupName = groupName; + mCallback = callback; + loadGroupInfo(); + new PermissionAppsLoader().execute(); + } + + public void refresh() { + new PermissionAppsLoader().execute(); + } + + public Collection<PermissionApp> getApps() { + return mPermApps; + } + + public PermissionApp getApp(String key) { + return mAppLookup.get(key); + } + + public CharSequence getLabel() { + return mLabel; + } + + public Drawable getIcon() { + return mIcon; + } + + private void loadGroupInfo() { + PackageItemInfo info; + try { + info = mPm.getPermissionGroupInfo(mGroupName, 0); + } catch (PackageManager.NameNotFoundException e) { + try { + PermissionInfo permInfo = mPm.getPermissionInfo(mGroupName, 0); + if (permInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { + Log.w(LOG_TAG, mGroupName + " is not a runtime permission"); + return; + } + info = permInfo; + } catch (NameNotFoundException reallyNotFound) { + Log.w(LOG_TAG, "Can't find permission: " + mGroupName, reallyNotFound); + return; + } + } + mLabel = info.loadLabel(mPm); + mIcon = info.loadUnbadgedIcon(mPm); + LightingColorFilter filter = new LightingColorFilter(0, 0xffffffff); + mIcon.setColorFilter(filter); + } + + public static class PermissionApp implements Comparable<PermissionApp> { + private final PermissionGroup mPermissionGroup; + private final String mLabel; + private final Drawable mIcon; + + public PermissionApp(PermissionGroup permissionGroup, String label, + Drawable icon) { + mPermissionGroup = permissionGroup; + mLabel = label; + mIcon = icon; + } + + public String getKey() { + return Integer.toString(getUid()); + } + + public String getLabel() { + return mLabel; + } + + public Drawable getIcon() { + return mIcon; + } + + public boolean areRuntimePermissionsGranted() { + return mPermissionGroup.areRuntimePermissionsGranted(); + } + + public void grantRuntimePermissions() { + mPermissionGroup.grantRuntimePermissions(); + } + + public void revokeRuntimePermissions() { + mPermissionGroup.revokeRuntimePermissions(); + } + + @Override + public int compareTo(PermissionApp another) { + final int result = mLabel.compareTo(another.mLabel); + if (result == 0) { + // Unbadged before badged. + return getUid() - another.getUid(); + } + return result; + } + + private int getUid() { + return mPermissionGroup.getApp().applicationInfo.uid; + } + } + + private class PermissionAppsLoader extends AsyncTask<Void, Void, List<PermissionApp>> { + @Override + protected List<PermissionApp> doInBackground(Void... args) { + PackageItemInfo groupInfo = getGroupInfo(mGroupName); + if (groupInfo == null) { + return Collections.emptyList(); + } + + List<PermissionInfo> groupPermInfos = getGroupPermissionInfos(mGroupName); + if (groupPermInfos == null) { + return Collections.emptyList(); + } + + ArrayList<PermissionApp> permApps = new ArrayList<>(); + + for (UserHandle user : UserManager.get(mContext).getUserProfiles()) { + List<PackageInfo> apps = mPm.getInstalledPackages( + PackageManager.GET_PERMISSIONS, user.getIdentifier()); + + final int N = apps.size(); + for (int i = 0; i < N; i++) { + PackageInfo app = apps.get(i); + if (app.requestedPermissions == null) { + continue; + } + + for (int j = 0; j < app.requestedPermissions.length; j++) { + String requestedPerm = app.requestedPermissions[j]; + + boolean requestsPermissionInGroup = false; + + for (PermissionInfo groupPermInfo : groupPermInfos) { + if (groupPermInfo.name.equals(requestedPerm)) { + requestsPermissionInGroup = true; + break; + } + } + + if (!requestsPermissionInGroup) { + continue; + } + + PermissionGroup group = PermissionGroup.create(mContext, + app, groupInfo, groupPermInfos); + + PermissionApp permApp = new PermissionApp(group, + app.applicationInfo.loadLabel(mPm).toString(), + getBadgedIcon(app.applicationInfo)); + + permApps.add(permApp); + } + } + } + + Collections.sort(permApps); + + return permApps; + } + + private PackageItemInfo getGroupInfo(String groupName) { + try { + return mContext.getPackageManager().getPermissionGroupInfo(groupName, 0); + } catch (NameNotFoundException e) { + /* ignore */ + } + try { + return mContext.getPackageManager().getPermissionInfo(groupName, 0); + } catch (NameNotFoundException e2) { + /* ignore */ + } + return null; + } + + private List<PermissionInfo> getGroupPermissionInfos(String groupName) { + try { + return mContext.getPackageManager().queryPermissionsByGroup(groupName, 0); + } catch (NameNotFoundException e) { + /* ignore */ + } + try { + PermissionInfo permissionInfo = mContext.getPackageManager() + .getPermissionInfo(groupName, 0); + List<PermissionInfo> permissions = new ArrayList<>(); + permissions.add(permissionInfo); + return permissions; + } catch (NameNotFoundException e2) { + /* ignore */ + } + return null; + } + + private Drawable getBadgedIcon(ApplicationInfo appInfo) { + Drawable unbadged = appInfo.loadUnbadgedIcon(mPm); + return mPm.getUserBadgedIcon(unbadged, + new UserHandle(UserHandle.getUserId(appInfo.uid))); + } + + @Override + protected void onPostExecute(List<PermissionApp> result) { + mAppLookup = new ArrayMap<>(); + for (PermissionApp app : result) { + mAppLookup.put(app.getKey(), app); + } + mPermApps = result; + mCallback.onPermissionsLoaded(); + } + } + + public interface Callback { + void onPermissionsLoaded(); + } +} diff --git a/src/com/android/packageinstaller/permission/model/PermissionGroup.java b/src/com/android/packageinstaller/permission/model/PermissionGroup.java new file mode 100644 index 00000000..9a5291d8 --- /dev/null +++ b/src/com/android/packageinstaller/permission/model/PermissionGroup.java @@ -0,0 +1,380 @@ +/* + * 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.model; + +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.os.Build; +import android.os.UserHandle; +import android.util.ArrayMap; + +import java.util.ArrayList; +import java.util.List; + +public final class PermissionGroup implements Comparable<PermissionGroup> { + private static final String PLATFORM_PACKAGE_NAME = "android"; + + private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"; + + private final Context mContext; + private final AppOpsManager mAppOps; + private final ActivityManager mActivityManager; + + private final PackageInfo mPackageInfo; + private final String mName; + private final CharSequence mLabel; + private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>(); + private final String mIconPkg; + private final int mIconResId; + + private final boolean mAppSupportsRuntimePermissions; + + public static PermissionGroup create(Context context, PackageInfo packageInfo, + String permissionName) { + PermissionInfo permissionInfo; + try { + permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + + if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { + return null; + } + + PackageItemInfo groupInfo = permissionInfo; + if (permissionInfo.group != null) { + try { + groupInfo = context.getPackageManager().getPermissionGroupInfo( + permissionInfo.group, 0); + } catch (PackageManager.NameNotFoundException e) { + /* ignore */ + } + } + + List<PermissionInfo> permissionInfos = null; + if (groupInfo instanceof PermissionGroupInfo) { + try { + permissionInfos = context.getPackageManager().queryPermissionsByGroup( + groupInfo.name, 0); + } catch (PackageManager.NameNotFoundException e) { + /* ignore */ + } + } + + return create(context, packageInfo, groupInfo, permissionInfos); + + } + + public static PermissionGroup create(Context context, PackageInfo packageInfo, + PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos) { + + PermissionGroup group = new PermissionGroup(context, packageInfo, groupInfo.name, + groupInfo.loadLabel(context.getPackageManager()), groupInfo.packageName, + groupInfo.icon); + + if (groupInfo instanceof PermissionInfo) { + permissionInfos = new ArrayList<>(); + permissionInfos.add((PermissionInfo) groupInfo); + } + + if (permissionInfos == null || permissionInfos.isEmpty()) { + return null; + } + + final int permissionCount = packageInfo.requestedPermissions.length; + for (int i = 0; i < permissionCount; i++) { + String requestedPermission = packageInfo.requestedPermissions[i]; + + PermissionInfo requestedPermissionInfo = null; + + for (PermissionInfo permissionInfo : permissionInfos) { + if (requestedPermission.equals(permissionInfo.name)) { + requestedPermissionInfo = permissionInfo; + break; + } + } + + if (requestedPermissionInfo == null) { + continue; + } + + // Collect only runtime permissions. + if (requestedPermissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) { + continue; + } + + // Don't allow toggle of non platform defined permissions for legacy apps via app ops. + if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1 + && !PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)) { + continue; + } + + + final boolean granted = (packageInfo.requestedPermissionsFlags[i] + & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; + + final int appOp; + final boolean appOpAllowed; + + if (group.mAppSupportsRuntimePermissions) { + appOp = AppOpsManager.OP_NONE; + appOpAllowed = false; + } else { + appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName) + ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name) + : AppOpsManager.OP_NONE; + appOpAllowed = appOp != AppOpsManager.OP_NONE + && context.getSystemService(AppOpsManager.class).checkOp(appOp, + packageInfo.applicationInfo.uid, packageInfo.packageName) + == AppOpsManager.MODE_ALLOWED; + } + + Permission permission = new Permission(requestedPermission, granted, + appOp, appOpAllowed); + group.addPermission(permission); + } + + return group; + } + + private PermissionGroup(Context context, PackageInfo packageInfo, String name, + CharSequence label, String iconPkg, int iconResId) { + mContext = context; + mPackageInfo = packageInfo; + mAppSupportsRuntimePermissions = packageInfo.applicationInfo + .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; + mAppOps = context.getSystemService(AppOpsManager.class); + mActivityManager = context.getSystemService(ActivityManager.class); + mName = name; + mLabel = label; + mIconPkg = iconPkg; + mIconResId = iconResId; + } + + public PackageInfo getApp() { + return mPackageInfo; + } + + public String getName() { + return mName; + } + + public String getIconPkg() { + return mIconPkg; + } + + public int getIconResId() { + return mIconResId; + } + + public CharSequence getLabel() { + return mLabel; + } + + public boolean hasPermission(String permission) { + return mPermissions.get(permission) != null; + } + + public boolean areRuntimePermissionsGranted() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (mAppSupportsRuntimePermissions) { + if (!permission.isGranted()) { + return false; + } + } else if (permission.isGranted() && permission.getAppOp() + != AppOpsManager.OP_NONE && !permission.isAppOpAllowed()) { + return false; + } + } + return true; + } + + public boolean grantRuntimePermissions() { + final boolean isSharedUser = mPackageInfo.sharedUserId != null; + final int uid = mPackageInfo.applicationInfo.uid; + + // We toggle permissions only to apps that support runtime + // permissions, otherwise we toggle the app op corresponding + // to the permission if the permission is granted to the app. + for (Permission permission : mPermissions.values()) { + if (mAppSupportsRuntimePermissions) { + if (!permission.isGranted()) { + mContext.getPackageManager().grantPermission(mPackageInfo.packageName, + permission.getName(), new UserHandle(mContext.getUserId())); + permission.setGranted(true); + } + } else { + // Legacy apps cannot have a not granted permission but just in case. + // Also if the permissions has no corresponding app op, then it is a + // third-party one and we do not offer toggling of such permissions. + if (!permission.isGranted() || permission.getAppOp() == AppOpsManager.OP_NONE) { + continue; + } + + if (!permission.isAppOpAllowed()) { + // It this is a shared user we want to enable the app op for all + // packages in the shared user to match the behavior of this + // shared user having a runtime permission. + if (isSharedUser) { + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + for (String packageName : packageNames) { + mAppOps.setMode(permission.getAppOp(), uid, packageName, + AppOpsManager.MODE_ALLOWED); + } + } else { + mAppOps.setMode(permission.getAppOp(), uid, mPackageInfo.packageName, + AppOpsManager.MODE_ALLOWED); + } + + // Legacy apps do not know that they have to retry access to a + // resource due to changes in runtime permissions (app ops in this + // case). Therefore, we restart them on app op change, so they + // can pick up the change. + mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); + + permission.setAppOpAllowed(true); + } + } + } + + return true; + } + + public boolean revokeRuntimePermissions() { + final boolean isSharedUser = mPackageInfo.sharedUserId != null; + final int uid = mPackageInfo.applicationInfo.uid; + + // We toggle permissions only to apps that support runtime + // permissions, otherwise we toggle the app op corresponding + // to the permission if the permission is granted to the app. + for (Permission permission : mPermissions.values()) { + if (mAppSupportsRuntimePermissions) { + if (permission.isGranted()) { + mContext.getPackageManager().revokePermission(mPackageInfo.packageName, + permission.getName(), new UserHandle(mContext.getUserId())); + permission.setGranted(false); + } + } else { + // Legacy apps cannot have a non-granted permission but just in case. + // Also if the permission has no corresponding app op, then it is a + // third-party one and we do not offer toggling of such permissions. + if (!permission.isGranted() || permission.getAppOp() == AppOpsManager.OP_NONE) { + continue; + } + + if (permission.isAppOpAllowed()) { + // It this is a shared user we want to enable the app op for all + // packages the the shared user to match the behavior of this + // shared user having a runtime permission. + if (isSharedUser) { + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + for (String packageName : packageNames) { + mAppOps.setMode(permission.getAppOp(), uid, + packageName, AppOpsManager.MODE_IGNORED); + } + } else { + mAppOps.setMode(permission.getAppOp(), uid, + mPackageInfo.packageName, AppOpsManager.MODE_IGNORED); + } + + // Disabling an app op may put the app in a situation in which it + // has a handle to state it shouldn't have, so we have to kill the + // app. This matches the revoke runtime permission behavior. + mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); + + permission.setAppOpAllowed(false); + } + } + } + + return true; + } + + public List<Permission> getPermissions() { + return new ArrayList<>(mPermissions.values()); + } + + @Override + public int compareTo(PermissionGroup another) { + final int result = mLabel.toString().compareTo(another.mLabel.toString()); + if (result == 0) { + // Unbadged before badged. + return mPackageInfo.applicationInfo.uid + - another.mPackageInfo.applicationInfo.uid; + } + return result; + } + + @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.getName(), permission); + } +} + |