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

Commit 2f41aa79 authored by Jason Monk's avatar Jason Monk
Browse files

Add permissions screen to advanced apps

Add a screen that shows a list of permissions and how many apps
have been granted them, link to PackageInstaller for control of
the permissions.

Depends on I68cdbe53177f742daf396f4eb53761fd5cda2636
Change-Id: I183848ea89ea41a17eaf663441dc4ef963be99bf
parent ec5a3373
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -5638,6 +5638,7 @@
    <string name="keywords_keyboard_and_ime">text correction correct sound vibrate auto language gesture suggest suggestion theme offensive word type emoji international</string>
    <string name="keywords_reset_apps">reset preferences default</string>
    <string name="keywords_all_apps">apps download applications system</string>
    <string name="keywords_app_permissions">apps permissions security</string>
    <!-- Search keywords for different screen unlock modes : slide to unlock, password, pattern and PIN [CHAR LIMIT=none] -->
    <string name="keywords_lockscreen">slide password pattern pin</string>

@@ -6136,4 +6137,12 @@
   <!-- Title for profile selection dialog [CHAR LIMIT=30] -->
   <string name="choose_profile">Choose Profile</string>

   <!-- Label for list that shows all permissions -->
   <string name="app_permissions">App permissions</string>
   <!-- Summary of permissions currently granted to apps [CHAR LIMIT=45] -->
   <string name="app_permissions_summary"><xliff:g id="count" example="10">%d</xliff:g> of <xliff:g id="count" example="10">%d</xliff:g> apps allowed additional access</string>
   <!-- Summary of permissions currently granted to a single permission [CHAR
        LIMIT=45] -->
   <string name="app_permissions_group_summary"><xliff:g id="count" example="10">%d</xliff:g> of <xliff:g id="count" example="10">%d</xliff:g> apps allowed</string>

</resources>
+6 −0
Original line number Diff line number Diff line
@@ -19,6 +19,12 @@
    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
    android:key="applications_settings">

    <PreferenceScreen
        android:key="manage_perms"
        android:fragment="com.android.settings.applications.ManagePermissions"
        android:title="@string/app_permissions"
        settings:keywords="@string/keywords_app_permissions" />

    <PreferenceScreen
        android:key="all_apps"
        android:fragment="com.android.settings.applications.ManageApplications"
+17 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.NetworkPolicyManager;
import android.os.AsyncTask;
@@ -34,9 +35,11 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,22 +50,25 @@ import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.applications.ApplicationsState.AppEntry;
import com.android.settings.applications.ApplicationsState.Callbacks;
import com.android.settings.applications.ApplicationsState.Session;
import com.android.settings.applications.PermissionsInfo.Callback;

import java.util.ArrayList;
import java.util.List;

