• 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.permissioncontroller.permission.ui.television;
18 
19 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
20 
21 import android.app.ActionBar;
22 import android.app.Activity;
23 import android.app.Application;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.graphics.drawable.Drawable;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.text.BidiFormatter;
35 import android.util.ArraySet;
36 import android.util.Log;
37 import android.view.Menu;
38 import android.view.MenuInflater;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.widget.Toast;
42 
43 import androidx.lifecycle.ViewModelProvider;
44 import androidx.preference.Preference;
45 import androidx.preference.Preference.OnPreferenceClickListener;
46 import androidx.preference.PreferenceScreen;
47 import androidx.preference.PreferenceViewHolder;
48 import androidx.preference.SwitchPreference;
49 
50 import com.android.permissioncontroller.R;
51 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
52 import com.android.permissioncontroller.permission.model.AppPermissions;
53 import com.android.permissioncontroller.permission.model.livedatatypes.AutoRevokeState;
54 import com.android.permissioncontroller.permission.ui.ReviewPermissionsActivity;
55 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel;
56 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory;
57 import com.android.permissioncontroller.permission.ui.ReviewPermissionsActivity;
58 import com.android.permissioncontroller.permission.utils.KotlinUtils;
59 import com.android.permissioncontroller.permission.utils.LocationUtils;
60 import com.android.permissioncontroller.permission.utils.SafetyNetLogger;
61 import com.android.permissioncontroller.permission.utils.Utils;
62 
63 public final class AppPermissionsFragment extends SettingsWithHeader
64         implements OnPreferenceClickListener {
65 
66     private static final String LOG_TAG = "ManagePermsFragment";
67 
68     static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
69     private static final String AUTO_REVOKE_SWITCH_KEY = "_AUTO_REVOKE_SWITCH_KEY";
70 
71     private static final int MENU_ALL_PERMS = 0;
72 
73     private ArraySet<AppPermissionGroup> mToggledGroups;
74     private AppPermissionGroupsViewModel mViewModel;
75     private AppPermissions mAppPermissions;
76     private PreferenceScreen mExtraScreen;
77 
78     private boolean mHasConfirmedRevoke;
79 
newInstance(String packageName, UserHandle user)80     public static AppPermissionsFragment newInstance(String packageName, UserHandle user) {
81         return setPackage(new AppPermissionsFragment(), packageName, user);
82     }
83 
setPackage( T fragment, String packageName, UserHandle user)84     private static <T extends PermissionsFrameFragment> T setPackage(
85             T fragment, String packageName, UserHandle user) {
86         Bundle arguments = new Bundle();
87         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
88         arguments.putParcelable(Intent.EXTRA_USER, user);
89         fragment.setArguments(arguments);
90         return fragment;
91     }
92 
93     @Override
onCreate(Bundle savedInstanceState)94     public void onCreate(Bundle savedInstanceState) {
95         super.onCreate(savedInstanceState);
96         setLoading(true /* loading */, false /* animate */);
97         setHasOptionsMenu(true);
98         final ActionBar ab = getActivity().getActionBar();
99         if (ab != null) {
100             ab.setDisplayHomeAsUpEnabled(true);
101         }
102 
103         final String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
104         final UserHandle user = getArguments().getParcelable(Intent.EXTRA_USER);
105 
106         Activity activity = getActivity();
107         PackageInfo packageInfo = getPackageInfo(activity, packageName);
108         if (packageName == null) {
109             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
110             getActivity().finish();
111             return;
112         }
113 
114         mAppPermissions = new AppPermissions(activity, packageInfo, true,
115                 () -> getActivity().finish());
116 
117         if (mAppPermissions.isReviewRequired()) {
118             Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class);
119             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
120             intent.putExtra(Intent.EXTRA_USER, user);
121             startActivity(intent);
122             getActivity().finish();
123             return;
124         }
125     }
126 
127     @Override
onResume()128     public void onResume() {
129         super.onResume();
130         final String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
131         final UserHandle user = getArguments().getParcelable(Intent.EXTRA_USER);
132 
133         AppPermissionGroupsViewModelFactory factory =
134                 new AppPermissionGroupsViewModelFactory(packageName, user, 0);
135         mViewModel = new ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel.class);
136         mViewModel.getAutoRevokeLiveData().observe(this, this::setAutoRevokeToggleState);
137 
138         mAppPermissions.refresh();
139         loadPreferences();
140         setPreferencesCheckedState();
141     }
142 
143     @Override
onOptionsItemSelected(MenuItem item)144     public boolean onOptionsItemSelected(MenuItem item) {
145         switch (item.getItemId()) {
146             case android.R.id.home: {
147                 getActivity().finish();
148                 return true;
149             }
150 
151             case MENU_ALL_PERMS: {
152                 PermissionsFrameFragment frag =
153                         AllAppPermissionsFragment.newInstance(
154                                 getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
155                 getFragmentManager().beginTransaction()
156                         .replace(android.R.id.content, frag)
157                         .addToBackStack("AllPerms")
158                         .commit();
159                 return true;
160             }
161         }
162         return super.onOptionsItemSelected(item);
163     }
164 
165     @Override
onViewCreated(View view, Bundle savedInstanceState)166     public void onViewCreated(View view, Bundle savedInstanceState) {
167         super.onViewCreated(view, savedInstanceState);
168         if (mAppPermissions != null) {
169             bindUi(this,
170                 getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
171                 getArguments().getParcelable(Intent.EXTRA_USER),
172                 R.string.app_permissions_decor_title);
173         }
174     }
175 
176     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)177     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
178         super.onCreateOptionsMenu(menu, inflater);
179         menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
180     }
181 
bindUi(SettingsWithHeader fragment, String packageName, UserHandle user, int decorTitleStringResId)182     static void bindUi(SettingsWithHeader fragment, String packageName,
183             UserHandle user, int decorTitleStringResId) {
184         final Activity activity = fragment.getActivity();
185         final Application application = activity.getApplication();
186 
187         CharSequence label = BidiFormatter.getInstance().unicodeWrap(
188                 KotlinUtils.INSTANCE.getPackageLabel(application, packageName, user));
189         Drawable icon= KotlinUtils.INSTANCE.getBadgedPackageIcon(application, packageName, user);
190 
191         Intent infoIntent = null;
192         if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
193             infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
194                     .setData(Uri.fromParts("package", packageName, null));
195         }
196 
197         fragment.setHeader(icon, label, infoIntent, fragment.getString(
198                 R.string.additional_permissions_decor_title));
199     }
200 
loadPreferences()201     private void loadPreferences() {
202         Context context = getPreferenceManager().getContext();
203         if (context == null) {
204             return;
205         }
206 
207         PreferenceScreen screen = getPreferenceScreen();
208         screen.removeAll();
209         screen.addPreference(createHeaderLineTwoPreference(context));
210 
211         if (mExtraScreen != null) {
212             mExtraScreen.removeAll();
213             mExtraScreen = null;
214         }
215 
216         final Preference extraPerms = new Preference(context);
217         extraPerms.setIcon(R.drawable.ic_toc);
218         extraPerms.setTitle(R.string.additional_permissions);
219 
220         for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
221             if (!Utils.shouldShowPermission(getContext(), group)) {
222                 continue;
223             }
224 
225             boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
226 
227             Preference preference = new Preference(context);
228             preference.setOnPreferenceClickListener(this);
229             preference.setKey(group.getName());
230             Drawable icon = Utils.loadDrawable(context.getPackageManager(),
231                     group.getIconPkg(), group.getIconResId());
232             preference.setIcon(Utils.applyTint(getContext(), icon,
233                     android.R.attr.colorControlNormal));
234             preference.setTitle(group.getLabel());
235             if (group.isSystemFixed()) {
236                 preference.setSummary(getString(R.string.permission_summary_enabled_system_fixed));
237             } else if (group.isPolicyFixed()) {
238                 preference.setSummary(getString(R.string.permission_summary_enforced_by_policy));
239             }
240             preference.setPersistent(false);
241             preference.setEnabled(!group.isSystemFixed() && !group.isPolicyFixed());
242 
243             if (isPlatform) {
244                 screen.addPreference(preference);
245             } else {
246                 if (mExtraScreen == null) {
247                     mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
248                     mExtraScreen.addPreference(createHeaderLineTwoPreference(context));
249                 }
250                 mExtraScreen.addPreference(preference);
251             }
252         }
253 
254         final String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
255         final UserHandle user = getArguments().getParcelable(Intent.EXTRA_USER);
256 
257         if (mExtraScreen != null) {
258             extraPerms.setOnPreferenceClickListener(preference -> {
259                 AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
260                 setPackage(frag, packageName, user);
261                 frag.setTargetFragment(AppPermissionsFragment.this, 0);
262                 getFragmentManager().beginTransaction()
263                         .replace(android.R.id.content, frag)
264                         .addToBackStack(null)
265                         .commit();
266                 return true;
267             });
268             int count = mExtraScreen.getPreferenceCount() - 1;
269             extraPerms.setSummary(getResources().getQuantityString(
270                     R.plurals.additional_permissions_more, count, count));
271             screen.addPreference(extraPerms);
272         }
273 
274         addAutoRevokePreferences(getPreferenceScreen());
275 
276         setLoading(false /* loading */, true /* animate */);
277     }
278 
279     /**
280      * Creates a heading below decor_title and above the rest of the preferences. This heading
281      * displays the app name and banner icon. It's used in both system and additional permissions
282      * fragments for each app. The styling used is the same as a leanback preference with a
283      * customized background color
284      * @param context The context the preferences created on
285      * @return The preference header to be inserted as the first preference in the list.
286      */
createHeaderLineTwoPreference(Context context)287     private Preference createHeaderLineTwoPreference(Context context) {
288         Preference headerLineTwo = new Preference(context) {
289             @Override
290             public void onBindViewHolder(PreferenceViewHolder holder) {
291                 super.onBindViewHolder(holder);
292                 holder.itemView.setBackgroundColor(
293                         getResources().getColor(R.color.lb_header_banner_color));
294             }
295         };
296         headerLineTwo.setKey(HEADER_PREFERENCE_KEY);
297         headerLineTwo.setSelectable(false);
298         headerLineTwo.setTitle(mLabel);
299         headerLineTwo.setIcon(mIcon);
300         return headerLineTwo;
301     }
302 
303     @Override
onPreferenceClick(final Preference preference)304     public boolean onPreferenceClick(final Preference preference) {
305         String groupName = preference.getKey();
306         final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
307 
308         if (group == null) {
309             return false;
310         }
311 
312         addToggledGroup(group);
313 
314         if (LocationUtils.isLocationGroupAndProvider(getContext(), group.getName(),
315                 group.getApp().packageName)) {
316             LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
317             return false;
318         }
319 
320         AppPermissionFragment frag = new AppPermissionFragment();
321 
322         frag.setArguments(AppPermissionFragment.createArgs(
323                     /* packageName= */ group.getApp().packageName,
324                     /* permName= */ null,
325                     /* groupName= */ group.getName(),
326                     /* userHandle= */ group.getUser(),
327                     /* caller= */ null,
328                     /* sessionId= */ INVALID_SESSION_ID,
329                     /* grantCategory= */ null));
330         frag.setTargetFragment(AppPermissionsFragment.this, 0);
331         getFragmentManager().beginTransaction()
332                 .replace(android.R.id.content, frag)
333                 .addToBackStack(null)
334                 .commit();
335 
336         return true;
337     }
338 
339     @Override
onPause()340     public void onPause() {
341         mViewModel.getAutoRevokeLiveData().removeObservers(this);
342         super.onPause();
343         logToggledGroups();
344     }
345 
addToggledGroup(AppPermissionGroup group)346     private void addToggledGroup(AppPermissionGroup group) {
347         if (mToggledGroups == null) {
348             mToggledGroups = new ArraySet<>();
349         }
350         mToggledGroups.add(group);
351     }
352 
logToggledGroups()353     private void logToggledGroups() {
354         if (mToggledGroups != null) {
355             SafetyNetLogger.logPermissionsToggled(mToggledGroups);
356             mToggledGroups = null;
357         }
358     }
359 
setPreferencesCheckedState()360     private void setPreferencesCheckedState() {
361         setPreferencesCheckedState(getPreferenceScreen());
362         if (mExtraScreen != null) {
363             setPreferencesCheckedState(mExtraScreen);
364         }
365         setAutoRevokeToggleState(mViewModel.getAutoRevokeLiveData().getValue());
366     }
367 
setPreferencesCheckedState(PreferenceScreen screen)368     private void setPreferencesCheckedState(PreferenceScreen screen) {
369         int preferenceCount = screen.getPreferenceCount();
370         for (int i = 0; i < preferenceCount; i++) {
371             Preference preference = screen.getPreference(i);
372             if (preference.getKey() == null) {
373                 continue;
374             }
375             AppPermissionGroup group = mAppPermissions.getPermissionGroup(preference.getKey());
376             if (group == null) {
377                 continue;
378             }
379             AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
380 
381             if (group.areRuntimePermissionsGranted()) {
382                 if (backgroundGroup == null) {
383                     preference.setSummary(R.string.app_permission_button_allow);
384                 } else {
385                     if (backgroundGroup.areRuntimePermissionsGranted()) {
386                         preference.setSummary(R.string.permission_access_always);
387                     } else {
388                         preference.setSummary(R.string.permission_access_only_foreground);
389                     }
390                 }
391             } else {
392                 preference.setSummary(R.string.permission_access_never);
393             }
394         }
395     }
396 
397 
addAutoRevokePreferences(PreferenceScreen screen)398     private void addAutoRevokePreferences(PreferenceScreen screen) {
399         SwitchPreference autoRevokeSwitch =
400                 new SwitchPreference(screen.getPreferenceManager().getContext());
401         autoRevokeSwitch.setLayoutResource(R.layout.preference_permissions_revoke);
402         autoRevokeSwitch.setOnPreferenceClickListener((preference) -> {
403             mViewModel.setAutoRevoke(autoRevokeSwitch.isChecked());
404             android.util.Log.w(LOG_TAG, "setAutoRevoke " + autoRevokeSwitch.isChecked());
405             return true;
406         });
407         autoRevokeSwitch.setTitle(R.string.auto_revoke_label);
408         autoRevokeSwitch.setSummary(R.string.auto_revoke_summary);
409         autoRevokeSwitch.setKey(AUTO_REVOKE_SWITCH_KEY);
410         screen.addPreference(autoRevokeSwitch);
411     }
412 
setAutoRevokeToggleState(AutoRevokeState state)413     private void setAutoRevokeToggleState(AutoRevokeState state) {
414         SwitchPreference autoRevokeSwitch = getPreferenceScreen().findPreference(
415                 AUTO_REVOKE_SWITCH_KEY);
416         if (state == null || autoRevokeSwitch == null) {
417             return;
418         }
419         if (!state.isEnabledGlobal() || !state.getShouldShowSwitch()) {
420             autoRevokeSwitch.setVisible(false);
421             return;
422         }
423         autoRevokeSwitch.setVisible(true);
424         autoRevokeSwitch.setChecked(state.isEnabledForApp());
425     }
426 
getPackageInfo(Activity activity, String packageName)427     private static PackageInfo getPackageInfo(Activity activity, String packageName) {
428         try {
429             return activity.getPackageManager().getPackageInfo(
430                     packageName, PackageManager.GET_PERMISSIONS);
431         } catch (PackageManager.NameNotFoundException e) {
432             Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
433             return null;
434         }
435     }
436 
437     public static class AdditionalPermissionsFragment extends SettingsWithHeader {
438         AppPermissionsFragment mOuterFragment;
439 
440         @Override
onCreate(Bundle savedInstanceState)441         public void onCreate(Bundle savedInstanceState) {
442             mOuterFragment = (AppPermissionsFragment) getTargetFragment();
443             super.onCreate(savedInstanceState);
444             setHasOptionsMenu(true);
445         }
446 
447         @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)448         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
449             setPreferenceScreen(mOuterFragment.mExtraScreen);
450         }
451 
452         @Override
onViewCreated(View view, Bundle savedInstanceState)453         public void onViewCreated(View view, Bundle savedInstanceState) {
454             super.onViewCreated(view, savedInstanceState);
455             bindUi(this,
456                 getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
457                 getArguments().getParcelable(Intent.EXTRA_USER),
458                 R.string.additional_permissions_decor_title);
459         }
460 
461         @Override
onOptionsItemSelected(MenuItem item)462         public boolean onOptionsItemSelected(MenuItem item) {
463             switch (item.getItemId()) {
464             case android.R.id.home:
465                 getFragmentManager().popBackStack();
466                 return true;
467             }
468             return super.onOptionsItemSelected(item);
469         }
470     }
471 }
472