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