Home | History | Annotate | Download | only in notification
      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.settings.notification;
     18 
     19 import android.annotation.Nullable;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.DialogFragment;
     23 import android.app.NotificationManager;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.DialogInterface;
     27 import android.content.pm.ApplicationInfo;
     28 import android.content.pm.PackageItemInfo;
     29 import android.content.pm.PackageManager;
     30 import android.database.ContentObserver;
     31 import android.net.Uri;
     32 import android.os.AsyncTask;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.Looper;
     36 import android.provider.Settings;
     37 import android.provider.Settings.Secure;
     38 import android.support.v14.preference.SwitchPreference;
     39 import android.support.v7.preference.Preference;
     40 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
     41 import android.support.v7.preference.PreferenceScreen;
     42 import android.text.TextUtils;
     43 import android.util.ArraySet;
     44 import android.view.View;
     45 import android.widget.Toast;
     46 
     47 import com.android.internal.annotations.VisibleForTesting;
     48 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     49 import com.android.settings.R;
     50 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
     51 import com.android.settings.overlay.FeatureFactory;
     52 
     53 import java.util.ArrayList;
     54 import java.util.Collections;
     55 import java.util.List;
     56 
     57 public class ZenAccessSettings extends EmptyTextSettings {
     58 
     59     private final SettingObserver mObserver = new SettingObserver();
     60     private static final String ENABLED_SERVICES_SEPARATOR = ":";
     61 
     62     private Context mContext;
     63     private PackageManager mPkgMan;
     64     private NotificationManager mNoMan;
     65 
     66     @Override
     67     public int getMetricsCategory() {
     68         return MetricsEvent.NOTIFICATION_ZEN_MODE_ACCESS;
     69     }
     70 
     71     @Override
     72     public void onCreate(Bundle icicle) {
     73         super.onCreate(icicle);
     74 
     75         mContext = getActivity();
     76         mPkgMan = mContext.getPackageManager();
     77         mNoMan = mContext.getSystemService(NotificationManager.class);
     78         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext));
     79     }
     80 
     81     @Override
     82     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
     83         super.onViewCreated(view, savedInstanceState);
     84         setEmptyText(R.string.zen_access_empty_text);
     85     }
     86 
     87     @Override
     88     public void onResume() {
     89         super.onResume();
     90         reloadList();
     91         getContentResolver().registerContentObserver(
     92                 Secure.getUriFor(Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), false,
     93                 mObserver);
     94         getContentResolver().registerContentObserver(
     95                 Secure.getUriFor(Secure.ENABLED_NOTIFICATION_LISTENERS), false,
     96                 mObserver);
     97     }
     98 
     99     @Override
    100     public void onPause() {
    101         super.onPause();
    102         getContentResolver().unregisterContentObserver(mObserver);
    103     }
    104 
    105     private void reloadList() {
    106         final PreferenceScreen screen = getPreferenceScreen();
    107         screen.removeAll();
    108         final ArrayList<ApplicationInfo> apps = new ArrayList<>();
    109         final ArraySet<String> requesting = mNoMan.getPackagesRequestingNotificationPolicyAccess();
    110         if (!requesting.isEmpty()) {
    111             final List<ApplicationInfo> installed = mPkgMan.getInstalledApplications(0);
    112             if (installed != null) {
    113                 for (ApplicationInfo app : installed) {
    114                     if (requesting.contains(app.packageName)) {
    115                         apps.add(app);
    116                     }
    117                 }
    118             }
    119         }
    120         ArraySet<String> autoApproved = getEnabledNotificationListeners();
    121         requesting.addAll(autoApproved);
    122         Collections.sort(apps, new PackageItemInfo.DisplayNameComparator(mPkgMan));
    123         for (ApplicationInfo app : apps) {
    124             final String pkg = app.packageName;
    125             final CharSequence label = app.loadLabel(mPkgMan);
    126             final SwitchPreference pref = new SwitchPreference(getPrefContext());
    127             pref.setPersistent(false);
    128             pref.setIcon(app.loadIcon(mPkgMan));
    129             pref.setTitle(label);
    130             pref.setChecked(hasAccess(pkg));
    131             if (autoApproved.contains(pkg)) {
    132                 pref.setEnabled(false);
    133                 pref.setSummary(getString(R.string.zen_access_disabled_package_warning));
    134             }
    135             pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
    136                 @Override
    137                 public boolean onPreferenceChange(Preference preference, Object newValue) {
    138                     final boolean access = (Boolean) newValue;
    139                     if (access) {
    140                         new ScaryWarningDialogFragment()
    141                                 .setPkgInfo(pkg, label)
    142                                 .show(getFragmentManager(), "dialog");
    143                     } else {
    144                         new FriendlyWarningDialogFragment()
    145                                 .setPkgInfo(pkg, label)
    146                                 .show(getFragmentManager(), "dialog");
    147                     }
    148                     return false;
    149                 }
    150             });
    151             screen.addPreference(pref);
    152         }
    153     }
    154 
    155     private ArraySet<String> getEnabledNotificationListeners() {
    156         ArraySet<String> packages = new ArraySet<>();
    157         String settingValue = Settings.Secure.getString(getContext().getContentResolver(),
    158                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
    159         if (!TextUtils.isEmpty(settingValue)) {
    160             String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
    161             for (int i = 0; i < restored.length; i++) {
    162                 ComponentName value = ComponentName.unflattenFromString(restored[i]);
    163                 if (null != value) {
    164                     packages.add(value.getPackageName());
    165                 }
    166             }
    167         }
    168         return packages;
    169     }
    170 
    171     private boolean hasAccess(String pkg) {
    172         return mNoMan.isNotificationPolicyAccessGrantedForPackage(pkg);
    173     }
    174 
    175     private static void setAccess(final Context context, final String pkg, final boolean access) {
    176         logSpecialPermissionChange(access, pkg, context);
    177         AsyncTask.execute(new Runnable() {
    178             @Override
    179             public void run() {
    180                 final NotificationManager mgr = context.getSystemService(NotificationManager.class);
    181                 mgr.setNotificationPolicyAccessGranted(pkg, access);
    182             }
    183         });
    184     }
    185 
    186     @VisibleForTesting
    187     static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
    188         int logCategory = enable ? MetricsEvent.APP_SPECIAL_PERMISSION_DND_ALLOW
    189                 : MetricsEvent.APP_SPECIAL_PERMISSION_DND_DENY;
    190         FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
    191                 logCategory, packageName);
    192     }
    193 
    194 
    195     private static void deleteRules(final Context context, final String pkg) {
    196         AsyncTask.execute(new Runnable() {
    197             @Override
    198             public void run() {
    199                 final NotificationManager mgr = context.getSystemService(NotificationManager.class);
    200                 mgr.removeAutomaticZenRules(pkg);
    201             }
    202         });
    203     }
    204 
    205     private final class SettingObserver extends ContentObserver {
    206         public SettingObserver() {
    207             super(new Handler(Looper.getMainLooper()));
    208         }
    209 
    210         @Override
    211         public void onChange(boolean selfChange, Uri uri) {
    212             reloadList();
    213         }
    214     }
    215 
    216     /**
    217      * Warning dialog when allowing zen access warning about the privileges being granted.
    218      */
    219     public static class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
    220         static final String KEY_PKG = "p";
    221         static final String KEY_LABEL = "l";
    222 
    223         @Override
    224         public int getMetricsCategory() {
    225             return MetricsEvent.DIALOG_ZEN_ACCESS_GRANT;
    226         }
    227 
    228         public ScaryWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
    229             Bundle args = new Bundle();
    230             args.putString(KEY_PKG, pkg);
    231             args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
    232             setArguments(args);
    233             return this;
    234         }
    235 
    236         @Override
    237         public Dialog onCreateDialog(Bundle savedInstanceState) {
    238             super.onCreate(savedInstanceState);
    239             final Bundle args = getArguments();
    240             final String pkg = args.getString(KEY_PKG);
    241             final String label = args.getString(KEY_LABEL);
    242 
    243             final String title = getResources().getString(R.string.zen_access_warning_dialog_title,
    244                     label);
    245             final String summary = getResources()
    246                     .getString(R.string.zen_access_warning_dialog_summary);
    247             return new AlertDialog.Builder(getContext())
    248                     .setMessage(summary)
    249                     .setTitle(title)
    250                     .setCancelable(true)
    251                     .setPositiveButton(R.string.allow,
    252                             new DialogInterface.OnClickListener() {
    253                                 public void onClick(DialogInterface dialog, int id) {
    254                                     setAccess(getContext(), pkg, true);
    255                                 }
    256                             })
    257                     .setNegativeButton(R.string.deny,
    258                             new DialogInterface.OnClickListener() {
    259                                 public void onClick(DialogInterface dialog, int id) {
    260                                     // pass
    261                                 }
    262                             })
    263                     .create();
    264         }
    265     }
    266 
    267     /**
    268      * Warning dialog when revoking zen access warning that zen rule instances will be deleted.
    269      */
    270     public static class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
    271         static final String KEY_PKG = "p";
    272         static final String KEY_LABEL = "l";
    273 
    274 
    275         @Override
    276         public int getMetricsCategory() {
    277             return MetricsEvent.DIALOG_ZEN_ACCESS_REVOKE;
    278         }
    279 
    280         public FriendlyWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
    281             Bundle args = new Bundle();
    282             args.putString(KEY_PKG, pkg);
    283             args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
    284             setArguments(args);
    285             return this;
    286         }
    287 
    288         @Override
    289         public Dialog onCreateDialog(Bundle savedInstanceState) {
    290             super.onCreate(savedInstanceState);
    291             final Bundle args = getArguments();
    292             final String pkg = args.getString(KEY_PKG);
    293             final String label = args.getString(KEY_LABEL);
    294 
    295             final String title = getResources().getString(
    296                     R.string.zen_access_revoke_warning_dialog_title, label);
    297             final String summary = getResources()
    298                     .getString(R.string.zen_access_revoke_warning_dialog_summary);
    299             return new AlertDialog.Builder(getContext())
    300                     .setMessage(summary)
    301                     .setTitle(title)
    302                     .setCancelable(true)
    303                     .setPositiveButton(R.string.okay,
    304                             new DialogInterface.OnClickListener() {
    305                                 public void onClick(DialogInterface dialog, int id) {
    306                                     deleteRules(getContext(), pkg);
    307                                     setAccess(getContext(), pkg, false);
    308                                 }
    309                             })
    310                     .setNegativeButton(R.string.cancel,
    311                             new DialogInterface.OnClickListener() {
    312                                 public void onClick(DialogInterface dialog, int id) {
    313                                     // pass
    314                                 }
    315                             })
    316                     .create();
    317         }
    318     }
    319 }
    320