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