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