1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.packageinstaller.permission.model;
18
19import android.app.LoaderManager;
20import android.app.LoaderManager.LoaderCallbacks;
21import android.content.AsyncTaskLoader;
22import android.content.Context;
23import android.content.Loader;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageItemInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.PermissionGroupInfo;
28import android.content.pm.PermissionInfo;
29import android.graphics.drawable.Drawable;
30import android.os.Bundle;
31import android.util.ArraySet;
32
33import com.android.packageinstaller.R;
34import com.android.packageinstaller.permission.utils.Utils;
35
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.List;
39import java.util.Set;
40
41public final class PermissionGroups implements LoaderCallbacks<List<PermissionGroup>> {
42    private final ArrayList<PermissionGroup> mGroups = new ArrayList<>();
43    private final Context mContext;
44    private final LoaderManager mLoaderManager;
45    private final PermissionsGroupsChangeCallback mCallback;
46
47    public interface PermissionsGroupsChangeCallback {
48        public void onPermissionGroupsChanged();
49    }
50
51    public PermissionGroups(Context context, LoaderManager loaderManager,
52            PermissionsGroupsChangeCallback callback) {
53        mContext = context;
54        mLoaderManager = loaderManager;
55        mCallback = callback;
56        mLoaderManager.initLoader(0, null, this);
57    }
58
59    @Override
60    public Loader<List<PermissionGroup>> onCreateLoader(int id, Bundle args) {
61        return new PermissionsLoader(mContext);
62    }
63
64    @Override
65    public void onLoadFinished(Loader<List<PermissionGroup>> loader,
66            List<PermissionGroup> groups) {
67        if (mGroups.equals(groups)) {
68            return;
69        }
70        mGroups.clear();
71        mGroups.addAll(groups);
72        mCallback.onPermissionGroupsChanged();
73    }
74
75    @Override
76    public void onLoaderReset(Loader<List<PermissionGroup>> loader) {
77        mGroups.clear();
78        mCallback.onPermissionGroupsChanged();
79    }
80
81    public List<PermissionGroup> getGroups() {
82        return mGroups;
83    }
84
85    public PermissionGroup getGroup(String name) {
86        for (PermissionGroup group : mGroups) {
87            if (group.getName().equals(name)) {
88                return group;
89            }
90        }
91        return null;
92    }
93
94    private static final class PermissionsLoader extends AsyncTaskLoader<List<PermissionGroup>>
95            implements PackageManager.OnPermissionsChangedListener {
96
97        public PermissionsLoader(Context context) {
98            super(context);
99        }
100
101        @Override
102        protected void onStartLoading() {
103            getContext().getPackageManager().addOnPermissionsChangeListener(this);
104            forceLoad();
105        }
106
107        @Override
108        protected void onStopLoading() {
109            getContext().getPackageManager().removeOnPermissionsChangeListener(this);
110        }
111
112        @Override
113        public List<PermissionGroup> loadInBackground() {
114            ArraySet<String> launcherPkgs = Utils.getLauncherPackages(getContext());
115            PermissionApps.PmCache pmCache = new PermissionApps.PmCache(
116                    getContext().getPackageManager());
117
118            List<PermissionGroup> groups = new ArrayList<>();
119            Set<String> seenPermissions = new ArraySet<>();
120
121            PackageManager packageManager = getContext().getPackageManager();
122            List<PermissionGroupInfo> groupInfos = packageManager.getAllPermissionGroups(0);
123
124            for (PermissionGroupInfo groupInfo : groupInfos) {
125                // Mare sure we respond to cancellation.
126                if (isLoadInBackgroundCanceled()) {
127                    return Collections.emptyList();
128                }
129
130                // Get the permissions in this group.
131                final List<PermissionInfo> groupPermissions;
132                try {
133                    groupPermissions = packageManager.queryPermissionsByGroup(groupInfo.name, 0);
134                } catch (PackageManager.NameNotFoundException e) {
135                    continue;
136                }
137
138                boolean hasRuntimePermissions = false;
139
140                // Cache seen permissions and see if group has runtime permissions.
141                for (PermissionInfo groupPermission : groupPermissions) {
142                    seenPermissions.add(groupPermission.name);
143                    if ((groupPermission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
144                            == PermissionInfo.PROTECTION_DANGEROUS
145                            && (groupPermission.flags & PermissionInfo.FLAG_INSTALLED) != 0
146                            && (groupPermission.flags & PermissionInfo.FLAG_REMOVED) == 0) {
147                        hasRuntimePermissions = true;
148                    }
149                }
150
151                // No runtime permissions - not interesting for us.
152                if (!hasRuntimePermissions) {
153                    continue;
154                }
155
156                CharSequence label = loadItemInfoLabel(groupInfo);
157                Drawable icon = loadItemInfoIcon(groupInfo);
158
159                PermissionApps permApps = new PermissionApps(getContext(), groupInfo.name, null,
160                        pmCache);
161                permApps.refreshSync();
162
163                // Create the group and add to the list.
164                PermissionGroup group = new PermissionGroup(groupInfo.name,
165                        groupInfo.packageName, label, icon, permApps.getTotalCount(launcherPkgs),
166                        permApps.getGrantedCount(launcherPkgs));
167                groups.add(group);
168            }
169
170
171            // Make sure we add groups for lone runtime permissions.
172            List<PackageInfo> installedPackages = getContext().getPackageManager()
173                    .getInstalledPackages(PackageManager.GET_PERMISSIONS);
174
175
176            // We will filter out permissions that no package requests.
177            Set<String> requestedPermissions = new ArraySet<>();
178            for (PackageInfo installedPackage : installedPackages) {
179                if (installedPackage.requestedPermissions == null) {
180                    continue;
181                }
182                for (String requestedPermission : installedPackage.requestedPermissions) {
183                    requestedPermissions.add(requestedPermission);
184                }
185            }
186
187            for (PackageInfo installedPackage : installedPackages) {
188                if (installedPackage.permissions == null) {
189                    continue;
190                }
191
192                for (PermissionInfo permissionInfo : installedPackage.permissions) {
193                    // If we have handled this permission, no more work to do.
194                    if (!seenPermissions.add(permissionInfo.name)) {
195                        continue;
196                    }
197
198                    // We care only about installed runtime permissions.
199                    if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
200                            != PermissionInfo.PROTECTION_DANGEROUS
201                            || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0) {
202                        continue;
203                    }
204
205                    // If no app uses this permission,
206                    if (!requestedPermissions.contains(permissionInfo.name)) {
207                        continue;
208                    }
209
210                    CharSequence label = loadItemInfoLabel(permissionInfo);
211                    Drawable icon = loadItemInfoIcon(permissionInfo);
212
213                    PermissionApps permApps = new PermissionApps(getContext(), permissionInfo.name,
214                            null, pmCache);
215                    permApps.refreshSync();
216
217                    // Create the group and add to the list.
218                    PermissionGroup group = new PermissionGroup(permissionInfo.name,
219                            permissionInfo.packageName, label, icon,
220                            permApps.getTotalCount(launcherPkgs),
221                            permApps.getGrantedCount(launcherPkgs));
222                    groups.add(group);
223                }
224            }
225
226            Collections.sort(groups);
227            return groups;
228        }
229
230        private CharSequence loadItemInfoLabel(PackageItemInfo itemInfo) {
231            CharSequence label = itemInfo.loadLabel(getContext().getPackageManager());
232            if (label == null) {
233                label = itemInfo.name;
234            }
235            return label;
236        }
237
238        private Drawable loadItemInfoIcon(PackageItemInfo itemInfo) {
239            Drawable icon = null;
240            if (itemInfo.icon > 0) {
241                icon = Utils.loadDrawable(getContext().getPackageManager(),
242                        itemInfo.packageName, itemInfo.icon);
243            }
244            if (icon == null) {
245                icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
246            }
247            return icon;
248        }
249
250        @Override
251        public void onPermissionsChanged(int uid) {
252            forceLoad();
253        }
254    }
255}
256