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