Home | History | Annotate | Download | only in ui
      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 package com.android.packageinstaller.permission.ui;
     17 
     18 import android.app.ActionBar;
     19 import android.app.AlertDialog;
     20 import android.app.Fragment;
     21 import android.content.Context;
     22 import android.content.DialogInterface;
     23 import android.content.DialogInterface.OnClickListener;
     24 import android.content.Intent;
     25 import android.graphics.drawable.Drawable;
     26 import android.os.Bundle;
     27 import android.support.v14.preference.SwitchPreference;
     28 import android.support.v4.util.ArrayMap;
     29 import android.support.v7.preference.Preference;
     30 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
     31 import android.support.v7.preference.Preference.OnPreferenceClickListener;
     32 import android.support.v7.preference.PreferenceScreen;
     33 import android.util.ArraySet;
     34 import android.view.Menu;
     35 import android.view.MenuInflater;
     36 import android.view.MenuItem;
     37 import android.view.View;
     38 import android.view.ViewGroup;
     39 import android.widget.ImageView;
     40 import android.widget.TextView;
     41 
     42 import com.android.packageinstaller.R;
     43 import com.android.packageinstaller.permission.model.AppPermissionGroup;
     44 import com.android.packageinstaller.permission.model.PermissionApps;
     45 import com.android.packageinstaller.permission.model.PermissionApps.Callback;
     46 import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
     47 import com.android.packageinstaller.permission.utils.LocationUtils;
     48 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
     49 import com.android.packageinstaller.permission.utils.Utils;
     50 
     51 import java.util.ArrayList;
     52 import java.util.List;
     53 
     54 public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback,
     55         OnPreferenceChangeListener {
     56 
     57     private static final int MENU_SHOW_SYSTEM = Menu.FIRST;
     58     private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 1;
     59     private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem";
     60 
     61     public static PermissionAppsFragment newInstance(String permissionName) {
     62         return setPermissionName(new PermissionAppsFragment(), permissionName);
     63     }
     64 
     65     private static <T extends Fragment> T setPermissionName(T fragment, String permissionName) {
     66         Bundle arguments = new Bundle();
     67         arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName);
     68         fragment.setArguments(arguments);
     69         return fragment;
     70     }
     71 
     72     private PermissionApps mPermissionApps;
     73 
     74     private PreferenceScreen mExtraScreen;
     75 
     76     private ArrayMap<String, AppPermissionGroup> mToggledGroups;
     77     private ArraySet<String> mLauncherPkgs;
     78     private boolean mHasConfirmedRevoke;
     79 
     80     private boolean mShowSystem;
     81     private MenuItem mShowSystemMenu;
     82     private MenuItem mHideSystemMenu;
     83 
     84     private Callback mOnPermissionsLoadedListener;
     85 
     86     @Override
     87     public void onCreate(Bundle savedInstanceState) {
     88         super.onCreate(savedInstanceState);
     89         setLoading(true /* loading */, false /* animate */);
     90         setHasOptionsMenu(true);
     91         final ActionBar ab = getActivity().getActionBar();
     92         if (ab != null) {
     93             ab.setDisplayHomeAsUpEnabled(true);
     94         }
     95         mLauncherPkgs = Utils.getLauncherPackages(getContext());
     96 
     97         String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
     98         mPermissionApps = new PermissionApps(getActivity(), groupName, this);
     99         mPermissionApps.refresh(true);
    100     }
    101 
    102     @Override
    103     public void onResume() {
    104         super.onResume();
    105         mPermissionApps.refresh(true);
    106     }
    107 
    108     @Override
    109     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    110         mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
    111                 R.string.menu_show_system);
    112         mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
    113                 R.string.menu_hide_system);
    114         updateMenu();
    115     }
    116 
    117     @Override
    118     public boolean onOptionsItemSelected(MenuItem item) {
    119         switch (item.getItemId()) {
    120             case android.R.id.home:
    121                 getActivity().finish();
    122                 return true;
    123             case MENU_SHOW_SYSTEM:
    124             case MENU_HIDE_SYSTEM:
    125                 mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM;
    126                 if (mPermissionApps.getApps() != null) {
    127                     onPermissionsLoaded(mPermissionApps);
    128                 }
    129                 updateMenu();
    130                 break;
    131         }
    132         return super.onOptionsItemSelected(item);
    133     }
    134 
    135     private void updateMenu() {
    136         mShowSystemMenu.setVisible(!mShowSystem);
    137         mHideSystemMenu.setVisible(mShowSystem);
    138     }
    139 
    140     @Override
    141     protected void onSetEmptyText(TextView textView) {
    142         textView.setText(R.string.no_apps);
    143     }
    144 
    145     @Override
    146     public void onViewCreated(View view, Bundle savedInstanceState) {
    147         super.onViewCreated(view, savedInstanceState);
    148         bindUi(this, mPermissionApps);
    149     }
    150 
    151     private static void bindUi(Fragment fragment, PermissionApps permissionApps) {
    152         final Drawable icon = permissionApps.getIcon();
    153         final CharSequence label = permissionApps.getLabel();
    154         final ActionBar ab = fragment.getActivity().getActionBar();
    155         if (ab != null) {
    156             ab.setTitle(fragment.getString(R.string.permission_title, label));
    157         }
    158 
    159         final ViewGroup rootView = (ViewGroup) fragment.getView();
    160         final ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
    161         if (iconView != null) {
    162             // Set the icon as the background instead of the image because ImageView
    163             // doesn't properly scale vector drawables beyond their intrinsic size
    164             iconView.setBackground(icon);
    165         }
    166         final TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
    167         if (titleView != null) {
    168             titleView.setText(label);
    169         }
    170         final TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
    171         if (breadcrumbView != null) {
    172             breadcrumbView.setText(R.string.app_permissions);
    173         }
    174     }
    175 
    176     private void setOnPermissionsLoadedListener(Callback callback) {
    177         mOnPermissionsLoadedListener = callback;
    178     }
    179 
    180     @Override
    181     public void onPermissionsLoaded(PermissionApps permissionApps) {
    182         Context context = getPreferenceManager().getContext();
    183 
    184         if (context == null) {
    185             return;
    186         }
    187 
    188         boolean isTelevision = Utils.isTelevision(context);
    189         PreferenceScreen screen = getPreferenceScreen();
    190 
    191         ArraySet<String> preferencesToRemove = new ArraySet<>();
    192         for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
    193             preferencesToRemove.add(screen.getPreference(i).getKey());
    194         }
    195         if (mExtraScreen != null) {
    196             for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
    197                 preferencesToRemove.add(mExtraScreen.getPreference(i).getKey());
    198             }
    199         }
    200 
    201         for (PermissionApp app : permissionApps.getApps()) {
    202             if (!Utils.shouldShowPermission(app)) {
    203                 continue;
    204             }
    205 
    206             String key = app.getKey();
    207             preferencesToRemove.remove(key);
    208             Preference existingPref = screen.findPreference(key);
    209             if (existingPref == null && mExtraScreen != null) {
    210                 existingPref = mExtraScreen.findPreference(key);
    211             }
    212 
    213             boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs);
    214             if (isSystemApp && !isTelevision && !mShowSystem) {
    215                 if (existingPref != null) {
    216                     screen.removePreference(existingPref);
    217                 }
    218                 continue;
    219             }
    220 
    221             if (existingPref != null) {
    222                 // If existing preference - only update its state.
    223                 if (app.isPolicyFixed()) {
    224                     existingPref.setSummary(getString(
    225                             R.string.permission_summary_enforced_by_policy));
    226                 }
    227                 existingPref.setPersistent(false);
    228                 existingPref.setEnabled(!app.isPolicyFixed());
    229                 if (existingPref instanceof SwitchPreference) {
    230                     ((SwitchPreference) existingPref)
    231                             .setChecked(app.areRuntimePermissionsGranted());
    232                 }
    233                 continue;
    234             }
    235 
    236             SwitchPreference pref = new SwitchPreference(context);
    237             pref.setOnPreferenceChangeListener(this);
    238             pref.setKey(app.getKey());
    239             pref.setIcon(app.getIcon());
    240             pref.setTitle(app.getLabel());
    241             if (app.isPolicyFixed()) {
    242                 pref.setSummary(getString(R.string.permission_summary_enforced_by_policy));
    243             }
    244             pref.setPersistent(false);
    245             pref.setEnabled(!app.isPolicyFixed());
    246             pref.setChecked(app.areRuntimePermissionsGranted());
    247 
    248             if (isSystemApp && isTelevision) {
    249                 if (mExtraScreen == null) {
    250                     mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
    251                 }
    252                 mExtraScreen.addPreference(pref);
    253             } else {
    254                 screen.addPreference(pref);
    255             }
    256         }
    257 
    258         if (mExtraScreen != null) {
    259             preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS);
    260             Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS);
    261 
    262             if (pref == null) {
    263                 pref = new Preference(context);
    264                 pref.setKey(KEY_SHOW_SYSTEM_PREFS);
    265                 pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc,
    266                         android.R.attr.colorControlNormal));
    267                 pref.setTitle(R.string.preference_show_system_apps);
    268                 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
    269                     @Override
    270                     public boolean onPreferenceClick(Preference preference) {
    271                         SystemAppsFragment frag = new SystemAppsFragment();
    272                         setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME));
    273                         frag.setTargetFragment(PermissionAppsFragment.this, 0);
    274                         getFragmentManager().beginTransaction()
    275                             .replace(android.R.id.content, frag)
    276                             .addToBackStack("SystemApps")
    277                             .commit();
    278                         return true;
    279                     }
    280                 });
    281                 screen.addPreference(pref);
    282             }
    283 
    284             int grantedCount = 0;
    285             for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
    286                 if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) {
    287                     grantedCount++;
    288                 }
    289             }
    290             pref.setSummary(getString(R.string.app_permissions_group_summary,
    291                     grantedCount, mExtraScreen.getPreferenceCount()));
    292         }
    293 
    294         for (String key : preferencesToRemove) {
    295             Preference pref = screen.findPreference(key);
    296             if (pref != null) {
    297                 screen.removePreference(pref);
    298             } else if (mExtraScreen != null) {
    299                 pref = mExtraScreen.findPreference(key);
    300                 if (pref != null) {
    301                     mExtraScreen.removePreference(pref);
    302                 }
    303             }
    304         }
    305 
    306         setLoading(false /* loading */, true /* animate */);
    307 
    308         if (mOnPermissionsLoadedListener != null) {
    309             mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps);
    310         }
    311     }
    312 
    313     @Override
    314     public boolean onPreferenceChange(final Preference preference, Object newValue) {
    315         String pkg = preference.getKey();
    316         final PermissionApp app = mPermissionApps.getApp(pkg);
    317 
    318         if (app == null) {
    319             return false;
    320         }
    321 
    322         OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
    323         if (activity.isObscuredTouch()) {
    324             activity.showOverlayDialog();
    325             return false;
    326         }
    327 
    328         addToggledGroup(app.getPackageName(), app.getPermissionGroup());
    329 
    330         if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
    331                 app.getPackageName())) {
    332             LocationUtils.showLocationDialog(getContext(), app.getLabel());
    333             return false;
    334         }
    335         if (newValue == Boolean.TRUE) {
    336             app.grantRuntimePermissions();
    337         } else {
    338             final boolean grantedByDefault = app.hasGrantedByDefaultPermissions();
    339             if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) {
    340                 new AlertDialog.Builder(getContext())
    341                         .setMessage(grantedByDefault ? R.string.system_warning
    342                                 : R.string.old_sdk_deny_warning)
    343                         .setNegativeButton(R.string.cancel, null)
    344                         .setPositiveButton(R.string.grant_dialog_button_deny,
    345                                 new OnClickListener() {
    346                             @Override
    347                             public void onClick(DialogInterface dialog, int which) {
    348                                 ((SwitchPreference) preference).setChecked(false);
    349                                 app.revokeRuntimePermissions();
    350                                 if (!grantedByDefault) {
    351                                     mHasConfirmedRevoke = true;
    352                                 }
    353                             }
    354                         })
    355                         .show();
    356                 return false;
    357             } else {
    358                 app.revokeRuntimePermissions();
    359             }
    360         }
    361         return true;
    362     }
    363 
    364     @Override
    365     public void onPause() {
    366         super.onPause();
    367         logToggledGroups();
    368     }
    369 
    370     private void addToggledGroup(String packageName, AppPermissionGroup group) {
    371         if (mToggledGroups == null) {
    372             mToggledGroups = new ArrayMap<>();
    373         }
    374         // Double toggle is back to initial state.
    375         if (mToggledGroups.containsKey(packageName)) {
    376             mToggledGroups.remove(packageName);
    377         } else {
    378             mToggledGroups.put(packageName, group);
    379         }
    380     }
    381 
    382     private void logToggledGroups() {
    383         if (mToggledGroups != null) {
    384             final int groupCount = mToggledGroups.size();
    385             for (int i = 0; i < groupCount; i++) {
    386                 String packageName = mToggledGroups.keyAt(i);
    387                 List<AppPermissionGroup> groups = new ArrayList<>();
    388                 groups.add(mToggledGroups.valueAt(i));
    389                 SafetyNetLogger.logPermissionsToggled(packageName, groups);
    390             }
    391             mToggledGroups = null;
    392         }
    393     }
    394 
    395     public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback {
    396         PermissionAppsFragment mOuterFragment;
    397 
    398         @Override
    399         public void onCreate(Bundle savedInstanceState) {
    400             mOuterFragment = (PermissionAppsFragment) getTargetFragment();
    401             setLoading(true /* loading */, false /* animate */);
    402             super.onCreate(savedInstanceState);
    403         }
    404 
    405         @Override
    406         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
    407             if (mOuterFragment.mExtraScreen != null) {
    408                 setPreferenceScreen();
    409             } else {
    410                 mOuterFragment.setOnPermissionsLoadedListener(this);
    411             }
    412         }
    413 
    414         @Override
    415         public void onViewCreated(View view, Bundle savedInstanceState) {
    416             super.onViewCreated(view, savedInstanceState);
    417             String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
    418             PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null);
    419             bindUi(this, permissionApps);
    420         }
    421 
    422         @Override
    423         public void onPermissionsLoaded(PermissionApps permissionApps) {
    424             setPreferenceScreen();
    425             mOuterFragment.setOnPermissionsLoadedListener(null);
    426         }
    427 
    428         private void setPreferenceScreen() {
    429             setPreferenceScreen(mOuterFragment.mExtraScreen);
    430             setLoading(false /* loading */, true /* animate */);
    431         }
    432     }
    433 }
    434