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 }