1 /*
2  * Copyright (C) 2021 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.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
20 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
21 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
22 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
23 
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.pm.ServiceInfo;
27 import android.os.Build;
28 import android.service.notification.NotificationListenerFilter;
29 import android.service.notification.NotificationListenerService;
30 import android.text.TextUtils;
31 
32 import androidx.preference.CheckBoxPreference;
33 import androidx.preference.Preference;
34 
35 import com.android.settings.core.BasePreferenceController;
36 import com.android.settings.core.PreferenceControllerMixin;
37 import com.android.settings.notification.NotificationBackend;
38 
39 public abstract class TypeFilterPreferenceController extends BasePreferenceController implements
40         PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
41 
42     private static final String TAG = "TypeFilterPrefCntlr";
43     private static final String FLAG_SEPARATOR = "\\|";
44 
45     private ComponentName mCn;
46     private int mUserId;
47     private NotificationBackend mNm;
48     private NotificationListenerFilter mNlf;
49     private ServiceInfo mSi;
50     private int mTargetSdk;
51 
TypeFilterPreferenceController(Context context, String key)52     public TypeFilterPreferenceController(Context context, String key) {
53         super(context, key);
54     }
55 
setCn(ComponentName cn)56     public TypeFilterPreferenceController setCn(ComponentName cn) {
57         mCn = cn;
58         return this;
59     }
60 
setUserId(int userId)61     public TypeFilterPreferenceController setUserId(int userId) {
62         mUserId = userId;
63         return this;
64     }
65 
setNm(NotificationBackend nm)66     public TypeFilterPreferenceController setNm(NotificationBackend nm) {
67         mNm = nm;
68         return this;
69     }
70 
setServiceInfo(ServiceInfo si)71     public TypeFilterPreferenceController setServiceInfo(ServiceInfo si) {
72         mSi = si;
73         return this;
74     }
75 
setTargetSdk(int targetSdk)76     public TypeFilterPreferenceController setTargetSdk(int targetSdk) {
77         mTargetSdk = targetSdk;
78         return this;
79     }
80 
getType()81     abstract protected int getType();
82 
83     @Override
getAvailabilityStatus()84     public int getAvailabilityStatus() {
85         if (mNm.isNotificationListenerAccessGranted(mCn)) {
86             if (mTargetSdk > Build.VERSION_CODES.S) {
87                 return AVAILABLE;
88             }
89 
90             mNlf = mNm.getListenerFilter(mCn, mUserId);
91             if (!mNlf.areAllTypesAllowed() || !mNlf.getDisallowedPackages().isEmpty()) {
92                 return AVAILABLE;
93             }
94         }
95         return DISABLED_DEPENDENT_SETTING;
96     }
97 
hasFlag(int value, int flag)98     private boolean hasFlag(int value, int flag) {
99         return (value & flag) != 0;
100     }
101 
102     @Override
onPreferenceChange(Preference preference, Object newValue)103     public boolean onPreferenceChange(Preference preference, Object newValue) {
104         // retrieve latest in case the package filter has changed
105         mNlf = mNm.getListenerFilter(mCn, mUserId);
106 
107         boolean enabled = (boolean) newValue;
108 
109         int newFilter = mNlf.getTypes();
110         if (enabled) {
111             newFilter |= getType();
112         } else {
113             newFilter &= ~getType();
114         }
115         mNlf.setTypes(newFilter);
116         mNm.setListenerFilter(mCn, mUserId, mNlf);
117         return true;
118     }
119 
120     @Override
updateState(Preference pref)121     public void updateState(Preference pref) {
122         mNlf = mNm.getListenerFilter(mCn, mUserId);
123 
124         CheckBoxPreference check = (CheckBoxPreference) pref;
125         check.setChecked(hasFlag(mNlf.getTypes(), getType()));
126 
127         boolean disableRequestedByApp = false;
128         if (mSi != null) {
129             if (mSi.metaData != null && mSi.metaData.containsKey(
130                     NotificationListenerService.META_DATA_DISABLED_FILTER_TYPES)) {
131                 String typeList = mSi.metaData.get(
132                         NotificationListenerService.META_DATA_DISABLED_FILTER_TYPES).toString();
133                 if (typeList != null) {
134                     int types = 0;
135                     String[] typeStrings = typeList.split(FLAG_SEPARATOR);
136                     for (int i = 0; i < typeStrings.length; i++) {
137                         final String typeString = typeStrings[i];
138                         if (TextUtils.isEmpty(typeString)) {
139                             continue;
140                         }
141                         if (typeString.equalsIgnoreCase("ONGOING")) {
142                             types |= FLAG_FILTER_TYPE_ONGOING;
143                         } else if (typeString.equalsIgnoreCase("CONVERSATIONS")) {
144                             types |= FLAG_FILTER_TYPE_CONVERSATIONS;
145                         } else if (typeString.equalsIgnoreCase("SILENT")) {
146                             types |= FLAG_FILTER_TYPE_SILENT;
147                         } else if (typeString.equalsIgnoreCase("ALERTING")) {
148                             types |= FLAG_FILTER_TYPE_ALERTING;
149                         } else {
150                             try {
151                                 types |= Integer.parseInt(typeString);
152                             } catch (NumberFormatException e) {
153                                 // skip
154                             }
155                         }
156                     }
157                     if (hasFlag(types, getType())) {
158                         disableRequestedByApp = true;
159                     }
160                 }
161             }
162         }
163         // Apps can prevent a category from being turned on, but not turned off
164         boolean disabledByApp = disableRequestedByApp && !check.isChecked();
165         pref.setEnabled(getAvailabilityStatus() == AVAILABLE && !disabledByApp);
166     }
167 }