public class AdvancedAppSettings extends SettingsPreferenceFragment implements Callbacks,
        DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
        DialogInterface.OnClickListener, DialogInterface.OnDismissListener, Callback {

    static final String TAG = "AdvancedAppSettings";
    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final String KEY_APP_PERM = "manage_perms";
    private static final String KEY_ALL_APPS = "all_apps";
    private static final String KEY_RESET_ALL = "reset_all";
    private static final String EXTRA_RESET_DIALOG = "resetDialog";

    private ApplicationsState mApplicationsState;
    private Session mSession;
    private Preference mAppPerms;
    private Preference mAllApps;
    private Preference mResetAll;

@@ -75,6 +81,7 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements C
    private NetworkPolicyManager mNpm;
    private AppOpsManager mAom;
    private Handler mHandler;
    private PermissionsInfo mPermissionsInfo;

    @Override
    public void onCreate(Bundle icicle) {
@@ -84,6 +91,7 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements C
        mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
        mSession = mApplicationsState.newSession(this);

        mAppPerms = findPreference(KEY_APP_PERM);
        mAllApps = findPreference(KEY_ALL_APPS);
        mResetAll = findPreference(KEY_RESET_ALL);
        mResetAll.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@@ -136,6 +144,7 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements C
    public void onResume() {
        super.onResume();
        mActivityResumed = true;
        mPermissionsInfo = new PermissionsInfo(getActivity(), this);
    }

    @Override
@@ -265,4 +274,11 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements C
        // No-op.
    }

    @Override
    public void onPermissionLoadComplete() {
        mAppPerms.setSummary(getActivity().getString(R.string.app_permissions_summary,
                mPermissionsInfo.getRuntimePermAppsGrantedCount(),
                mPermissionsInfo.getRuntimePermAppsCount()));
    }

}
+117 −0
Original line number Diff line number Diff line
/*
 * 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.settings.applications;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.util.Log;

import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.applications.PermissionsInfo.PermissionGroup;

import java.util.List;

public class ManagePermissions extends SettingsPreferenceFragment
        implements PermissionsInfo.Callback, OnPreferenceClickListener {

    private static final String TAG = "ManagePermissions";

    private boolean mLoadComplete;
    private PermissionsInfo mPermissionsInfo;

    @Override
    public void onResume() {
        super.onResume();

        mPermissionsInfo = new PermissionsInfo(getActivity(), this);
    }

    private void refreshUi() {
        PreferenceScreen screen = getPreferenceScreen();
        if (screen == null) {
            screen = getPreferenceManager().createPreferenceScreen(getActivity());
            setPreferenceScreen(screen);
        } else {
            screen.removeAll();
        }
        final int count = screen.getPreferenceCount();
        if (count == 0) {
            List<PermissionGroup> groups = mPermissionsInfo.getGroups();
            for (PermissionGroup group : groups) {
                if (group.possibleApps.size() == 0) continue;
                PermissionPreference pref = new PermissionPreference(getActivity(), group);
                pref.refreshUi();
                screen.addPreference(pref);
            }
        } else {
            for (int i = 0; i < count; i++) {
                ((PermissionPreference) screen.getPreference(i)).refreshUi();
            }
        }
    }

    @Override
    public void onPermissionLoadComplete() {
        refreshUi();
    }

    @Override
    public boolean onPreferenceClick(Preference preference) {
        return true;
    }

    @Override
    protected int getMetricsCategory() {
        return MetricsLogger.MANAGE_PERMISSIONS;
    }

    private class PermissionPreference extends Preference implements OnPreferenceClickListener {
        private final PermissionGroup mGroup;

        public PermissionPreference(Context context, PermissionGroup group) {
            super(context);
            setOnPreferenceClickListener(this);
            mGroup = group;
        }

        public void refreshUi() {
            setTitle(mGroup.label);
            setIcon(mGroup.icon);
            setSummary(getContext().getString(R.string.app_permissions_group_summary,
                    mGroup.grantedApps.size(), mGroup.possibleApps.size()));
        }

        @Override
        public boolean onPreferenceClick(Preference preference) {
            Intent i = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
                    .putExtra(Intent.EXTRA_PERMISSION_NAME, mGroup.name);
            try {
                getActivity().startActivity(i);
            } catch (ActivityNotFoundException e) {
                Log.w(TAG, "No app to handle " + i.getAction());
            }
            return true;
        }
    }

}
+188 −0
Original line number Diff line number Diff line
/*
 * 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.settings.applications;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Log;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class PermissionsInfo {

    private static final String TAG = "PermissionsInfo";

    private final PackageManager mPm;
    private final ArrayList<PermissionGroup> mGroups = new ArrayList<>();
    private final Map<String, PermissionGroup> mGroupLookup = new ArrayMap<>();
    private final Callback mCallback;
    private final Context mContext;
    // Count of apps that request runtime permissions.
    private int mRuntimePermAppsCt;
    // Count of apps that are granted runtime permissions.
    private int mRuntimePermAppsGrantedCt;

    public PermissionsInfo(Context context, Callback callback) {
        mContext = context;
        mPm = context.getPackageManager();
        mCallback = callback;
        new PermissionsLoader().execute();
    }

    public List<PermissionGroup> getGroups() {
        synchronized (mGroups) {
            return new ArrayList<>(mGroups);
        }
    }

    public int getRuntimePermAppsCount() {
        return mRuntimePermAppsCt;
    }

    public int getRuntimePermAppsGrantedCount() {
        return mRuntimePermAppsGrantedCt;
    }

    private PermissionGroup getOrCreateGroup(String permission) {
        PermissionGroup group = mGroupLookup.get(permission);
        if (group == null) {
            // Some permissions don't have a group, in that case treat them like a group
            // and create their own PermissionGroup (only if they are runtime).
            try {
                PermissionInfo info = mPm.getPermissionInfo(permission, 0);
                if (info.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) {
                    group = new PermissionGroup();
                    // TODO: Add default permission icon.
                    group.icon = info.icon != 0 ? info.loadIcon(mPm) : new ShapeDrawable();
                    group.name = info.name;
                    group.label = info.loadLabel(mPm).toString();
                    mGroups.add(group);
                    mGroupLookup.put(permission, group);
                }
            } catch (NameNotFoundException e) {
                Log.w(TAG, "Unknown permission " + permission, e);
            }
        }
        return group;
    }

    private class PermissionsLoader extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            List<PermissionGroupInfo> groups =
                    mPm.getAllPermissionGroups(PackageManager.GET_META_DATA);
            // Get the groups.
            for (PermissionGroupInfo groupInfo : groups) {
                PermissionGroup group = new PermissionGroup();
                // TODO: Add default permission icon.
                group.icon = groupInfo.icon != 0 ? groupInfo.loadIcon(mPm) : new ShapeDrawable();
                group.name = groupInfo.name;
                group.label = groupInfo.loadLabel(mPm).toString();
                synchronized (mGroups) {
                    mGroups.add(group);
                }
            }
            // Load permissions and which are runtime.
            for (PermissionGroup group : mGroups) {
                try {
                    List<PermissionInfo> permissions =
                            mPm.queryPermissionsByGroup(group.name, 0);
                    for (PermissionInfo info : permissions) {
                        if (info.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) continue;
                        mGroupLookup.put(info.name, group);
                    }
                } catch (NameNotFoundException e) {
                    Log.w(TAG, "Problem getting permissions", e);
                }
            }
            // Load granted info.
            for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
                List<PackageInfo> allApps = mPm.getInstalledPackages(
                        PackageManager.GET_PERMISSIONS, user.getIdentifier());
                for (PackageInfo info : allApps) {
                    if (info.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
                            || info.requestedPermissions == null)  {
                        continue;
                    }
                    final int N = info.requestedPermissionsFlags.length;
                    boolean appHasRuntimePerms = false;
                    boolean appGrantedRuntimePerms = false;
                    for (int i = 0; i < N; i++) {
                        boolean granted = (info.requestedPermissionsFlags[i]
                                & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
                        PermissionGroup group = getOrCreateGroup(info.requestedPermissions[i]);
                        String key = Integer.toString(info.applicationInfo.uid);
                        if (group != null && !group.possibleApps.contains(key)) {
                            appHasRuntimePerms = true;
                            group.possibleApps.add(key);
                            if (granted) {
                                appGrantedRuntimePerms = true;
                                group.grantedApps.add(key);
                            }
                        }
                    }
                    if (appHasRuntimePerms) {
                        mRuntimePermAppsCt++;
                        if (appGrantedRuntimePerms) {
                            mRuntimePermAppsGrantedCt++;
                        }
                    }
                }
            }
            Collections.sort(mGroups);

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            mCallback.onPermissionLoadComplete();
        }
    }

    public static class PermissionGroup implements Comparable<PermissionGroup> {
        public final List<String> possibleApps = new ArrayList<>();
        public final List<String> grantedApps = new ArrayList<>();
        public String name;
        public String label;
        public Drawable icon;

        @Override
        public int compareTo(PermissionGroup another) {
            return label.compareTo(another.label);
        }
    }

    public interface Callback {
        void onPermissionLoadComplete();
    }

}