Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1ce1a950 authored by Svet Ganov's avatar Svet Ganov
Browse files

Update PermissionController to use the new historical ops API

The historical ops API changed and we had to update the code
to use the new APIs. The new APIs now read history from the
disk which would be a problem with the current impl where we
make multiple calls into the app ops manager - one per app.
It is more efficient to make a single call for all data we
care about. This change adds a new mechanism to load the data
once and off the main thread as the IPC would hit the disk.

Test: manual

bug:111061782

Change-Id: I86113929fdd4a80fe620ed3eccd591712de30f47
parent 6fce37d8
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -19,7 +19,8 @@
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    android:orientation="vertical"
    android:id="@+id/app_permission_root">

    <include layout="@layout/button_header" />

+2 −63
Original line number Diff line number Diff line
@@ -54,12 +54,8 @@ import com.android.packageinstaller.permission.utils.LocationUtils;
import com.android.packageinstaller.permission.utils.Utils;
import com.android.permissioncontroller.R;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
@@ -97,7 +93,6 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
    private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
    private final String mIconPkg;
    private final int mIconResId;
    private final List<AppPermissionUsage> mAppPermissionUsages;

    /** Delay changes until {@link #persistChanges} is called */
    private final boolean mDelayChanges;
@@ -206,7 +201,6 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
                groupInfo.packageName, groupLabel, loadGroupDescription(context, groupInfo),
                getRequest(groupInfo), getRequestDetail(groupInfo), getBackgroundRequest(groupInfo),
                getBackgroundRequestDetail(groupInfo), groupInfo.packageName, groupInfo.icon,
                getAppUsages(context, packageInfo, groupInfo.name, groupLabel, permissionNames),
                userHandle, delayChanges);

        // Parse and create permissions reqested by the app
@@ -306,14 +300,12 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>

            if (permission.isBackgroundPermission()) {
                if (group.getBackgroundPermissions() == null) {
                    List<AppPermissionUsage> usages = getAppUsages(context, group.getApp(),
                            group.getName(), group.getLabel(), new String[] { group.getName() });
                    group.mBackgroundPermissions = new AppPermissionGroup(group.mContext,
                            group.getApp(), group.getName(), group.getDeclaringPackage(),
                            group.getLabel(), group.getDescription(), group.getRequest(),
                            group.getRequestDetail(), group.getBackgroundRequest(),
                            group.getBackgroundRequestDetail(), group.getIconPkg(),
                            group.getIconResId(), usages, group.getUser(), delayChanges);
                            group.getIconResId(), group.getUser(), delayChanges);
                }

                group.getBackgroundPermissions().addPermission(permission);
