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 use(MoreSettingsPreferenceController.class) 127 .setPackage(mComponentName.getPackageName()) 128 .setPackageManager(mPm); 129 final int finalListenerTargetSdk = listenerTargetSdk; 130 getPreferenceControllers().forEach(controllers -> { 131 controllers.forEach(controller -> { 132 if (controller instanceof TypeFilterPreferenceController) { 133 TypeFilterPreferenceController tfpc = 134 (TypeFilterPreferenceController) controller; 135 tfpc.setNm(backend) 136 .setCn(mComponentName) 137 .setServiceInfo(mServiceInfo) 138 .setUserId(mUserId) 139 .setTargetSdk(finalListenerTargetSdk); 140 } 141 }); 142 }); 143 } 144 145 @Override getMetricsCategory()146 public int getMetricsCategory() { 147 return SettingsEnums.NOTIFICATION_ACCESS_DETAIL; 148 } 149 refreshUi()150 protected boolean refreshUi() { 151 if (mComponentName == null) { 152 // No service given 153 Slog.d(TAG, "No component name provided"); 154 return false; 155 } 156 if (!mIsNls) { 157 // This component doesn't have the right androidmanifest definition to be an NLS 158 Slog.d(TAG, "Provided component name is not an NLS"); 159 return false; 160 } 161 if (UserManager.get(getContext()).isManagedProfile()) { 162 // Apps in the work profile do not support notification listeners. 163 Slog.d(TAG, "NLSes aren't allowed in work profiles"); 164 return false; 165 } 166 return true; 167 } 168 169 @Override onResume()170 public void onResume() { 171 super.onResume(); 172 mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 173 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 174 mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction( 175 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 176 177 if (!refreshUi()) { 178 finish(); 179 } 180 Preference apps = getPreferenceScreen().findPreference( 181 use(BridgedAppsLinkPreferenceController.class).getPreferenceKey()); 182 if (apps != null) { 183 184 apps.setOnPreferenceClickListener(preference -> { 185 final Bundle args = new Bundle(); 186 args.putString(AppInfoBase.ARG_PACKAGE_NAME, mPackageName); 187 args.putString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, 188 mComponentName.flattenToString()); 189 190 new SubSettingLauncher(getContext()) 191 .setDestination(BridgedAppsSettings.class.getName()) 192 .setSourceMetricsCategory(getMetricsCategory()) 193 .setTitleRes(R.string.notif_listener_excluded_app_screen_title) 194 .setArguments(args) 195 .setUserHandle(UserHandle.of(mUserId)) 196 .launch(); 197 return true; 198 }); 199 } 200 } 201 retrieveAppEntry()202 protected void retrieveAppEntry() { 203 final Bundle args = getArguments(); 204 mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; 205 Intent intent = (args == null) ? 206 getIntent() : (Intent) args.getParcelable("intent"); 207 if (mPackageName == null) { 208 if (intent != null && intent.getData() != null) { 209 mPackageName = intent.getData().getSchemeSpecificPart(); 210 } 211 } 212 if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { 213 if (hasInteractAcrossUsersPermission()) { 214 mUserId = ((UserHandle) intent.getParcelableExtra( 215 Intent.EXTRA_USER_HANDLE)).getIdentifier(); 216 } else { 217 finish(); 218 } 219 } else { 220 mUserId = UserHandle.myUserId(); 221 } 222 223 try { 224 mPackageInfo = mPm.getPackageInfoAsUser(mPackageName, 225 PackageManager.MATCH_DISABLED_COMPONENTS | 226 PackageManager.GET_SIGNING_CERTIFICATES | 227 PackageManager.GET_PERMISSIONS, mUserId); 228 } catch (PackageManager.NameNotFoundException e) { 229 // oh well 230 } 231 } 232 hasInteractAcrossUsersPermission()233 private boolean hasInteractAcrossUsersPermission() { 234 final String callingPackageName = 235 ((SettingsActivity) getActivity()).getInitialCallingPackage(); 236 237 if (TextUtils.isEmpty(callingPackageName)) { 238 Log.w(TAG, "Not able to get calling package name for permission check"); 239 return false; 240 } 241 242 if (getContext().getPackageManager().checkPermission( 243 Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) 244 != PERMISSION_GRANTED) { 245 Log.w(TAG, "Package " + callingPackageName + " does not have required permission " 246 + Manifest.permission.INTERACT_ACROSS_USERS_FULL); 247 return false; 248 } 249 250 return true; 251 } 252 253 // Dialogs only have access to the parent fragment, not the controller, so pass the information 254 // along to keep business logic out of this file disable(final ComponentName cn)255 public void disable(final ComponentName cn) { 256 final PreferenceScreen screen = getPreferenceScreen(); 257 ApprovalPreferenceController apc = use(ApprovalPreferenceController.class); 258 apc.disable(cn); 259 apc.updateState(screen.findPreference(apc.getPreferenceKey())); 260 getPreferenceControllers().forEach(controllers -> { 261 controllers.forEach(controller -> { 262 controller.updateState(screen.findPreference(controller.getPreferenceKey())); 263 }); 264 }); 265 } 266 enable(ComponentName cn)267 protected void enable(ComponentName cn) { 268 final PreferenceScreen screen = getPreferenceScreen(); 269 ApprovalPreferenceController apc = use(ApprovalPreferenceController.class); 270 apc.enable(cn); 271 apc.updateState(screen.findPreference(apc.getPreferenceKey())); 272 getPreferenceControllers().forEach(controllers -> { 273 controllers.forEach(controller -> { 274 controller.updateState(screen.findPreference(controller.getPreferenceKey())); 275 }); 276 }); 277 } 278 279 // To save binder calls, load this in the fragment rather than each preference controller loadNotificationListenerService()280 protected void loadNotificationListenerService() { 281 mIsNls = false; 282 283 if (mComponentName == null) { 284 return; 285 } 286 Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE) 287 .setComponent(mComponentName); 288 List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser( 289 intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, mUserId); 290 for (ResolveInfo resolveInfo : installedServices) { 291 ServiceInfo info = resolveInfo.serviceInfo; 292 if (android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals( 293 info.permission)) { 294 if (Objects.equals(mComponentName, info.getComponentName())) { 295 mIsNls = true; 296 mServiceName = info.loadLabel(mPm); 297 mServiceInfo = info; 298 break; 299 } 300 } 301 } 302 } 303 304 @Override getPreferenceScreenResId()305 protected int getPreferenceScreenResId() { 306 return R.xml.notification_access_permission_details; 307 } 308 309 @Override getLogTag()310 protected String getLogTag() { 311 return TAG; 312 } 313 }