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