@@ -365,53 +357,11 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
        return description;
    }

    private static List<AppPermissionUsage> getAppUsages(Context context, PackageInfo packageInfo,
            String groupName, CharSequence groupLabel, String[] permissionNames) {
        try {
            AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(
                    AppOpsManager.class);

            // Get the appops for the given permissions.  Note that this does not get the appops
            // whose switch is this permission group.
            // TODO: Use the real API instead of reflection once the API is finalized.
            int[] ops = new int[permissionNames.length];
            Method permissionToOpCodeMethod = AppOpsManager.class.getMethod("permissionToOpCode",
                    String.class);
            for (int i = 0, numPerms = permissionNames.length; i < numPerms; i++) {
                ops[i] = (int) permissionToOpCodeMethod.invoke(null, permissionNames[i]);
            }
            List<AppOpsManager.PackageOps> pkgOps = appOpsManager.getOpsForPackage(
                    packageInfo.applicationInfo.uid, packageInfo.packageName, ops);
            if (pkgOps == null) {
                return Collections.emptyList();
            }

            // Convert each single appop into an AppPermissionUsage.
            List<AppPermissionUsage> appPermissionUsages = new ArrayList<>();
            int numPkgOps = pkgOps.size();
            for (int packageNum = 0; packageNum < numPkgOps; packageNum++) {
                AppOpsManager.PackageOps pkgOp = pkgOps.get(packageNum);
                List<AppOpsManager.OpEntry> curOps = pkgOp.getOps();
                int numOps = curOps.size();
                for (int opNum = 0; opNum < numOps; opNum++) {
                    AppOpsManager.OpEntry op = curOps.get(opNum);
                    AppPermissionUsage appPermissionUsage = new AppPermissionUsage(pkgOp, op,
                            groupName, groupLabel);
                    appPermissionUsages.add(appPermissionUsage);
                }
            }
            appPermissionUsages.sort(Comparator.comparing(AppPermissionUsage::getTime).reversed());
            return appPermissionUsages;
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            return Collections.emptyList();
        }
    }

    private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
            String declaringPackage, CharSequence label, CharSequence description,
            @StringRes int request, @StringRes int requestDetail,
            @StringRes int backgroundRequest, @StringRes int backgroundRequestDetail,
            String iconPkg, int iconResId, List<AppPermissionUsage> appPermissionUsages,
            String iconPkg, int iconResId,
            UserHandle userHandle, boolean delayChanges) {
        mContext = context;
        mUserHandle = userHandle;
@@ -440,7 +390,6 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
            mIconPkg = context.getPackageName();
            mIconResId = R.drawable.ic_perm_device_info;
        }
        mAppPermissionUsages = appPermissionUsages;
    }

    public boolean doesSupportRuntimePermissions() {
@@ -515,16 +464,6 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
        return mLabel;
    }

    /**
     * Get a list of the permission usages by this app, sorted by last access time, with the most
     * recent first.
     *
     * @return a sort list of this app's permission usages.
     */
    public List<AppPermissionUsage> getAppPermissionUsage() {
        return mAppPermissionUsages;
    }

    /**
     * @hide
     * @return The resource Id of the request string.
+168 −36
Original line number Diff line number Diff line
@@ -16,64 +16,196 @@

package com.android.packageinstaller.permission.model;

import android.app.AppOpsManager;
import android.app.AppOpsManager.HistoricalOp;
import android.app.AppOpsManager.HistoricalPackageOps;
import android.app.AppOpsManager.OpEntry;

import androidx.annotation.NonNull;
import android.app.AppOpsManager.PackageOps;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * A single instance of an app accessing a permission.
 * Stats for permission usage of an app. This data is for a given time period,
 * i.e. does not contain the full history.
 */
public final class AppPermissionUsage {
    private final @NonNull AppOpsManager.PackageOps mPkgOp;
    private final @NonNull AppOpsManager.OpEntry mOp;
    private final @NonNull String mPermissionGroupName;
    private final @NonNull CharSequence mPermissionGroupLabel;
    private final @NonNull List<GroupUsage> mGroupUsages = new ArrayList<>();
    private final @NonNull PermissionApp mPermissionApp;

    private AppPermissionUsage(@NonNull PermissionApp permissionApp,
            @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage,
            @Nullable HistoricalPackageOps historicalUsage) {
        mPermissionApp = permissionApp;
        final int groupCount = groups.size();
        for (int i = 0; i < groupCount; i++) {
            final AppPermissionGroup group = groups.get(i);
            mGroupUsages.add(new GroupUsage(group, lastUsage, historicalUsage));
        }
    }

