• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.packageinstaller.permission.ui.handheld;
18 
19 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
20 
21 import android.app.ActionBar;
22 import android.app.Activity;
23 import android.app.AlertDialog;
24 import android.app.Fragment;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageManager;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.preference.Preference;
35 import android.preference.Preference.OnPreferenceChangeListener;
36 import android.preference.Preference.OnPreferenceClickListener;
37 import android.preference.PreferenceScreen;
38 import android.preference.SwitchPreference;
39 import android.provider.Settings;
40 import android.util.IconDrawableFactory;
41 import android.util.Log;
42 import android.view.Menu;
43 import android.view.MenuInflater;
44 import android.view.MenuItem;
45 import android.view.View;
46 import android.widget.Switch;
47 import android.widget.Toast;
48 
49 import com.android.packageinstaller.R;
50 import com.android.packageinstaller.permission.model.AppPermissionGroup;
51 import com.android.packageinstaller.permission.model.AppPermissions;
52 import com.android.packageinstaller.permission.model.Permission;
53 import com.android.packageinstaller.permission.utils.LocationUtils;
54 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
55 import com.android.packageinstaller.permission.utils.Utils;
56 import com.android.settingslib.HelpUtils;
57 import com.android.settingslib.RestrictedLockUtils;
58 
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 public final class AppPermissionsFragment extends SettingsWithHeader
63         implements OnPreferenceChangeListener {
64 
65     private static final String LOG_TAG = "ManagePermsFragment";
66 
67     static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
68 
69     private static final int MENU_ALL_PERMS = 0;
70 
71     private List<AppPermissionGroup> mToggledGroups;
72     private AppPermissions mAppPermissions;
73     private PreferenceScreen mExtraScreen;
74 
75     private boolean mHasConfirmedRevoke;
76 
newInstance(String packageName)77     public static AppPermissionsFragment newInstance(String packageName) {
78         return setPackageName(new AppPermissionsFragment(), packageName);
79     }
80 
setPackageName(T fragment, String packageName)81     private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
82         Bundle arguments = new Bundle();
83         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
84         fragment.setArguments(arguments);
85         return fragment;
86     }
87 
88     @Override
onCreate(Bundle savedInstanceState)89     public void onCreate(Bundle savedInstanceState) {
90         super.onCreate(savedInstanceState);
91         setLoading(true /* loading */, false /* animate */);
92         setHasOptionsMenu(true);
93         final ActionBar ab = getActivity().getActionBar();
94         if (ab != null) {
95             ab.setDisplayHomeAsUpEnabled(true);
96         }
97 
98         String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
99         Activity activity = getActivity();
100         PackageInfo packageInfo = getPackageInfo(activity, packageName);
101         if (packageInfo == null) {
102             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
103             activity.finish();
104             return;
105         }
106 
107         mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() {
108             @Override
109             public void run() {
110                 getActivity().finish();
111             }
112         });
113         loadPreferences();
114     }
115 
116     @Override
onResume()117     public void onResume() {
118         super.onResume();
119         mAppPermissions.refresh();
120         loadPreferences();
121         setPreferencesCheckedState();
122     }
123 
124     @Override
onOptionsItemSelected(MenuItem item)125     public boolean onOptionsItemSelected(MenuItem item) {
126         switch (item.getItemId()) {
127             case android.R.id.home: {
128                 getActivity().finish();
129                 return true;
130             }
131 
132             case MENU_ALL_PERMS: {
133                 showAllPermissions(null);
134                 return true;
135             }
136         }
137         return super.onOptionsItemSelected(item);
138     }
139 
140     @Override
onViewCreated(View view, Bundle savedInstanceState)141     public void onViewCreated(View view, Bundle savedInstanceState) {
142         super.onViewCreated(view, savedInstanceState);
143         if (mAppPermissions != null) {
144             bindUi(this, mAppPermissions.getPackageInfo());
145         }
146     }
147 
148     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)149     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
150         super.onCreateOptionsMenu(menu, inflater);
151         menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
152         HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
153                 getClass().getName());
154     }
155 
showAllPermissions(String filterGroup)156     private void showAllPermissions(String filterGroup) {
157         Fragment frag = AllAppPermissionsFragment.newInstance(
158                 getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
159                 filterGroup);
160         getFragmentManager().beginTransaction()
161                 .replace(android.R.id.content, frag)
162                 .addToBackStack("AllPerms")
163                 .commit();
164     }
165 
bindUi(SettingsWithHeader fragment, PackageInfo packageInfo)166     private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
167         Activity activity = fragment.getActivity();
168         PackageManager pm = activity.getPackageManager();
169         ApplicationInfo appInfo = packageInfo.applicationInfo;
170         Intent infoIntent = null;
171         if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
172             infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
173                     .setData(Uri.fromParts("package", packageInfo.packageName, null));
174         }
175 
176         Drawable icon = IconDrawableFactory.newInstance(activity).getBadgedIcon(appInfo);
177         CharSequence label = appInfo.loadLabel(pm);
178         fragment.setHeader(icon, label, infoIntent);
179 
180         ActionBar ab = activity.getActionBar();
181         if (ab != null) {
182             ab.setTitle(R.string.app_permissions);
183         }
184     }
185 
loadPreferences()186     private void loadPreferences() {
187         Context context = getActivity();
188         if (context == null) {
189             return;
190         }
191 
192         PreferenceScreen screen = getPreferenceScreen();
193         if (screen == null) {
194             screen = getPreferenceManager().createPreferenceScreen(getActivity());
195             setPreferenceScreen(screen);
196         }
197 
198         screen.removeAll();
199 
200         if (mExtraScreen != null) {
201             mExtraScreen.removeAll();
202         }
203 
204         final Preference extraPerms = new Preference(context);
205         extraPerms.setIcon(R.drawable.ic_toc);
206         extraPerms.setTitle(R.string.additional_permissions);
207 
208         for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
209             if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) {
210                 continue;
211             }
212 
213             boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
214 
215             RestrictedSwitchPreference preference = new RestrictedSwitchPreference(context);
216             preference.setChecked(group.areRuntimePermissionsGranted());
217 
218             // Some groups may be a double target - one to toggle and one to fine manage
219             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
220                 preference.setOnPreferenceClickListener((pref) -> {
221                     showAllPermissions(group.getName());
222                     return false;
223                 });
224 
225                 preference.setSwitchOnClickListener(v -> {
226                     Switch switchView = (Switch) v;
227                     onPreferenceChange(preference, switchView.isChecked());
228                     updateSummaryForIndividuallyControlledPermissionGroup(
229                             group, preference);
230                     preference.setCheckedOverride(switchView.isChecked());
231                 });
232 
233                 updateSummaryForIndividuallyControlledPermissionGroup(group, preference);
234             } else {
235                 preference.setOnPreferenceChangeListener(this);
236             }
237 
238             preference.setKey(group.getName());
239             Drawable icon = Utils.loadDrawable(context.getPackageManager(),
240                     group.getIconPkg(), group.getIconResId());
241             preference.setIcon(Utils.applyTint(getContext(), icon,
242                     android.R.attr.colorControlNormal));
243             preference.setTitle(group.getLabel());
244 
245 
246             if (group.isPolicyFixed()) {
247                 EnforcedAdmin admin = RestrictedLockUtils.getProfileOrDeviceOwner(getContext(),
248                         group.getUserId());
249                 if (admin != null) {
250                     preference.setDisabledByAdmin(admin);
251                     preference.setSummary(R.string.disabled_by_admin_summary_text);
252                 } else {
253                     preference.setSummary(R.string.permission_summary_enforced_by_policy);
254                     preference.setEnabled(false);
255                 }
256             }
257             preference.setPersistent(false);
258 
259             if (isPlatform) {
260                 screen.addPreference(preference);
261             } else {
262                 if (mExtraScreen == null) {
263                     mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
264                 }
265                 mExtraScreen.addPreference(preference);
266             }
267         }
268 
269         if (mExtraScreen != null) {
270             extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() {
271                 @Override
272                 public boolean onPreferenceClick(Preference preference) {
273                     AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
274                     setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
275                     frag.setTargetFragment(AppPermissionsFragment.this, 0);
276                     getFragmentManager().beginTransaction()
277                             .replace(android.R.id.content, frag)
278                             .addToBackStack(null)
279                             .commit();
280                     return true;
281                 }
282             });
283             int count = mExtraScreen.getPreferenceCount();
284             extraPerms.setSummary(getResources().getQuantityString(
285                     R.plurals.additional_permissions_more, count, count));
286             screen.addPreference(extraPerms);
287         }
288 
289         setLoading(false /* loading */, true /* animate */);
290     }
291 
292     @Override
onPreferenceChange(final Preference preference, Object newValue)293     public boolean onPreferenceChange(final Preference preference, Object newValue) {
294         String groupName = preference.getKey();
295         final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
296 
297         if (group == null) {
298             return false;
299         }
300 
301         addToggledGroup(group);
302 
303         if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
304             LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
305             return false;
306         }
307         if (newValue == Boolean.TRUE) {
308             group.grantRuntimePermissions(false);
309         } else {
310             final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
311             if (grantedByDefault || (!group.doesSupportRuntimePermissions()
312                     && !mHasConfirmedRevoke)) {
313                 new AlertDialog.Builder(getContext())
314                         .setMessage(grantedByDefault ? R.string.system_warning
315                                 : R.string.old_sdk_deny_warning)
316                         .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
317                             if (preference instanceof MultiTargetSwitchPreference) {
318                                 ((MultiTargetSwitchPreference) preference).setCheckedOverride(true);
319                             }
320                         })
321                         .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
322                                 (DialogInterface dialog, int which) -> {
323                             ((SwitchPreference) preference).setChecked(false);
324                             group.revokeRuntimePermissions(false);
325                             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(),
326                                     group.getName())) {
327                                 updateSummaryForIndividuallyControlledPermissionGroup(
328                                         group, preference);
329                             }
330                             if (!grantedByDefault) {
331                                 mHasConfirmedRevoke = true;
332                             }
333                         })
334                         .show();
335                 return false;
336             } else {
337                 group.revokeRuntimePermissions(false);
338             }
339         }
340 
341         return true;
342     }
343 
344     @Override
onPause()345     public void onPause() {
346         super.onPause();
347         logToggledGroups();
348     }
349 
updateSummaryForIndividuallyControlledPermissionGroup( AppPermissionGroup group, Preference preference)350     private void updateSummaryForIndividuallyControlledPermissionGroup(
351             AppPermissionGroup group, Preference preference) {
352         int revokedCount = 0;
353         List<Permission> permissions = group.getPermissions();
354         final int permissionCount = permissions.size();
355         for (int i = 0; i < permissionCount; i++) {
356             Permission permission = permissions.get(i);
357             if (group.doesSupportRuntimePermissions()
358                     ? !permission.isGranted() : !permission.isAppOpAllowed()) {
359                 revokedCount++;
360             }
361         }
362 
363         final int resId;
364         if (revokedCount == 0) {
365             resId = R.string.permission_revoked_none;
366         } else if (revokedCount == permissionCount) {
367             resId = R.string.permission_revoked_all;
368         } else {
369             resId = R.string.permission_revoked_count;
370         }
371 
372         String summary = getString(resId, revokedCount);
373         preference.setSummary(summary);
374     }
375 
addToggledGroup(AppPermissionGroup group)376     private void addToggledGroup(AppPermissionGroup group) {
377         if (mToggledGroups == null) {
378             mToggledGroups = new ArrayList<>();
379         }
380         // Double toggle is back to initial state.
381         if (mToggledGroups.contains(group)) {
382             mToggledGroups.remove(group);
383         } else {
384             mToggledGroups.add(group);
385         }
386     }
387 
logToggledGroups()388     private void logToggledGroups() {
389         if (mToggledGroups != null) {
390             String packageName = mAppPermissions.getPackageInfo().packageName;
391             SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
392             mToggledGroups = null;
393         }
394     }
395 
setPreferencesCheckedState()396     private void setPreferencesCheckedState() {
397         setPreferencesCheckedState(getPreferenceScreen());
398         if (mExtraScreen != null) {
399             setPreferencesCheckedState(mExtraScreen);
400         }
401     }
402 
setPreferencesCheckedState(PreferenceScreen screen)403     private void setPreferencesCheckedState(PreferenceScreen screen) {
404         int preferenceCount = screen.getPreferenceCount();
405         for (int i = 0; i < preferenceCount; i++) {
406             Preference preference = screen.getPreference(i);
407             if (preference instanceof SwitchPreference) {
408                 SwitchPreference switchPref = (SwitchPreference) preference;
409                 AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey());
410                 if (group != null) {
411                     switchPref.setChecked(group.areRuntimePermissionsGranted());
412                 }
413             }
414         }
415     }
416 
getPackageInfo(Activity activity, String packageName)417     private static PackageInfo getPackageInfo(Activity activity, String packageName) {
418         try {
419             return activity.getPackageManager().getPackageInfo(
420                     packageName, PackageManager.GET_PERMISSIONS);
421         } catch (PackageManager.NameNotFoundException e) {
422             Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
423             return null;
424         }
425     }
426 
427     public static class AdditionalPermissionsFragment extends SettingsWithHeader {
428         AppPermissionsFragment mOuterFragment;
429 
430         @Override
onCreate(Bundle savedInstanceState)431         public void onCreate(Bundle savedInstanceState) {
432             mOuterFragment = (AppPermissionsFragment) getTargetFragment();
433             super.onCreate(savedInstanceState);
434             setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent);
435             setHasOptionsMenu(true);
436             setPreferenceScreen(mOuterFragment.mExtraScreen);
437         }
438 
439         @Override
onViewCreated(View view, Bundle savedInstanceState)440         public void onViewCreated(View view, Bundle savedInstanceState) {
441             super.onViewCreated(view, savedInstanceState);
442             String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
443             bindUi(this, getPackageInfo(getActivity(), packageName));
444         }
445 
446         @Override
onOptionsItemSelected(MenuItem item)447         public boolean onOptionsItemSelected(MenuItem item) {
448             switch (item.getItemId()) {
449             case android.R.id.home:
450                 getFragmentManager().popBackStack();
451                 return true;
452             }
453             return super.onOptionsItemSelected(item);
454         }
455     }
456 }
457