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.notification.app; 18 19 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; 20 import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; 21 22 import android.annotation.Nullable; 23 import android.app.ActivityManager; 24 import android.app.NotificationChannel; 25 import android.content.Context; 26 import android.provider.Settings; 27 28 import androidx.annotation.VisibleForTesting; 29 import androidx.fragment.app.FragmentManager; 30 import androidx.preference.Preference; 31 32 import com.android.settings.core.PreferenceControllerMixin; 33 import com.android.settings.notification.NotificationBackend; 34 import com.android.settingslib.RestrictedSwitchPreference; 35 36 /** 37 * Preference controller for Bubbles. This is used as the app-specific page and conversation 38 * settings. 39 */ 40 public class BubblePreferenceController extends NotificationPreferenceController 41 implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { 42 43 private static final String TAG = "BubblePrefContr"; 44 private static final String KEY = "bubble_pref"; 45 @VisibleForTesting 46 static final int SYSTEM_WIDE_ON = 1; 47 @VisibleForTesting 48 static final int SYSTEM_WIDE_OFF = 0; 49 50 private FragmentManager mFragmentManager; 51 private boolean mIsAppPage; 52 private boolean mHasSentInvalidMsg; 53 private int mNumConversations; 54 private NotificationSettings.DependentFieldListener mListener; 55 BubblePreferenceController(Context context, @Nullable FragmentManager fragmentManager, NotificationBackend backend, boolean isAppPage, @Nullable NotificationSettings.DependentFieldListener listener)56 public BubblePreferenceController(Context context, @Nullable FragmentManager fragmentManager, 57 NotificationBackend backend, boolean isAppPage, 58 @Nullable NotificationSettings.DependentFieldListener listener) { 59 super(context, backend); 60 mFragmentManager = fragmentManager; 61 mIsAppPage = isAppPage; 62 mListener = listener; 63 } 64 65 @Override getPreferenceKey()66 public String getPreferenceKey() { 67 return KEY; 68 } 69 70 @Override isAvailable()71 public boolean isAvailable() { 72 if (!super.isAvailable()) { 73 return false; 74 } 75 if (!mIsAppPage && !isEnabled()) { 76 return false; 77 } 78 if (mChannel != null) { 79 if (isDefaultChannel()) { 80 return true; 81 } else { 82 return mAppRow != null && mAppRow.bubblePreference != BUBBLE_PREFERENCE_NONE; 83 } 84 } 85 return true; 86 } 87 88 @Override isIncludedInFilter()89 boolean isIncludedInFilter() { 90 return mPreferenceFilter.contains(NotificationChannel.EDIT_CONVERSATION); 91 } 92 93 @Override updateState(Preference preference)94 public void updateState(Preference preference) { 95 if (mIsAppPage && mAppRow != null) { 96 mHasSentInvalidMsg = mBackend.isInInvalidMsgState(mAppRow.pkg, mAppRow.uid); 97 mNumConversations = mBackend.getConversations( 98 mAppRow.pkg, mAppRow.uid).getList().size(); 99 // We're on the app specific bubble page which displays a tri-state 100 int backEndPref = mAppRow.bubblePreference; 101 BubblePreference pref = (BubblePreference) preference; 102 pref.setDisabledByAdmin(mAdmin); 103 pref.setSelectedVisibility(!mHasSentInvalidMsg || mNumConversations > 0); 104 if (!isEnabled()) { 105 pref.setSelectedPreference(BUBBLE_PREFERENCE_NONE); 106 } else { 107 pref.setSelectedPreference(backEndPref); 108 } 109 } else if (mChannel != null) { 110 // We're on the channel specific notification page which displays a toggle. 111 RestrictedSwitchPreference switchpref = (RestrictedSwitchPreference) preference; 112 switchpref.setDisabledByAdmin(mAdmin); 113 switchpref.setChecked(mChannel.canBubble() && isEnabled()); 114 } 115 } 116 117 @Override onPreferenceChange(Preference preference, Object newValue)118 public boolean onPreferenceChange(Preference preference, Object newValue) { 119 if (mChannel != null) { 120 // Channel page is toggle 121 mChannel.setAllowBubbles((boolean) newValue); 122 saveChannel(); 123 } else if (mIsAppPage) { 124 // App page is bubble preference 125 BubblePreference pref = (BubblePreference) preference; 126 if (mAppRow != null && mFragmentManager != null) { 127 final int value = (int) newValue; 128 if (!isEnabled() 129 && pref.getSelectedPreference() == BUBBLE_PREFERENCE_NONE) { 130 // if the global setting is off, toggling app level permission requires extra 131 // confirmation 132 new BubbleWarningDialogFragment() 133 .setPkgPrefInfo(mAppRow.pkg, mAppRow.uid, value) 134 .show(mFragmentManager, "dialog"); 135 return false; 136 } else { 137 mAppRow.bubblePreference = value; 138 mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value); 139 } 140 } 141 if (mListener != null) { 142 mListener.onFieldValueChanged(); 143 } 144 } 145 return true; 146 } 147 isEnabled()148 private boolean isEnabled() { 149 ActivityManager am = mContext.getSystemService(ActivityManager.class); 150 return !am.isLowRamDevice() && Settings.Secure.getInt(mContext.getContentResolver(), 151 NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON; 152 } 153 154 /** 155 * Used in app level prompt that confirms the user is ok with turning on bubbles 156 * globally. If they aren't, undo that. 157 */ revertBubblesApproval(Context context, String pkg, int uid)158 public static void revertBubblesApproval(Context context, String pkg, int uid) { 159 NotificationBackend backend = new NotificationBackend(); 160 backend.setAllowBubbles(pkg, uid, BUBBLE_PREFERENCE_NONE); 161 162 // changing the global settings will cause the observer on the host page to reload 163 // correct preference state 164 Settings.Secure.putInt(context.getContentResolver(), 165 NOTIFICATION_BUBBLES, 166 SYSTEM_WIDE_OFF); 167 } 168 169 /** 170 * Apply global bubbles approval 171 */ applyBubblesApproval(Context context, String pkg, int uid, int pref)172 public static void applyBubblesApproval(Context context, String pkg, int uid, int pref) { 173 NotificationBackend backend = new NotificationBackend(); 174 backend.setAllowBubbles(pkg, uid, pref); 175 // changing the global settings will cause the observer on the host page to reload 176 // correct preference state 177 Settings.Secure.putInt(context.getContentResolver(), 178 NOTIFICATION_BUBBLES, 179 SYSTEM_WIDE_ON); 180 } 181 } 182