1 /* 2 * Copyright (C) 2010 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.app.Dialog; 20 import android.app.NotificationManager; 21 import android.app.settings.SettingsEnums; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.os.AsyncTask; 25 import android.os.Bundle; 26 import android.os.UserManager; 27 import android.provider.SearchIndexableResource; 28 import android.provider.Settings; 29 import android.service.notification.NotificationListenerService; 30 import android.widget.Toast; 31 32 import androidx.annotation.VisibleForTesting; 33 import androidx.appcompat.app.AlertDialog; 34 import androidx.fragment.app.Fragment; 35 36 import com.android.settings.R; 37 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 38 import com.android.settings.overlay.FeatureFactory; 39 import com.android.settings.search.BaseSearchIndexProvider; 40 import com.android.settings.search.Indexable; 41 import com.android.settings.utils.ManagedServiceSettings; 42 import com.android.settingslib.search.SearchIndexable; 43 44 import java.util.ArrayList; 45 import java.util.List; 46 47 /** 48 * Settings screen for managing notification listener permissions 49 */ 50 @SearchIndexable 51 public class NotificationAccessSettings extends ManagedServiceSettings { 52 private static final String TAG = "NotificationAccessSettings"; 53 private static final Config CONFIG = new Config.Builder() 54 .setTag(TAG) 55 .setSetting(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS) 56 .setIntentAction(NotificationListenerService.SERVICE_INTERFACE) 57 .setPermission(android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE) 58 .setNoun("notification listener") 59 .setWarningDialogTitle(R.string.notification_listener_security_warning_title) 60 .setWarningDialogSummary(R.string.notification_listener_security_warning_summary) 61 .setEmptyText(R.string.no_notification_listeners) 62 .build(); 63 64 private NotificationManager mNm; 65 66 @Override onCreate(Bundle icicle)67 public void onCreate(Bundle icicle) { 68 super.onCreate(icicle); 69 final Context ctx = getContext(); 70 if (UserManager.get(ctx).isManagedProfile()) { 71 // Apps in the work profile do not support notification listeners. 72 Toast.makeText(ctx, R.string.notification_settings_work_profile, Toast.LENGTH_SHORT) 73 .show(); 74 finish(); 75 } 76 } 77 78 @Override getMetricsCategory()79 public int getMetricsCategory() { 80 return SettingsEnums.NOTIFICATION_ACCESS; 81 } 82 83 @Override onAttach(Context context)84 public void onAttach(Context context) { 85 super.onAttach(context); 86 mNm = context.getSystemService(NotificationManager.class); 87 } 88 89 @Override getConfig()90 protected Config getConfig() { 91 return CONFIG; 92 } 93 94 @Override setEnabled(ComponentName service, String title, boolean enable)95 protected boolean setEnabled(ComponentName service, String title, boolean enable) { 96 logSpecialPermissionChange(enable, service.getPackageName()); 97 if (!enable) { 98 if (!isServiceEnabled(service)) { 99 return true; // already disabled 100 } 101 // show a friendly dialog 102 new FriendlyWarningDialogFragment() 103 .setServiceInfo(service, title, this) 104 .show(getFragmentManager(), "friendlydialog"); 105 return false; 106 } else { 107 if (isServiceEnabled(service)) { 108 return true; // already enabled 109 } 110 // show a scary dialog 111 new ScaryWarningDialogFragment() 112 .setServiceInfo(service, title, this) 113 .show(getFragmentManager(), "dialog"); 114 return false; 115 } 116 } 117 118 @Override isServiceEnabled(ComponentName cn)119 protected boolean isServiceEnabled(ComponentName cn) { 120 return mNm.isNotificationListenerAccessGranted(cn); 121 } 122 123 @Override enable(ComponentName service)124 protected void enable(ComponentName service) { 125 mNm.setNotificationListenerAccessGranted(service, true); 126 } 127 128 @Override getPreferenceScreenResId()129 protected int getPreferenceScreenResId() { 130 return R.xml.notification_access_settings; 131 } 132 133 @VisibleForTesting logSpecialPermissionChange(boolean enable, String packageName)134 void logSpecialPermissionChange(boolean enable, String packageName) { 135 int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW 136 : SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY; 137 FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(), 138 logCategory, packageName); 139 } 140 disable(final NotificationAccessSettings parent, final ComponentName cn)141 private static void disable(final NotificationAccessSettings parent, final ComponentName cn) { 142 parent.mNm.setNotificationListenerAccessGranted(cn, false); 143 AsyncTask.execute(() -> { 144 if (!parent.mNm.isNotificationPolicyAccessGrantedForPackage( 145 cn.getPackageName())) { 146 parent.mNm.removeAutomaticZenRules(cn.getPackageName()); 147 } 148 }); 149 } 150 151 public static class FriendlyWarningDialogFragment extends InstrumentedDialogFragment { 152 static final String KEY_COMPONENT = "c"; 153 static final String KEY_LABEL = "l"; 154 setServiceInfo(ComponentName cn, String label, Fragment target)155 public FriendlyWarningDialogFragment setServiceInfo(ComponentName cn, String label, 156 Fragment target) { 157 Bundle args = new Bundle(); 158 args.putString(KEY_COMPONENT, cn.flattenToString()); 159 args.putString(KEY_LABEL, label); 160 setArguments(args); 161 setTargetFragment(target, 0); 162 return this; 163 } 164 165 @Override getMetricsCategory()166 public int getMetricsCategory() { 167 return SettingsEnums.DIALOG_DISABLE_NOTIFICATION_ACCESS; 168 } 169 170 @Override onCreateDialog(Bundle savedInstanceState)171 public Dialog onCreateDialog(Bundle savedInstanceState) { 172 final Bundle args = getArguments(); 173 final String label = args.getString(KEY_LABEL); 174 final ComponentName cn = ComponentName.unflattenFromString(args 175 .getString(KEY_COMPONENT)); 176 NotificationAccessSettings parent = (NotificationAccessSettings) getTargetFragment(); 177 178 final String summary = getResources().getString( 179 R.string.notification_listener_disable_warning_summary, label); 180 return new AlertDialog.Builder(getContext()) 181 .setMessage(summary) 182 .setCancelable(true) 183 .setPositiveButton(R.string.notification_listener_disable_warning_confirm, 184 (dialog, id) -> disable(parent, cn)) 185 .setNegativeButton(R.string.notification_listener_disable_warning_cancel, 186 (dialog, id) -> { 187 // pass 188 }) 189 .create(); 190 } 191 } 192 193 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 194 new BaseSearchIndexProvider() { 195 @Override 196 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, 197 boolean enabled) { 198 final List<SearchIndexableResource> result = new ArrayList<>(); 199 200 final SearchIndexableResource sir = new SearchIndexableResource(context); 201 sir.xmlResId = R.xml.notification_access_settings; 202 result.add(sir); 203 return result; 204 } 205 }; 206 } 207