1 /* 2 * Copyright (C) 2019 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.applications.specialaccess.notificationaccess; 18 19 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21 import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME; 22 23 import android.Manifest; 24 import android.app.NotificationManager; 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.Intent; 30 import android.content.pm.PackageInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.content.pm.ServiceInfo; 34 import android.os.Build; 35 import android.os.Bundle; 36 import android.os.ServiceManager; 37 import android.os.UserHandle; 38 import android.os.UserManager; 39 import android.provider.Settings; 40 import android.service.notification.NotificationListenerService; 41 import android.text.TextUtils; 42 import android.util.Log; 43 import android.util.Slog; 44 45 import androidx.preference.Preference; 46 import androidx.preference.PreferenceScreen; 47 48 import com.android.settings.R; 49 import com.android.settings.SettingsActivity; 50 import com.android.settings.applications.AppInfoBase; 51 import com.android.settings.bluetooth.Utils; 52 import com.android.settings.core.SubSettingLauncher; 53 import com.android.settings.dashboard.DashboardFragment; 54 import com.android.settings.notification.NotificationBackend; 55 import com.android.settingslib.RestrictedLockUtils; 56 import com.android.settingslib.RestrictedLockUtilsInternal; 57 58 import java.util.List; 59 import java.util.Objects; 60 61 public class NotificationAccessDetails extends DashboardFragment { 62 private static final String TAG = "NotifAccessDetails"; 63 64 private NotificationBackend mNm = new NotificationBackend(); 65 private ComponentName mComponentName; 66 private CharSequence mServiceName; 67 protected ServiceInfo mServiceInfo; 68 protected PackageInfo mPackageInfo; 69 protected int mUserId; 70 protected String mPackageName; 71 protected RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin; 72 protected boolean mAppsControlDisallowedBySystem; 73 private boolean mIsNls; 74 private PackageManager mPm; 75 76 @Override onAttach(Context context)77 public void onAttach(Context context) { 78 super.onAttach(context); 79 final Intent intent = getIntent(); 80 if (mComponentName == null && intent != null) { 81 String cn = intent.getStringExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME); 82 if (cn != null) { 83 mComponentName = ComponentName.unflattenFromString(cn); 84 if (mComponentName != null) { 85 final Bundle args = getArguments(); 86 args.putString(ARG_PACKAGE_NAME, mComponentName.getPackageName()); 87 } 88 } 89 } 90 mPm = getPackageManager(); 91 retrieveAppEntry(); 92 loadNotificationListenerService(); 93 NotificationBackend backend = new NotificationBackend(); 94 int listenerTargetSdk = Build.VERSION_CODES.S; 95 try { 96 listenerTargetSdk = mPm.getTargetSdkVersion(mComponentName.getPackageName()); 97 } catch (PackageManager.NameNotFoundException e){ 98 // how did we get here? 99 } 100 use(ApprovalPreferenceController.class) 101 .setPkgInfo(mPackageInfo) 102 .setCn(mComponentName) 103 .setNm(context.getSystemService(NotificationManager.class)) 104 .setPm(mPm) 105 .setParent(this); 106 use(HeaderPreferenceController.class) 107 .setFragment(this) 108 .setPackageInfo(mPackageInfo) 109 .setPm(context.getPackageManager()) 110 .setServiceName(mServiceName) 111 .setBluetoothManager(Utils.getLocalBtManager(context)) 112 .setCdm(ICompanionDeviceManager.Stub.asInterface( 113 ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE))) 114 .setCn(mComponentName) 115 .setUserId(mUserId); 116 use(PreUpgradePreferenceController.class) 117 .setNm(backend) 118 .setCn(mComponentName) 119 .setUserId(mUserId) 120 .setTargetSdk(listenerTargetSdk); 121 use(BridgedAppsLinkPreferenceController.class) 122 .setNm(backend) 123 .setCn(mComponentName) 124 .setUserId(mUserId) 125 .setTargetSdk(listenerTargetSdk); 126 final int finalListenerTargetSdk = listenerTargetSdk; 127 getPreferenceControllers().forEach(controllers -> { 128 controllers.forEach(controller -> { 129 if (controller instanceof TypeFilterPreferenceController) { 130 TypeFilterPreferenceController tfpc = 131 (TypeFilterPreferenceController) controller; 132 tfpc.setNm(backend) 133 .setCn(mComponentName) 134 .setServiceInfo(mServiceInfo) 135 .setUserId(mUserId) 136 .setTargetSdk(finalListenerTargetSdk); 137 } 138 }); 139 }); 140 } 141 142 @Override getMetricsCategory()143 public int getMetricsCategory() { 144 return SettingsEnums.NOTIFICATION_ACCESS_DETAIL; 145 } 146 refreshUi()147 protected boolean refreshUi() { 148 if (mComponentName == null) { 149 // No service given 150 Slog.d(TAG, "No component name provided"); 151 return false; 152 } 153 if (!mIsNls) { 154 // This component doesn't have the right androidmanifest definition to be an NLS 155 Slog.d(TAG, "Provided component name is not an NLS"); 156 return false; 157 } 158 if (UserManager.get(getContext()).isManagedProfile()) { 159 // Apps in the work profile do not support notification listeners. 160 Slog.d(TAG, "NLSes aren't allowed in work profiles"); 161 return false; 162 } 163 return true; 164 } 165 166 @Override onResume()167 public void onResume() { 168 super.onResume(); 169 mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 170 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 171 mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction( 172 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 173 174 if (!refreshUi()) { 175 finish(); 176 } 177 Preference apps = getPreferenceScreen().findPreference( 178 use(BridgedAppsLinkPreferenceController.class).getPreferenceKey()); 179 if (apps != null) { 180 181 apps.setOnPreferenceClickListener(preference -> { 182 final Bundle args = new Bundle(); 183 args.putString(AppInfoBase.ARG_PACKAGE_NAME, mPackageName); 184 args.putString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, 185 mComponentName.flattenToString()); 186 187 new SubSettingLauncher(getContext()) 188 .setDestination(BridgedAppsSettings.class.getName()) 189 .setSourceMetricsCategory(getMetricsCategory()) 190 .setTitleRes(R.string.notif_listener_excluded_app_screen_title) 191 .setArguments(args) 192 .setUserHandle(UserHandle.of(mUserId)) 193 .launch(); 194 return true; 195 }); 196 } 197 } 198 retrieveAppEntry()199 protected void retrieveAppEntry() { 200 final Bundle args = getArguments(); 201 mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; 202 Intent intent = (args == null) ? 203 getIntent() : (Intent) args.getParcelable("intent"); 204 if (mPackageName == null) { 205 if (intent != null && intent.getData() != null) { 206 mPackageName = intent.getData().getSchemeSpecificPart(); 207 } 208 } 209 if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { 210 if (hasInteractAcrossUsersPermission()) { 211 mUserId = ((UserHandle) intent.getParcelableExtra( 212 Intent.EXTRA_USER_HANDLE)).getIdentifier(); 213 } else { 214 finish(); 215 } 216 } else { 217 mUserId = UserHandle.myUserId(); 218 } 219 220 try { 221 mPackageInfo = mPm.getPackageInfoAsUser(mPackageName, 222 PackageManager.MATCH_DISABLED_COMPONENTS | 223 PackageManager.GET_SIGNING_CERTIFICATES | 224 PackageManager.GET_PERMISSIONS, mUserId); 225 } catch (PackageManager.NameNotFoundException e) { 226 // oh well 227 } 228 } 229 hasInteractAcrossUsersPermission()230 private boolean hasInteractAcrossUsersPermission() { 231 final String callingPackageName = 232 ((SettingsActivity) getActivity()).getInitialCallingPackage(); 233 234 if (TextUtils.isEmpty(callingPackageName)) { 235 Log.w(TAG, "Not able to get calling package name for permission check"); 236 return false; 237 } 238 239 if (getContext().getPackageManager().checkPermission( 240 Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) 241 != PERMISSION_GRANTED) { 242 Log.w(TAG, "Package " + callingPackageName + " does not have required permission " 243 + Manifest.permission.INTERACT_ACROSS_USERS_FULL); 244 return false; 245 } 246 247 return true; 248 } 249 250 // Dialogs only have access to the parent fragment, not the controller, so pass the information 251 // along to keep business logic out of this file disable(final ComponentName cn)252 public void disable(final ComponentName cn) { 253 final PreferenceScreen screen = getPreferenceScreen(); 254 ApprovalPreferenceController apc = use(ApprovalPreferenceController.class); 255 apc.disable(cn); 256 apc.updateState(screen.findPreference(apc.getPreferenceKey())); 257 getPreferenceControllers().forEach(controllers -> { 258 controllers.forEach(controller -> { 259 controller.updateState(screen.findPreference(controller.getPreferenceKey())); 260 }); 261 }); 262 } 263 enable(ComponentName cn)264 protected void enable(ComponentName cn) { 265 final PreferenceScreen screen = getPreferenceScreen(); 266 ApprovalPreferenceController apc = use(ApprovalPreferenceController.class); 267 apc.enable(cn); 268 apc.updateState(screen.findPreference(apc.getPreferenceKey())); 269 getPreferenceControllers().forEach(controllers -> { 270 controllers.forEach(controller -> { 271 controller.updateState(screen.findPreference(controller.getPreferenceKey())); 272 }); 273 }); 274 } 275 276 // To save binder calls, load this in the fragment rather than each preference controller loadNotificationListenerService()277 protected void loadNotificationListenerService() { 278 mIsNls = false; 279 280 if (mComponentName == null) { 281 return; 282 } 283 Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE) 284 .setComponent(mComponentName); 285 List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser( 286 intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, mUserId); 287 for (ResolveInfo resolveInfo : installedServices) { 288 ServiceInfo info = resolveInfo.serviceInfo; 289 if (android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals( 290 info.permission)) { 291 if (Objects.equals(mComponentName, info.getComponentName())) { 292 mIsNls = true; 293 mServiceName = info.loadLabel(mPm); 294 mServiceInfo = info; 295 break; 296 } 297 } 298 } 299 } 300 301 @Override getPreferenceScreenResId()302 protected int getPreferenceScreenResId() { 303 return R.xml.notification_access_permission_details; 304 } 305 306 @Override getLogTag()307 protected String getLogTag() { 308 return TAG; 309 } 310 }