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 static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS; 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED; 21 22 import android.annotation.Nullable; 23 import android.app.NotificationManager; 24 import android.app.admin.DevicePolicyManager; 25 import android.app.settings.SettingsEnums; 26 import android.companion.ICompanionDeviceManager; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.pm.PackageItemInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ServiceInfo; 32 import android.os.Bundle; 33 import android.os.ServiceManager; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.provider.Settings; 37 import android.service.notification.NotificationListenerService; 38 import android.util.IconDrawableFactory; 39 import android.util.Log; 40 import android.view.View; 41 import android.widget.Toast; 42 43 import androidx.preference.PreferenceCategory; 44 import androidx.preference.PreferenceScreen; 45 46 import com.android.settings.R; 47 import com.android.settings.Utils; 48 import com.android.settings.applications.AppInfoBase; 49 import com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessDetails; 50 import com.android.settings.core.SubSettingLauncher; 51 import com.android.settings.search.BaseSearchIndexProvider; 52 import com.android.settings.utils.ManagedServiceSettings; 53 import com.android.settings.widget.EmptyTextSettings; 54 import com.android.settingslib.applications.ServiceListing; 55 import com.android.settingslib.search.SearchIndexable; 56 import com.android.settingslib.widget.AppPreference; 57 58 import java.util.List; 59 60 /** 61 * Settings screen for managing notification listener permissions 62 */ 63 @SearchIndexable 64 public class NotificationAccessSettings extends EmptyTextSettings { 65 private static final String TAG = "NotifAccessSettings"; 66 private static final String ALLOWED_KEY = "allowed"; 67 private static final String NOT_ALLOWED_KEY = "not_allowed"; 68 private static final int MAX_CN_LENGTH = 500; 69 70 private static final ManagedServiceSettings.Config CONFIG = 71 new ManagedServiceSettings.Config.Builder() 72 .setTag(TAG) 73 .setSetting(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS) 74 .setIntentAction(NotificationListenerService.SERVICE_INTERFACE) 75 .setPermission(android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE) 76 .setNoun("notification listener") 77 .setWarningDialogTitle(R.string.notification_listener_security_warning_title) 78 .setWarningDialogSummary( 79 R.string.notification_listener_security_warning_summary) 80 .setEmptyText(R.string.no_notification_listeners) 81 .build(); 82 83 private NotificationManager mNm; 84 protected Context mContext; 85 private PackageManager mPm; 86 private DevicePolicyManager mDpm; 87 private ServiceListing mServiceListing; 88 private IconDrawableFactory mIconDrawableFactory; 89 private NotificationBackend mBackend = new NotificationBackend(); 90 91 @Override onCreate(Bundle icicle)92 public void onCreate(Bundle icicle) { 93 super.onCreate(icicle); 94 95 mContext = getActivity(); 96 mPm = mContext.getPackageManager(); 97 mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 98 mIconDrawableFactory = IconDrawableFactory.newInstance(mContext); 99 mServiceListing = new ServiceListing.Builder(mContext) 100 .setPermission(CONFIG.permission) 101 .setIntentAction(CONFIG.intentAction) 102 .setNoun(CONFIG.noun) 103 .setSetting(CONFIG.setting) 104 .setTag(CONFIG.tag) 105 .setValidator(info -> { 106 if (info.getComponentName().flattenToString().length() > MAX_CN_LENGTH) { 107 return false; 108 } 109 return true; 110 }) 111 .build(); 112 mServiceListing.addCallback(this::updateList); 113 114 if (UserManager.get(mContext).isManagedProfile()) { 115 // Apps in the work profile do not support notification listeners. 116 Toast.makeText(mContext, 117 mDpm.getResources().getString(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS, 118 () -> mContext.getString(R.string.notification_settings_work_profile)), 119 Toast.LENGTH_SHORT).show(); 120 finish(); 121 } 122 } 123 124 @Override onViewCreated(View view, @Nullable Bundle savedInstanceState)125 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 126 super.onViewCreated(view, savedInstanceState); 127 setEmptyText(CONFIG.emptyText); 128 } 129 130 @Override onResume()131 public void onResume() { 132 super.onResume(); 133 mServiceListing.reload(); 134 mServiceListing.setListening(true); 135 } 136 137 @Override onPause()138 public void onPause() { 139 super.onPause(); 140 mServiceListing.setListening(false); 141 } 142 updateList(List<ServiceInfo> services)143 private void updateList(List<ServiceInfo> services) { 144 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 145 final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId()); 146 147 final PreferenceScreen screen = getPreferenceScreen(); 148 final PreferenceCategory allowedCategory = screen.findPreference(ALLOWED_KEY); 149 allowedCategory.removeAll(); 150 final PreferenceCategory notAllowedCategory = screen.findPreference(NOT_ALLOWED_KEY); 151 notAllowedCategory.removeAll(); 152 153 services.sort(new PackageItemInfo.DisplayNameComparator(mPm)); 154 for (ServiceInfo service : services) { 155 final ComponentName cn = new ComponentName(service.packageName, service.name); 156 CharSequence title = null; 157 try { 158 title = mPm.getApplicationInfoAsUser( 159 service.packageName, 0, UserHandle.myUserId()).loadLabel(mPm); 160 } catch (PackageManager.NameNotFoundException e) { 161 // unlikely, as we are iterating over live services. 162 Log.e(TAG, "can't find package name", e); 163 } 164 165 final AppPreference pref = new AppPreference(getPrefContext()); 166 pref.setTitle(title); 167 pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo, 168 UserHandle.getUserId(service.applicationInfo.uid))); 169 pref.setKey(cn.flattenToString()); 170 pref.setSummary(mBackend.getDeviceList(ICompanionDeviceManager.Stub.asInterface( 171 ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)), 172 com.android.settings.bluetooth.Utils.getLocalBtManager(mContext), 173 service.packageName, 174 UserHandle.myUserId())); 175 if (managedProfileId != UserHandle.USER_NULL 176 && !mDpm.isNotificationListenerServicePermitted( 177 service.packageName, managedProfileId)) { 178 pref.setSummary(mDpm.getResources().getString( 179 WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED, 180 () -> getString( 181 R.string.work_profile_notification_access_blocked_summary))); 182 } 183 pref.setOnPreferenceClickListener(preference -> { 184 final Bundle args = new Bundle(); 185 args.putString(AppInfoBase.ARG_PACKAGE_NAME, cn.getPackageName()); 186 args.putInt(AppInfoBase.ARG_PACKAGE_UID, service.applicationInfo.uid); 187 188 Bundle extras = new Bundle(); 189 extras.putString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, 190 cn.flattenToString()); 191 192 new SubSettingLauncher(getContext()) 193 .setDestination(NotificationAccessDetails.class.getName()) 194 .setSourceMetricsCategory(getMetricsCategory()) 195 .setTitleRes(R.string.manage_notification_access_title) 196 .setArguments(args) 197 .setExtras(extras) 198 .setUserHandle(UserHandle.getUserHandleForUid(service.applicationInfo.uid)) 199 .launch(); 200 return true; 201 }); 202 pref.setKey(cn.flattenToString()); 203 if (mNm.isNotificationListenerAccessGranted(cn)) { 204 allowedCategory.addPreference(pref); 205 } else { 206 notAllowedCategory.addPreference(pref); 207 } 208 } 209 highlightPreferenceIfNeeded(); 210 } 211 212 @Override getMetricsCategory()213 public int getMetricsCategory() { 214 return SettingsEnums.NOTIFICATION_ACCESS; 215 } 216 217 @Override onAttach(Context context)218 public void onAttach(Context context) { 219 super.onAttach(context); 220 mNm = context.getSystemService(NotificationManager.class); 221 } 222 223 @Override getPreferenceScreenResId()224 protected int getPreferenceScreenResId() { 225 return R.xml.notification_access_settings; 226 } 227 228 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 229 new BaseSearchIndexProvider(R.xml.notification_access_settings); 230 } 231