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