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 com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME; 20 21 import android.app.Activity; 22 import android.app.NotificationManager; 23 import android.app.settings.SettingsEnums; 24 import android.companion.ICompanionDeviceManager; 25 import android.compat.annotation.ChangeId; 26 import android.compat.annotation.EnabledAfter; 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.NotificationListenerFilter; 41 import android.service.notification.NotificationListenerService; 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.applications.manageapplications.ManageApplications; 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 .setParent(this); 107 use(HeaderPreferenceController.class) 108 .setFragment(this) 109 .setPackageInfo(mPackageInfo) 110 .setPm(context.getPackageManager()) 111 .setServiceName(mServiceName) 112 .setBluetoothManager(Utils.getLocalBtManager(context)) 113 .setCdm(ICompanionDeviceManager.Stub.asInterface( 114 ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE))) 115 .setCn(mComponentName) 116 .setUserId(mUserId); 117 use(PreUpgradePreferenceController.class) 118 .setNm(backend) 119 .setCn(mComponentName) 120 .setUserId(mUserId) 121 .setTargetSdk(listenerTargetSdk); 122 use(BridgedAppsLinkPreferenceController.class) 123 .setNm(backend) 124 .setCn(mComponentName) 125 .setUserId(mUserId) 126 .setTargetSdk(listenerTargetSdk); 127 final int finalListenerTargetSdk = listenerTargetSdk; 128 getPreferenceControllers().forEach(controllers -> { 129 controllers.forEach(controller -> { 130 if (controller instanceof TypeFilterPreferenceController) { 131 TypeFilterPreferenceController tfpc = 132 (TypeFilterPreferenceController) controller; 133 tfpc.setNm(backend) 134 .setCn(mComponentName) 135 .setServiceInfo(mServiceInfo) 136 .setUserId(mUserId) 137 .setTargetSdk(finalListenerTargetSdk); 138 } 139 }); 140 }); 141 } 142 143 @Override getMetricsCategory()144 public int getMetricsCategory() { 145 return SettingsEnums.NOTIFICATION_ACCESS_DETAIL; 146 } 147 refreshUi()148 protected boolean refreshUi() { 149 if (mComponentName == null) { 150 // No service given 151 Slog.d(TAG, "No component name provided"); 152 return false; 153 } 154 if (!mIsNls) { 155 // This component doesn't have the right androidmanifest definition to be an NLS 156 Slog.d(TAG, "Provided component name is not an NLS"); 157 return false; 158 } 159 if (UserManager.get(getContext()).isManagedProfile()) { 160 // Apps in the work profile do not support notification listeners. 161 Slog.d(TAG, "NLSes aren't allowed in work profiles"); 162 return false; 163 } 164 return true; 165 } 166 167 @Override onResume()168 public void onResume() { 169 super.onResume(); 170 mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 171 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 172 mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction( 173 getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId); 174 175 if (!refreshUi()) { 176 setIntentAndFinish(true /* appChanged */); 177 } 178 Preference apps = getPreferenceScreen().findPreference( 179 use(BridgedAppsLinkPreferenceController.class).getPreferenceKey()); 180 if (apps != null) { 181 182 apps.setOnPreferenceClickListener(preference -> { 183 final Bundle args = new Bundle(); 184 args.putString(AppInfoBase.ARG_PACKAGE_NAME, mPackageName); 185 args.putString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, 186 mComponentName.flattenToString()); 187 188 new SubSettingLauncher(getContext()) 189 .setDestination(BridgedAppsSettings.class.getName()) 190 .setSourceMetricsCategory(getMetricsCategory()) 191 .setTitleRes(R.string.notif_listener_excluded_app_screen_title) 192 .setArguments(args) 193 .setUserHandle(UserHandle.of(mUserId)) 194 .launch(); 195 return true; 196 }); 197 } 198 } 199 setIntentAndFinish(boolean appChanged)200 protected void setIntentAndFinish(boolean appChanged) { 201 Log.i(TAG, "appChanged=" + appChanged); 202 Intent intent = new Intent(); 203 intent.putExtra(ManageApplications.APP_CHG, appChanged); 204 SettingsActivity sa = (SettingsActivity) getActivity(); 205 sa.finishPreferencePanel(Activity.RESULT_OK, intent); 206 } 207 retrieveAppEntry()208 protected void retrieveAppEntry() { 209 final Bundle args = getArguments(); 210 mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; 211 Intent intent = (args == null) ? 212 getIntent() : (Intent) args.getParcelable("intent"); 213 if (mPackageName == null) { 214 if (intent != null && intent.getData() != null) { 215 mPackageName = intent.getData().getSchemeSpecificPart(); 216 } 217 } 218 if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { 219 mUserId = ((UserHandle) intent.getParcelableExtra( 220 Intent.EXTRA_USER_HANDLE)).getIdentifier(); 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 Log.e(TAG, "Exception when retrieving package:" + mPackageName, e); 232 } 233 } 234 235 // Dialogs only have access to the parent fragment, not the controller, so pass the information 236 // along to keep business logic out of this file disable(final ComponentName cn)237 public void disable(final ComponentName cn) { 238 final PreferenceScreen screen = getPreferenceScreen(); 239 ApprovalPreferenceController apc = use(ApprovalPreferenceController.class); 240 apc.disable(cn); 241 apc.updateState(screen.findPreference(apc.getPreferenceKey())); 242 getPreferenceControllers().forEach(controllers -> { 243 controllers.forEach(controller -> { 244 if (controller instanceof TypeFilterPreferenceController) { 245 TypeFilterPreferenceController tfpc = 246 (TypeFilterPreferenceController) controller; 247 tfpc.updateState(screen.findPreference(tfpc.getPreferenceKey())); 248 } 249 }); 250 }); 251 } 252 enable(ComponentName cn)253 protected void enable(ComponentName cn) { 254 final PreferenceScreen screen = getPreferenceScreen(); 255 ApprovalPreferenceController apc = use(ApprovalPreferenceController.class); 256 apc.enable(cn); 257 apc.updateState(screen.findPreference(apc.getPreferenceKey())); 258 getPreferenceControllers().forEach(controllers -> { 259 controllers.forEach(controller -> { 260 if (controller instanceof TypeFilterPreferenceController) { 261 TypeFilterPreferenceController tfpc = 262 (TypeFilterPreferenceController) controller; 263 tfpc.updateState(screen.findPreference(tfpc.getPreferenceKey())); 264 } 265 }); 266 }); 267 } 268 269 // To save binder calls, load this in the fragment rather than each preference controller loadNotificationListenerService()270 protected void loadNotificationListenerService() { 271 mIsNls = false; 272 273 if (mComponentName == null) { 274 return; 275 } 276 Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE) 277 .setComponent(mComponentName); 278 List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser( 279 intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, mUserId); 280 for (ResolveInfo resolveInfo : installedServices) { 281 ServiceInfo info = resolveInfo.serviceInfo; 282 if (android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals( 283 info.permission)) { 284 if (Objects.equals(mComponentName, info.getComponentName())) { 285 mIsNls = true; 286 mServiceName = info.loadLabel(mPm); 287 mServiceInfo = info; 288 break; 289 } 290 } 291 } 292 } 293 294 @Override getPreferenceScreenResId()295 protected int getPreferenceScreenResId() { 296 return R.xml.notification_access_permission_details; 297 } 298 299 @Override getLogTag()300 protected String getLogTag() { 301 return TAG; 302 } 303 }