• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }