• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 android.app.Flags;
20 import android.content.Context;
21 import android.service.notification.Adjustment;
22 import android.util.ArrayMap;
23 
24 import androidx.annotation.NonNull;
25 import androidx.annotation.Nullable;
26 import androidx.preference.Preference;
27 import androidx.preference.PreferenceCategory;
28 import androidx.preference.TwoStatePreference;
29 
30 import com.android.settings.core.BasePreferenceController;
31 
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 
36 /**
37  * Preference controller governing both the global and individual type-based bundle preferences.
38  */
39 public class BundleCombinedPreferenceController extends BasePreferenceController {
40 
41     static final String GLOBAL_KEY = "global_pref";
42     static final String PROMO_KEY = "promotions";
43     static final String NEWS_KEY = "news";
44     static final String SOCIAL_KEY = "social";
45     static final String RECS_KEY = "recs";
46 
47     static final List<String> ALL_PREF_TYPES = List.of(PROMO_KEY, NEWS_KEY, SOCIAL_KEY, RECS_KEY);
48 
49     @NonNull NotificationBackend mBackend;
50 
51     private @Nullable TwoStatePreference mGlobalPref;
52     private Map<String, TwoStatePreference> mTypePrefs = new ArrayMap<>();
53 
BundleCombinedPreferenceController(@onNull Context context, @NonNull String prefKey, @NonNull NotificationBackend backend)54     public BundleCombinedPreferenceController(@NonNull Context context, @NonNull String prefKey,
55             @NonNull NotificationBackend backend) {
56         super(context, prefKey);
57         mBackend = backend;
58     }
59 
60     @Override
61     @AvailabilityStatus
getAvailabilityStatus()62     public int getAvailabilityStatus() {
63         if (Flags.notificationClassificationUi() && mBackend.isNotificationBundlingSupported()) {
64             return AVAILABLE;
65         }
66         return CONDITIONALLY_UNAVAILABLE;
67     }
68 
69     @Override
updateState(Preference preference)70     public void updateState(Preference preference) {
71         PreferenceCategory category = (PreferenceCategory) preference;
72 
73         // Find and cache relevant preferences for later updates, then set values
74         mGlobalPref = category.findPreference(GLOBAL_KEY);
75         if (mGlobalPref != null) {
76             mGlobalPref.setOnPreferenceChangeListener(mGlobalPrefListener);
77         }
78         for (String key : ALL_PREF_TYPES) {
79             TwoStatePreference typePref = category.findPreference(key);
80             if (typePref != null) {
81                 mTypePrefs.put(key, typePref);
82                 typePref.setOnPreferenceChangeListener(getListenerForType(key));
83             }
84         }
85 
86         updatePrefValues();
87     }
88 
updatePrefValues()89     void updatePrefValues() {
90         boolean isBundlingEnabled = mBackend.isNotificationBundlingEnabled(mContext);
91         Set<Integer> allowedTypes = mBackend.getAllowedBundleTypes();
92 
93         // State check: if bundling is globally enabled, but there are no allowed bundle types,
94         // disable the global bundling state from here before proceeding.
95         if (isBundlingEnabled && allowedTypes.size() == 0) {
96             mBackend.setNotificationBundlingEnabled(false);
97             isBundlingEnabled = false;
98         }
99 
100         if (mGlobalPref != null) {
101             mGlobalPref.setChecked(isBundlingEnabled);
102         }
103 
104         for (String key : mTypePrefs.keySet()) {
105             TwoStatePreference typePref = mTypePrefs.get(key);
106             // checkboxes for individual types should only be active if the global switch is on
107             typePref.setVisible(isBundlingEnabled);
108             if (isBundlingEnabled) {
109                 typePref.setChecked(allowedTypes.contains(getBundleTypeForKey(key)));
110             }
111         }
112     }
113 
114     private Preference.OnPreferenceChangeListener mGlobalPrefListener = (p, val) -> {
115         boolean checked = (boolean) val;
116         mBackend.setNotificationBundlingEnabled(checked);
117         // update state to hide or show preferences for individual types
118         updatePrefValues();
119         return true;
120     };
121 
122     // Returns a preference listener for the given pref key that:
123     //   * sets the backend state for whether that type is enabled
124     //   * if it is disabled, trigger a new update sync global switch if needed
getListenerForType(String prefKey)125     private Preference.OnPreferenceChangeListener getListenerForType(String prefKey) {
126         return (p, val) -> {
127             boolean checked = (boolean) val;
128             mBackend.setBundleTypeState(getBundleTypeForKey(prefKey), checked);
129             if (!checked) {
130                 // goes from checked to un-checked; update state in case this was the last enabled
131                 // individual category
132                 updatePrefValues();
133             }
134             return true;
135         };
136     }
137 
getBundleTypeForKey(String preferenceKey)138     static @Adjustment.Types int getBundleTypeForKey(String preferenceKey) {
139         if (PROMO_KEY.equals(preferenceKey)) {
140             return Adjustment.TYPE_PROMOTION;
141         } else if (NEWS_KEY.equals(preferenceKey)) {
142             return Adjustment.TYPE_NEWS;
143         } else if (SOCIAL_KEY.equals(preferenceKey)) {
144             return Adjustment.TYPE_SOCIAL_MEDIA;
145         } else if (RECS_KEY.equals(preferenceKey)) {
146             return Adjustment.TYPE_CONTENT_RECOMMENDATION;
147         }
148         return Adjustment.TYPE_OTHER;
149     }
150 
151 }
152