1 /*
2  * Copyright (C) 2010 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.notification;
18 
19 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS;
20 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED;
21 
22 import android.app.NotificationManager;
23 import android.app.admin.DevicePolicyManager;
24 import android.app.settings.SettingsEnums;
25 import android.companion.ICompanionDeviceManager;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.pm.PackageItemInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ServiceInfo;
31 import android.os.Bundle;
32 import android.os.ServiceManager;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.provider.Settings;
36 import android.service.notification.NotificationListenerService;
37 import android.util.IconDrawableFactory;
38 import android.util.Log;
39 import android.view.View;
40 import android.widget.Toast;
41 
42 import androidx.annotation.Nullable;
43 import androidx.preference.PreferenceCategory;
44 import androidx.preference.PreferenceScreen;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.settings.R;
48 import com.android.settings.Utils;
49 import com.android.settings.applications.AppInfoBase;
50 import com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessDetails;
51 import com.android.settings.core.SubSettingLauncher;
52 import com.android.settings.search.BaseSearchIndexProvider;
53 import com.android.settings.utils.ManagedServiceSettings;
54 import com.android.settings.widget.EmptyTextSettings;
55 import com.android.settingslib.applications.ServiceListing;
56 import com.android.settingslib.search.SearchIndexable;
57 import com.android.settingslib.widget.AppPreference;
58 
59 import java.util.List;
60 
61 /**
62  * Settings screen for managing notification listener permissions
63  */
64 @SearchIndexable
65 public class NotificationAccessSettings extends EmptyTextSettings {
66     private static final String TAG = "NotifAccessSettings";
67     static final String ALLOWED_KEY = "allowed";
68     static final String NOT_ALLOWED_KEY = "not_allowed";
69 
70     private static final ManagedServiceSettings.Config CONFIG =
71             new ManagedServiceSettings.Config.Builder()
72                     .setTag(TAG)
73                     .setSetting(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS)
74                     .setIntentAction(NotificationListenerService.SERVICE_INTERFACE)
75                     .setPermission(android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)
76                     .setNoun("notification listener")
77                     .setWarningDialogTitle(R.string.notification_listener_security_warning_title)
78                     .setWarningDialogSummary(
79                             R.string.notification_listener_security_warning_summary)
80                     .setEmptyText(R.string.no_notification_listeners)
81                     .build();
82 
83     @VisibleForTesting NotificationManager mNm;
84     protected Context mContext;
85     @VisibleForTesting PackageManager mPm;
86     private DevicePolicyManager mDpm;
87     private ServiceListing mServiceListing;
88     private IconDrawableFactory mIconDrawableFactory;
89     private NotificationBackend mBackend = new NotificationBackend();
90 
91     @Override
onCreate(Bundle icicle)92     public void onCreate(Bundle icicle) {
93         super.onCreate(icicle);
94 
95         mContext = getActivity();
96         mPm = mContext.getPackageManager();
97         mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
98         mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
99         mServiceListing = new ServiceListing.Builder(mContext)
100                 .setPermission(CONFIG.permission)
101                 .setIntentAction(CONFIG.intentAction)
102                 .setNoun(CONFIG.noun)
103                 .setSetting(CONFIG.setting)
104                 .setTag(CONFIG.tag)
105                 .build();
106         mServiceListing.addCallback(this::updateList);
107 
108         if (UserManager.get(mContext).isManagedProfile()) {
109             // Apps in the work profile do not support notification listeners.
110             Toast.makeText(mContext,
111                     mDpm.getResources().getString(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS,
112                             () -> mContext.getString(R.string.notification_settings_work_profile)),
113                     Toast.LENGTH_SHORT).show();
114             finish();
115         }
116     }
117 
118     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)119     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
120         super.onViewCreated(view, savedInstanceState);
121         setEmptyText(CONFIG.emptyText);
122     }
123 
124     @Override
onResume()125     public void onResume() {
126         super.onResume();
127         mServiceListing.reload();
128         mServiceListing.setListening(true);
129     }
130 
131     @Override
onPause()132     public void onPause() {
133         super.onPause();
134         mServiceListing.setListening(false);
135     }
136 
137     @VisibleForTesting
updateList(List<ServiceInfo> services)138     void updateList(List<ServiceInfo> services) {
139         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
140         final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId());
141 
142         final PreferenceScreen screen = getPreferenceScreen();
143         final PreferenceCategory allowedCategory = screen.findPreference(ALLOWED_KEY);
144         allowedCategory.removeAll();
145         final PreferenceCategory notAllowedCategory = screen.findPreference(NOT_ALLOWED_KEY);
146         notAllowedCategory.removeAll();
147 
148         services.sort(new PackageItemInfo.DisplayNameComparator(mPm));
149         for (ServiceInfo service : services) {
150             final ComponentName cn = new ComponentName(service.packageName, service.name);
151             boolean isAllowed = mNm.isNotificationListenerAccessGranted(cn);
152             if (!isAllowed && cn.flattenToString().length()
153                     > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
154                 continue;
155             }
156 
157             CharSequence title = null;
158             try {
159                 title = mPm.getApplicationInfoAsUser(
160                         service.packageName, 0, UserHandle.myUserId()).loadLabel(mPm);
161             } catch (PackageManager.NameNotFoundException e) {
162                 // unlikely, as we are iterating over live services.
163                 Log.e(TAG, "can't find package name", e);
164             }
165 
166             final AppPreference pref = new AppPreference(getPrefContext());
167             pref.setTitle(title);
168             pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
169                     UserHandle.getUserId(service.applicationInfo.uid)));
170             pref.setKey(cn.flattenToString());
171             pref.setSummary(mBackend.getDeviceList(ICompanionDeviceManager.Stub.asInterface(
172                     ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)),
173                     com.android.settings.bluetooth.Utils.getLocalBtManager(mContext),
174                     service.packageName,
175                     UserHandle.myUserId()));
176             if (managedProfileId != UserHandle.USER_NULL
177                     && !mDpm.isNotificationListenerServicePermitted(
178                     service.packageName, managedProfileId)) {
179                 pref.setSummary(mDpm.getResources().getString(
180                         WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED,
181                         () -> getString(
182                                 R.string.work_profile_notification_access_blocked_summary)));
183             }
184             pref.setOnPreferenceClickListener(preference -> {
185                 final Bundle args = new Bundle();
186                 args.putString(AppInfoBase.ARG_PACKAGE_NAME, cn.getPackageName());
187                 args.putInt(AppInfoBase.ARG_PACKAGE_UID, service.applicationInfo.uid);
188 
189                 Bundle extras = new Bundle();
190                 extras.putString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
191                         cn.flattenToString());
192 
193                 new SubSettingLauncher(getContext())
194                         .setDestination(NotificationAccessDetails.class.getName())
195                         .setSourceMetricsCategory(getMetricsCategory())
196                         .setTitleRes(R.string.manage_notification_access_title)
197                         .setArguments(args)
198                         .setExtras(extras)
199                         .setUserHandle(UserHandle.getUserHandleForUid(service.applicationInfo.uid))
200                         .launch();
201                         return true;
202                     });
203             pref.setKey(cn.flattenToString());
204             if (isAllowed) {
205                 allowedCategory.addPreference(pref);
206             } else {
207                 notAllowedCategory.addPreference(pref);
208             }
209         }
210         highlightPreferenceIfNeeded();
211     }
212 
213     @Override
getMetricsCategory()214     public int getMetricsCategory() {
215         return SettingsEnums.NOTIFICATION_ACCESS;
216     }
217 
218     @Override
onAttach(Context context)219     public void onAttach(Context context) {
220         super.onAttach(context);
221         mNm = context.getSystemService(NotificationManager.class);
222     }
223 
224     @Override
getPreferenceScreenResId()225     protected int getPreferenceScreenResId() {
226         return R.xml.notification_access_settings;
227     }
228 
229     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
230             new BaseSearchIndexProvider(R.xml.notification_access_settings);
231 }
232