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