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