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