    AppPermissionUsage(@NonNull AppOpsManager.PackageOps pkgOp, @NonNull AppOpsManager.OpEntry op,
            @NonNull String permissionGroupName, @NonNull CharSequence permissionGroupLabel) {
        mPkgOp = pkgOp;
        mOp = op;
        mPermissionGroupName = permissionGroupName;
        mPermissionGroupLabel = permissionGroupLabel;
    public @NonNull PermissionApp getApp() {
        return mPermissionApp;
    }

    public @NonNull String getPackageName() {
        return mPkgOp.getPackageName();
        return mPermissionApp.getPackageName();
    }

    public int getUid() {
        return mPkgOp.getUid();
        return mPermissionApp.getUid();
    }

    public long getTime() {
        return mOp.getLastAccessTime();
    public long getLastAccessTime() {
        long lastAccessTime = 0;
        final int permissionCount = mGroupUsages.size();
        for (int i = 0; i < permissionCount; i++) {
            final GroupUsage groupUsage = mGroupUsages.get(i);
            lastAccessTime = Math.max(lastAccessTime, groupUsage.getLastAccessTime());
        }
        return lastAccessTime;
    }

    public @NonNull String getPermissionGroupName() {
        return mPermissionGroupName;
    public long getAccessCount() {
        long accessCount = 0;
        final int permissionCount = mGroupUsages.size();
        for (int i = 0; i < permissionCount; i++) {
            final GroupUsage permission = mGroupUsages.get(i);
            accessCount += permission.getAccessCount();
        }
        return accessCount;
    }

    public @NonNull CharSequence getPermissionGroupLabel() {
        return mPermissionGroupLabel;
    public @NonNull List<GroupUsage> getGroupUsages() {
        return mGroupUsages;
    }

    /**
     * Get the name of the permission (not the group) this represents.
     *
     * @return the name of the permission this represents.
     * Stats for permission usage of a permission group. This data is for a
     * given time period, i.e. does not contain the full history.
     */
    public String getPermissionName() {
        // TODO: Replace reflection with a proper API (probably in AppOpsManager).
        try {
            Method getOpMethod = AppOpsManager.OpEntry.class.getMethod("getOp");
            Method opToPermissionMethod = AppOpsManager.class.getMethod("opToPermission",
                    int.class);
            return (String) opToPermissionMethod.invoke(null, (int) getOpMethod.invoke(mOp));
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            return null;
    public static class GroupUsage {
        private final @NonNull AppPermissionGroup mGroup;
        private final @Nullable PackageOps mLastUsage;
        private final @Nullable HistoricalPackageOps mHistoricalUsage;

        GroupUsage(@NonNull AppPermissionGroup group, @Nullable PackageOps lastUsage,
                @Nullable HistoricalPackageOps historicalUsage) {
            mGroup = group;
            mLastUsage = lastUsage;
            mHistoricalUsage = historicalUsage;
        }

        public long getLastAccessTime() {
            if (mLastUsage == null) {
                return 0;
            }
            long lastAccessTime = 0;
            final ArrayList<Permission> permissions = mGroup.getPermissions();
            final int permissionCount = permissions.size();
            for (int i = 0; i < permissionCount; i++) {
                final Permission permission = permissions.get(i);
                final String opName = permission.getAppOp();
                final List<OpEntry> ops = mLastUsage.getOps();
                final int opCount = ops.size();
                for (int j = 0; j < opCount; j++) {
                    final OpEntry op = ops.get(j);
                    if (op.getOpStr().equals(opName)) {
                        lastAccessTime = Math.max(lastAccessTime, op.getLastAccessTime());
                    }
                }
            }
            return lastAccessTime;
        }


        public long getForegroundAccessCount() {
            if (mHistoricalUsage == null) {
                return 0;
            }
            return extractAggregate(HistoricalOp::getForegroundAccessCount);
        }

        public long getBackgroundAccessCount() {
            if (mHistoricalUsage == null) {
                return 0;
            }
            return extractAggregate(HistoricalOp::getBackgroundAccessCount);
        }

        public long getAccessCount() {
            if (mHistoricalUsage == null) {
                return 0;
            }
            return extractAggregate((HistoricalOp op) ->
                op.getForegroundAccessCount() + op.getBackgroundAccessCount()
            );
        }

        public long getAccessDuration() {
            if (mHistoricalUsage == null) {
                return 0;
            }
            return extractAggregate((HistoricalOp op) ->
                    op.getForegroundAccessDuration() + op.getBackgroundAccessDuration()
            );
        }

        private long extractAggregate(@NonNull Function<HistoricalOp, Long> extractor) {
            long aggregate = 0;
            final ArrayList<Permission> permissions = mGroup.getPermissions();
            final int permissionCount = permissions.size();
            for (int i = 0; i < permissionCount; i++) {
                final Permission permission = permissions.get(i);
                final String opName = permission.getAppOp();
                final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName);
                if (historicalOp != null) {
                    aggregate += extractor.apply(historicalOp);
                }
            }
            return aggregate;
        }

        public @NonNull AppPermissionGroup getGroup() {
            return mGroup;
        }
    }

    public static class Builder {
        private final @NonNull List<AppPermissionGroup> mGroups = new ArrayList<>();
        private final @NonNull PermissionApp mPermissionApp;
        private @Nullable PackageOps mLastUsage;
        private @Nullable HistoricalPackageOps mHistoricalUsage;

        public Builder(@NonNull PermissionApp permissionApp) {
            mPermissionApp = permissionApp;
        }

        public @NonNull Builder addGroup(@NonNull AppPermissionGroup group) {
            mGroups.add(group);
            return this;
        }

        public @NonNull Builder setLastUsage(@Nullable PackageOps lastUsage) {
            mLastUsage = lastUsage;
            return this;
        }

        public @NonNull Builder setHistoricalUsage(@Nullable HistoricalPackageOps historicalUsage) {
            mHistoricalUsage = historicalUsage;
            return this;
        }

        public @NonNull
        AppPermissionUsage build() {
            if (mGroups.isEmpty()) {
                throw new IllegalStateException("mGroups cannot be empty.");
            }
            return new AppPermissionUsage(mPermissionApp, mGroups, mLastUsage, mHistoricalUsage);
        }
    }
}
+43 −11
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ public class PermissionApps {

    private final Context mContext;
    private final String mGroupName;
    private final String mPackageName;
    private final PackageManager mPm;
    private final Callback mCallback;

@@ -63,18 +64,23 @@ public class PermissionApps {
    private boolean mSkipUi;
    private boolean mRefreshing;

    public PermissionApps(Context context, String groupName, String packageName) {
        this(context, groupName, packageName, null, null, null);
    }

    public PermissionApps(Context context, String groupName, Callback callback) {
        this(context, groupName, callback, null, null);
        this(context, groupName, null, callback, null, null);
    }

    public PermissionApps(Context context, String groupName, Callback callback,
            @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache) {
    public PermissionApps(Context context, String groupName, String packageName,
            Callback callback, @Nullable PmCache pmCache, @Nullable AppDataCache appDataCache) {
        mPmCache = pmCache;
        mAppDataCache = appDataCache;
        mContext = context;
        mPm = mContext.getPackageManager();
        mGroupName = groupName;
        mCallback = callback;
        mPackageName = packageName;
        loadGroupInfo();
    }

@@ -93,10 +99,6 @@ public class PermissionApps {
     * @param getUiInfo If the UI info should be updated
     */
    public void refresh(boolean getUiInfo) {
        if (mCallback == null) {
            throw new IllegalStateException("callback needs to be set");
        }

        if (!mRefreshing) {
            mRefreshing = true;
            mSkipUi = !getUiInfo;
@@ -161,6 +163,39 @@ public class PermissionApps {
        return mIcon;
    }

    private @NonNull List<PackageInfo> getPackageInfos(@NonNull UserHandle user) {
        List<PackageInfo> apps = (mPmCache != null) ? mPmCache.getPackages(
                user.getIdentifier()) : null;
        if (apps != null) {
            if (mPackageName != null) {
                final int appCount = apps.size();
                for (int i = 0; i < appCount; i++) {
                    final PackageInfo app = apps.get(i);
                    if (mPackageName.equals(app.packageName)) {
                        apps = new ArrayList<>(1);
                        apps.add(app);
                        return apps;
                    }
                }
            }
            return apps;
        }
        if (mPackageName == null) {
            return mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS,
                    user.getIdentifier());
        } else {
            try {
                final PackageInfo packageInfo = mPm.getPackageInfo(mPackageName,
                        PackageManager.GET_PERMISSIONS);
                apps = new ArrayList<>(1);
                apps.add(packageInfo);
                return apps;
            } catch (NameNotFoundException e) {
                return Collections.emptyList();
            }
        }
    }

    private List<PermissionApp> loadPermissionApps() {
        PackageItemInfo groupInfo = Utils.getGroupInfo(mGroupName, mContext);
        if (groupInfo == null) {
@@ -176,10 +211,7 @@ public class PermissionApps {

        UserManager userManager = mContext.getSystemService(UserManager.class);
        for (UserHandle user : userManager.getUserProfiles()) {
            List<PackageInfo> apps = mPmCache != null ? mPmCache.getPackages(user.getIdentifier())
                    : mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS,
                            user.getIdentifier());

            List<PackageInfo> apps = getPackageInfos(user);
            final int N = apps.size();
            for (int i = 0; i < N; i++) {
                PackageInfo app = apps.get(i);
+43 −11
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.UsesPermissionInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
@@ -140,6 +141,23 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
     */
    public static @NonNull List<PermissionGroup> getAllPermissionGroups(@NonNull Context context,
            @Nullable Supplier<Boolean> isCanceled, boolean getUiInfo) {
        return getPermissionGroups(context, isCanceled, getUiInfo, null, null);
    }

    /**
     * Return all permission groups in the system.
     *
     * @param context Context to use
     * @param isCanceled callback checked if the group resolution should be aborted
     * @param getUiInfo If the UI info for apps should be updated
     * @param groupName Optional group to filter for.
     * @param packageName Optional package to filter for.
     *
     * @return the list of all groups int the system
     */
    public static @NonNull List<PermissionGroup> getPermissionGroups(@NonNull Context context,
            @Nullable Supplier<Boolean> isCanceled, boolean getUiInfo, @Nullable String groupName,
            @Nullable String  packageName) {
        ArraySet<String> launcherPkgs = Utils.getLauncherPackages(context);
        PermissionApps.PmCache pmCache = new PermissionApps.PmCache(
                context.getPackageManager());
@@ -150,7 +168,7 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
        Set<String> seenPermissions = new ArraySet<>();

        PackageManager packageManager = context.getPackageManager();
        List<PermissionGroupInfo> groupInfos = packageManager.getAllPermissionGroups(0);
        List<PermissionGroupInfo> groupInfos = getPermissionGroupInfos(context, groupName);

        for (PermissionGroupInfo groupInfo : groupInfos) {
            // Mare sure we respond to cancellation.
@@ -171,8 +189,7 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
            // Cache seen permissions and see if group has runtime permissions.
            for (PermissionInfo groupPermission : groupPermissions) {
                seenPermissions.add(groupPermission.name);
                if ((groupPermission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
                        == PermissionInfo.PROTECTION_DANGEROUS
                if (groupPermission.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
                        && (groupPermission.flags & PermissionInfo.FLAG_INSTALLED) != 0
                        && (groupPermission.flags & PermissionInfo.FLAG_REMOVED) == 0) {
                    hasRuntimePermissions = true;
@@ -187,8 +204,8 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
            CharSequence label = loadItemInfoLabel(context, groupInfo);
            Drawable icon = loadItemInfoIcon(context, groupInfo);

            PermissionApps permApps = new PermissionApps(context, groupInfo.name, null,
                    pmCache, appDataCache);
            PermissionApps permApps = new PermissionApps(context, groupInfo.name, packageName,
                    null, pmCache, appDataCache);
            permApps.refreshSync(getUiInfo);

            // Create the group and add to the list.
@@ -207,11 +224,11 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
        // We will filter out permissions that no package requests.
        Set<String> requestedPermissions = new ArraySet<>();
        for (PackageInfo installedPackage : installedPackages) {
            if (installedPackage.requestedPermissions == null) {
            if (installedPackage.usesPermissions == null) {
                continue;
            }
            for (String requestedPermission : installedPackage.requestedPermissions) {
                requestedPermissions.add(requestedPermission);
            for (UsesPermissionInfo usedPermission : installedPackage.usesPermissions) {
                requestedPermissions.add(usedPermission.name);
            }
        }

@@ -227,8 +244,7 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
                }

                // We care only about installed runtime permissions.
                if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
                        != PermissionInfo.PROTECTION_DANGEROUS
                if (permissionInfo.getProtection() != PermissionInfo.PROTECTION_DANGEROUS
                        || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0) {
                    continue;
                }
@@ -242,7 +258,7 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
                Drawable icon = loadItemInfoIcon(context, permissionInfo);

                PermissionApps permApps = new PermissionApps(context, permissionInfo.name,
                        null, pmCache, appDataCache);
                        packageName, null, pmCache, appDataCache);
                permApps.refreshSync(getUiInfo);

                // Create the group and add to the list.
@@ -269,6 +285,22 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
        return groups;
    }

    private static @NonNull List<PermissionGroupInfo> getPermissionGroupInfos(
            @NonNull Context context, @Nullable String groupName) {
        if (groupName == null) {
            return context.getPackageManager().getAllPermissionGroups(0);
        }
        try {
            final PermissionGroupInfo groupInfo = context.getPackageManager()
                    .getPermissionGroupInfo(groupName, 0);
            final List<PermissionGroupInfo> groupInfos = new ArrayList<>(1);
            groupInfos.add(groupInfo);
            return groupInfos;
        } catch (PackageManager.NameNotFoundException e) {
            return Collections.emptyList();
        }
    }

    private static final class PermissionsLoader extends AsyncTaskLoader<List<PermissionGroup>>
            implements PackageManager.OnPermissionsChangedListener {
        private final boolean mGetUiInfo;
Loading