• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 
17 package com.android.packageinstaller.permission.ui.auto;
18 
19 import android.app.AlertDialog;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageItemInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.PermissionGroupInfo;
27 import android.content.pm.PermissionInfo;
28 import android.graphics.drawable.Drawable;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.view.View;
35 import android.widget.Switch;
36 
37 import androidx.annotation.NonNull;
38 import androidx.preference.Preference;
39 import androidx.preference.PreferenceCategory;
40 import androidx.preference.PreferenceGroup;
41 import androidx.preference.PreferenceViewHolder;
42 import androidx.preference.SwitchPreference;
43 
44 import com.android.packageinstaller.auto.AutoSettingsFrameFragment;
45 import com.android.packageinstaller.permission.model.AppPermissionGroup;
46 import com.android.packageinstaller.permission.model.Permission;
47 import com.android.packageinstaller.permission.utils.ArrayUtils;
48 import com.android.packageinstaller.permission.utils.Utils;
49 import com.android.permissioncontroller.R;
50 
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.List;
54 
55 /** Screen which shows all permissions for a particular app. */
56 public class AutoAllAppPermissionsFragment extends AutoSettingsFrameFragment {
57 
58     private static final String LOG_TAG = "AllAppPermsFrag";
59     private static final String KEY_OTHER = "other_perms";
60 
61     private List<AppPermissionGroup> mGroups;
62 
63     /** Creates an {@link AutoAllAppPermissionsFragment} with no filter. */
newInstance(@onNull String packageName, @NonNull UserHandle userHandle)64     public static AutoAllAppPermissionsFragment newInstance(@NonNull String packageName,
65             @NonNull UserHandle userHandle) {
66         return newInstance(packageName, /* filterGroup= */ null, userHandle);
67     }
68 
69     /** Creates an {@link AutoAllAppPermissionsFragment} with a specific filter group. */
newInstance(@onNull String packageName, @NonNull String filterGroup, @NonNull UserHandle userHandle)70     public static AutoAllAppPermissionsFragment newInstance(@NonNull String packageName,
71             @NonNull String filterGroup, @NonNull UserHandle userHandle) {
72         AutoAllAppPermissionsFragment instance = new AutoAllAppPermissionsFragment();
73         Bundle arguments = new Bundle();
74         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
75         arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, filterGroup);
76         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
77         instance.setArguments(arguments);
78         return instance;
79     }
80 
81     @Override
onCreatePreferences(Bundle bundle, String s)82     public void onCreatePreferences(Bundle bundle, String s) {
83         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
84     }
85 
86     @Override
onStart()87     public void onStart() {
88         super.onStart();
89 
90         // If we target a group make this look like app permissions.
91         if (getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null) {
92             setHeaderLabel(getContext().getString(R.string.all_permissions));
93         } else {
94             setHeaderLabel(getContext().getString(R.string.app_permissions));
95         }
96 
97         updateUi();
98     }
99 
100     @Override
onStop()101     public void onStop() {
102         super.onStop();
103         getPreferenceScreen().removeAll();
104     }
105 
updateUi()106     private void updateUi() {
107         PreferenceGroup otherGroup = new PreferenceCategory(getContext());
108         otherGroup.setKey(KEY_OTHER);
109         otherGroup.setTitle(R.string.other_permissions);
110         getPreferenceScreen().addPreference(otherGroup);
111         ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting.
112         prefs.add(otherGroup);
113         String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
114         String filterGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
115         UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
116         otherGroup.removeAll();
117         PackageManager pm = getContext().getPackageManager();
118 
119         PackageInfo info = AutoPermissionsUtils.getPackageInfo(requireActivity(), pkg, userHandle);
120         if (info == null) {
121             return;
122         }
123 
124         ApplicationInfo appInfo = info.applicationInfo;
125         Preference header = AutoPermissionsUtils.createHeaderPreference(getContext(), appInfo);
126         header.setOrder(0);
127         getPreferenceScreen().addPreference(header);
128 
129         if (info.requestedPermissions != null) {
130             for (int i = 0; i < info.requestedPermissions.length; i++) {
131                 PermissionInfo perm;
132                 try {
133                     perm = pm.getPermissionInfo(info.requestedPermissions[i], /* flags= */ 0);
134                 } catch (PackageManager.NameNotFoundException e) {
135                     Log.e(LOG_TAG,
136                             "Can't get permission info for " + info.requestedPermissions[i], e);
137                     continue;
138                 }
139 
140                 if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
141                         || (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
142                     continue;
143                 }
144 
145                 if (appInfo.isInstantApp()
146                         && (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT)
147                         == 0) {
148                     continue;
149                 }
150                 if (appInfo.targetSdkVersion < Build.VERSION_CODES.M
151                         && (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
152                         != 0) {
153                     continue;
154                 }
155 
156                 if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
157                         == PermissionInfo.PROTECTION_DANGEROUS) {
158                     PackageItemInfo group = getGroup(Utils.getGroupOfPermission(perm), pm);
159                     if (group == null) {
160                         group = perm;
161                     }
162                     // If we show a targeted group, then ignore everything else.
163                     if (filterGroup != null && !group.name.equals(filterGroup)) {
164                         continue;
165                     }
166                     PreferenceGroup pref = findOrCreate(group, pm, prefs);
167                     pref.addPreference(getPreference(info, perm, group, pm));
168                 } else if (filterGroup == null) {
169                     if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
170                             == PermissionInfo.PROTECTION_NORMAL) {
171                         PermissionGroupInfo group = getGroup(perm.group, pm);
172                         otherGroup.addPreference(getPreference(info,
173                                 perm, group, pm));
174                     }
175                 }
176 
177                 // If we show a targeted group, then don't show 'other' permissions.
178                 if (filterGroup != null) {
179                     getPreferenceScreen().removePreference(otherGroup);
180                 }
181             }
182         }
183 
184         // Sort an ArrayList of the groups and then set the order from the sorting.
185         Collections.sort(prefs, (lhs, rhs) -> {
186             String lKey = lhs.getKey();
187             String rKey = rhs.getKey();
188             if (lKey.equals(KEY_OTHER)) {
189                 return 1;
190             } else if (rKey.equals(KEY_OTHER)) {
191                 return -1;
192             } else if (Utils.isModernPermissionGroup(lKey)
193                     != Utils.isModernPermissionGroup(rKey)) {
194                 return Utils.isModernPermissionGroup(lKey) ? -1 : 1;
195             }
196             return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
197         });
198         for (int i = 0; i < prefs.size(); i++) {
199             prefs.get(i).setOrder(i + 1);
200         }
201     }
202 
getGroup(String group, PackageManager pm)203     private PermissionGroupInfo getGroup(String group, PackageManager pm) {
204         try {
205             return pm.getPermissionGroupInfo(group, /* flags= */ 0);
206         } catch (PackageManager.NameNotFoundException e) {
207             return null;
208         }
209     }
210 
findOrCreate(PackageItemInfo group, PackageManager pm, ArrayList<Preference> prefs)211     private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm,
212             ArrayList<Preference> prefs) {
213         PreferenceGroup pref = findPreference(group.name);
214         if (pref == null) {
215             pref = new PreferenceCategory(getPreferenceManager().getContext());
216             pref.setKey(group.name);
217             pref.setTitle(group.loadLabel(pm));
218             prefs.add(pref);
219             getPreferenceScreen().addPreference(pref);
220         }
221         return pref;
222     }
223 
getPreference(PackageInfo packageInfo, PermissionInfo perm, PackageItemInfo group, PackageManager pm)224     private Preference getPreference(PackageInfo packageInfo, PermissionInfo perm,
225             PackageItemInfo group, PackageManager pm) {
226         final Preference pref;
227         Context context = getPreferenceManager().getContext();
228 
229         // We allow individual permission control for some permissions if review enabled
230         final boolean mutable = Utils.isPermissionIndividuallyControlled(getContext(), perm.name);
231         if (mutable) {
232             pref = new MyMultiTargetSwitchPreference(context, perm.name,
233                     getPermissionForegroundGroup(packageInfo, perm.name));
234         } else {
235             pref = new Preference(context);
236         }
237 
238         Drawable icon;
239         if (perm.icon != 0) {
240             icon = perm.loadUnbadgedIcon(pm);
241         } else if (group != null && group.icon != 0) {
242             icon = group.loadUnbadgedIcon(pm);
243         } else {
244             icon = context.getDrawable(
245                     com.android.permissioncontroller.R.drawable.ic_perm_device_info);
246         }
247         pref.setIcon(Utils.applyTint(context, icon, android.R.attr.colorControlNormal));
248         pref.setTitle(
249                 perm.loadSafeLabel(pm, /* ellipsizeDip= */ 20000, TextUtils.SAFE_STRING_FLAG_TRIM));
250         pref.setSingleLineTitle(false);
251         final CharSequence desc = perm.loadDescription(pm);
252 
253         pref.setOnPreferenceClickListener((Preference preference) -> {
254             new AlertDialog.Builder(getContext())
255                     .setMessage(desc)
256                     .setPositiveButton(android.R.string.ok, /* listener= */ null)
257                     .show();
258             return mutable;
259         });
260 
261         return pref;
262     }
263 
264     /**
265      * Return the (foreground-) {@link AppPermissionGroup group} a permission belongs to.
266      *
267      * <p>For foreground or non background-foreground permissions this returns the group
268      * {@link AppPermissionGroup} the permission is in. For background permisisons this returns
269      * the group the matching foreground
270      *
271      * @param packageInfo Package information about the app
272      * @param permission  The permission that belongs to a group
273      * @return the group the permissions belongs to
274      */
getPermissionForegroundGroup(PackageInfo packageInfo, String permission)275     private AppPermissionGroup getPermissionForegroundGroup(PackageInfo packageInfo,
276             String permission) {
277         AppPermissionGroup appPermissionGroup = null;
278         if (mGroups != null) {
279             final int groupCount = mGroups.size();
280             for (int i = 0; i < groupCount; i++) {
281                 AppPermissionGroup currentPermissionGroup = mGroups.get(i);
282                 if (currentPermissionGroup.hasPermission(permission)) {
283                     appPermissionGroup = currentPermissionGroup;
284                     break;
285                 }
286                 if (currentPermissionGroup.getBackgroundPermissions() != null
287                         && currentPermissionGroup.getBackgroundPermissions().hasPermission(
288                         permission)) {
289                     appPermissionGroup = currentPermissionGroup.getBackgroundPermissions();
290                     break;
291                 }
292             }
293         }
294         if (appPermissionGroup == null) {
295             appPermissionGroup = AppPermissionGroup.create(
296                     getContext(), packageInfo, permission, /* delayChanges= */ false);
297             if (mGroups == null) {
298                 mGroups = new ArrayList<>();
299             }
300             mGroups.add(appPermissionGroup);
301         }
302         return appPermissionGroup;
303     }
304 
305 
306     private static final class MyMultiTargetSwitchPreference extends SwitchPreference {
307         private View.OnClickListener mSwitchOnClickLister;
308 
MyMultiTargetSwitchPreference(Context context, String permission, AppPermissionGroup appPermissionGroup)309         MyMultiTargetSwitchPreference(Context context, String permission,
310                 AppPermissionGroup appPermissionGroup) {
311             super(context);
312 
313             setChecked(appPermissionGroup.areRuntimePermissionsGranted(
314                     new String[]{permission}));
315 
316             setSwitchOnClickListener(v -> {
317                 Switch switchView = (Switch) v;
318                 if (switchView.isChecked()) {
319                     appPermissionGroup.grantRuntimePermissions(false,
320                             new String[]{permission});
321                     // We are granting a permission from a group but since this is an
322                     // individual permission control other permissions in the group may
323                     // be revoked, hence we need to mark them user fixed to prevent the
324                     // app from requesting a non-granted permission and it being granted
325                     // because another permission in the group is granted. This applies
326                     // only to apps that support runtime permissions.
327                     if (appPermissionGroup.doesSupportRuntimePermissions()) {
328                         int grantedCount = 0;
329                         String[] revokedPermissionsToFix = null;
330                         final int permissionCount = appPermissionGroup.getPermissions().size();
331                         for (int i = 0; i < permissionCount; i++) {
332                             Permission current = appPermissionGroup.getPermissions().get(i);
333                             if (!current.isGrantedIncludingAppOp()) {
334                                 if (!current.isUserFixed()) {
335                                     revokedPermissionsToFix = ArrayUtils.appendString(
336                                             revokedPermissionsToFix, current.getName());
337                                 }
338                             } else {
339                                 grantedCount++;
340                             }
341                         }
342                         if (revokedPermissionsToFix != null) {
343                             // If some permissions were not granted then they should be fixed.
344                             appPermissionGroup.revokeRuntimePermissions(/* fixedByTheUser= */ true,
345                                     revokedPermissionsToFix);
346                         } else if (appPermissionGroup.getPermissions().size() == grantedCount) {
347                             // If all permissions are granted then they should not be fixed.
348                             appPermissionGroup.grantRuntimePermissions(/* fixedByTheUser= */ false);
349                         }
350                     }
351                 } else {
352                     appPermissionGroup.revokeRuntimePermissions(/* fixedByTheUser= */ true,
353                             new String[]{permission});
354                     // If we just revoked the last permission we need to clear
355                     // the user fixed state as now the app should be able to
356                     // request them at runtime if supported.
357                     if (appPermissionGroup.doesSupportRuntimePermissions()
358                             && !appPermissionGroup.areRuntimePermissionsGranted()) {
359                         appPermissionGroup.revokeRuntimePermissions(/* fixedByTheUser= */ false);
360                     }
361                 }
362             });
363         }
364 
365         @Override
setChecked(boolean checked)366         public void setChecked(boolean checked) {
367             // If double target behavior is enabled do nothing
368             if (mSwitchOnClickLister == null) {
369                 super.setChecked(checked);
370             }
371         }
372 
setSwitchOnClickListener(View.OnClickListener listener)373         void setSwitchOnClickListener(View.OnClickListener listener) {
374             mSwitchOnClickLister = listener;
375         }
376 
377         @Override
onBindViewHolder(PreferenceViewHolder holder)378         public void onBindViewHolder(PreferenceViewHolder holder) {
379             super.onBindViewHolder(holder);
380             Switch switchView = holder.itemView.findViewById(android.R.id.switch_widget);
381             if (switchView != null) {
382                 switchView.setOnClickListener(mSwitchOnClickLister);
383             }
384         }
385     }
386 }
387