1 /*
2  * Copyright (C) 2015 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.systemui.qs.tiles;
18 
19 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
20 import static android.provider.Settings.Global.ZEN_MODE_OFF;
21 
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.UserManager;
32 import android.provider.Settings;
33 import android.provider.Settings.Global;
34 import android.service.notification.ZenModeConfig;
35 import android.service.quicksettings.Tile;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.widget.Switch;
39 
40 import androidx.annotation.Nullable;
41 
42 import com.android.internal.jank.InteractionJankMonitor;
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
45 import com.android.settingslib.notification.EnableZenModeDialog;
46 import com.android.systemui.Prefs;
47 import com.android.systemui.animation.DialogCuj;
48 import com.android.systemui.animation.DialogTransitionAnimator;
49 import com.android.systemui.animation.Expandable;
50 import com.android.systemui.dagger.qualifiers.Background;
51 import com.android.systemui.dagger.qualifiers.Main;
52 import com.android.systemui.plugins.ActivityStarter;
53 import com.android.systemui.plugins.FalsingManager;
54 import com.android.systemui.plugins.qs.QSTile.BooleanState;
55 import com.android.systemui.plugins.statusbar.StatusBarStateController;
56 import com.android.systemui.qs.QSHost;
57 import com.android.systemui.qs.QsEventLogger;
58 import com.android.systemui.qs.UserSettingObserver;
59 import com.android.systemui.qs.logging.QSLogger;
60 import com.android.systemui.qs.tileimpl.QSTileImpl;
61 import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
62 import com.android.systemui.res.R;
63 import com.android.systemui.statusbar.phone.SystemUIDialog;
64 import com.android.systemui.statusbar.policy.ZenModeController;
65 import com.android.systemui.util.settings.SecureSettings;
66 
67 import javax.inject.Inject;
68 
69 /** Quick settings tile: Do not disturb **/
70 public class DndTile extends QSTileImpl<BooleanState> {
71 
72     public static final String TILE_SPEC = "dnd";
73 
74     private static final Intent ZEN_SETTINGS =
75             new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
76 
77     private static final Intent ZEN_PRIORITY_SETTINGS =
78             new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
79 
80     private static final String INTERACTION_JANK_TAG = "start_zen_mode";
81 
82     private final ZenModeController mController;
83     private final SharedPreferences mSharedPreferences;
84     private final UserSettingObserver mSettingZenDuration;
85     private final DialogTransitionAnimator mDialogTransitionAnimator;
86     private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
87 
88     private boolean mListening;
89 
90     @Inject
DndTile( QSHost host, QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, ZenModeController zenModeController, @Main SharedPreferences sharedPreferences, SecureSettings secureSettings, DialogTransitionAnimator dialogTransitionAnimator )91     public DndTile(
92             QSHost host,
93             QsEventLogger uiEventLogger,
94             @Background Looper backgroundLooper,
95             @Main Handler mainHandler,
96             FalsingManager falsingManager,
97             MetricsLogger metricsLogger,
98             StatusBarStateController statusBarStateController,
99             ActivityStarter activityStarter,
100             QSLogger qsLogger,
101             ZenModeController zenModeController,
102             @Main SharedPreferences sharedPreferences,
103             SecureSettings secureSettings,
104             DialogTransitionAnimator dialogTransitionAnimator
105     ) {
106         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
107                 statusBarStateController, activityStarter, qsLogger);
108         mController = zenModeController;
109         mSharedPreferences = sharedPreferences;
110         mController.observe(getLifecycle(), mZenCallback);
111         mDialogTransitionAnimator = dialogTransitionAnimator;
112         mSettingZenDuration = new UserSettingObserver(secureSettings, mUiHandler,
113                 Settings.Secure.ZEN_DURATION, getHost().getUserId()) {
114             @Override
115             protected void handleValueChanged(int value, boolean observedChange) {
116                 refreshState();
117             }
118         };
119         mQSZenDialogMetricsLogger = new QSZenModeDialogMetricsLogger(mContext);
120     }
121 
setVisible(Context context, boolean visible)122     public static void setVisible(Context context, boolean visible) {
123         Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
124     }
125 
isVisible(SharedPreferences prefs)126     public static boolean isVisible(SharedPreferences prefs) {
127         return prefs.getBoolean(Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
128     }
129 
setCombinedIcon(Context context, boolean combined)130     public static void setCombinedIcon(Context context, boolean combined) {
131         Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
132     }
133 
isCombinedIcon(SharedPreferences sharedPreferences)134     public static boolean isCombinedIcon(SharedPreferences sharedPreferences) {
135         return sharedPreferences.getBoolean(Prefs.Key.DND_TILE_COMBINED_ICON,
136                 false /* defaultValue */);
137     }
138 
139     @Override
newTileState()140     public BooleanState newTileState() {
141         return new BooleanState();
142     }
143 
144     @Override
getLongClickIntent()145     public Intent getLongClickIntent() {
146         return ZEN_SETTINGS;
147     }
148 
149     @Override
handleClick(@ullable Expandable expandable)150     protected void handleClick(@Nullable Expandable expandable) {
151         // Zen is currently on
152         if (mState.value) {
153             mController.setZen(ZEN_MODE_OFF, null, TAG);
154         } else {
155             enableZenMode(expandable);
156         }
157     }
158 
159     @Override
handleUserSwitch(int newUserId)160     protected void handleUserSwitch(int newUserId) {
161         super.handleUserSwitch(newUserId);
162         mSettingZenDuration.setUserId(newUserId);
163     }
164 
enableZenMode(@ullable Expandable expandable)165     private void enableZenMode(@Nullable Expandable expandable) {
166         int zenDuration = mSettingZenDuration.getValue();
167         boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(),
168                 Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
169                 && Settings.Secure.getInt(mContext.getContentResolver(),
170                 Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
171         if (showOnboarding) {
172             // don't show on-boarding again or notification ever
173             Settings.Secure.putInt(mContext.getContentResolver(),
174                     Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
175             // turn on DND
176             mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
177             // show on-boarding screen
178             Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
179             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
180             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
181         } else {
182             switch (zenDuration) {
183                 case Settings.Secure.ZEN_DURATION_PROMPT:
184                     mUiHandler.post(() -> {
185                         Dialog dialog = makeZenModeDialog();
186                         if (expandable != null) {
187                             DialogTransitionAnimator.Controller controller =
188                                     expandable.dialogTransitionController(new DialogCuj(
189                                             InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
190                                             INTERACTION_JANK_TAG));
191                             if (controller != null) {
192                                 mDialogTransitionAnimator.show(dialog,
193                                         controller, /* animateBackgroundBoundsChange= */ false);
194                             } else {
195                                 dialog.show();
196                             }
197                         } else {
198                             dialog.show();
199                         }
200                     });
201                     break;
202                 case Settings.Secure.ZEN_DURATION_FOREVER:
203                     mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
204                     break;
205                 default:
206                     Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
207                             mHost.getUserId(), true).id;
208                     mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
209                             conditionId, TAG);
210             }
211         }
212     }
213 
makeZenModeDialog()214     private Dialog makeZenModeDialog() {
215         AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
216                 true /* cancelIsNeutral */,
217                 mQSZenDialogMetricsLogger).createDialog();
218         SystemUIDialog.applyFlags(dialog);
219         SystemUIDialog.setShowForAllUsers(dialog, true);
220         SystemUIDialog.registerDismissListener(dialog);
221         SystemUIDialog.setDialogSize(dialog);
222         return dialog;
223     }
224 
225     @Override
handleSecondaryClick(@ullable Expandable expandable)226     protected void handleSecondaryClick(@Nullable Expandable expandable) {
227         handleLongClick(expandable);
228     }
229 
230     @Override
getTileLabel()231     public CharSequence getTileLabel() {
232         return mContext.getString(R.string.quick_settings_dnd_label);
233     }
234 
235     @Override
handleUpdateState(BooleanState state, Object arg)236     protected void handleUpdateState(BooleanState state, Object arg) {
237         if (mController == null) return;
238         final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
239         final boolean newValue = zen != ZEN_MODE_OFF;
240         state.dualTarget = true;
241         state.value = newValue;
242         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
243         state.icon = ResourceIcon.get(state.value
244                 ? R.drawable.qs_dnd_icon_on
245                 : R.drawable.qs_dnd_icon_off);
246         state.label = getTileLabel();
247         state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext,
248                 zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
249         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
250         // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier
251         // to understand.
252         switch (zen) {
253             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
254                 state.contentDescription =
255                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", "
256                         + state.secondaryLabel;
257                 break;
258             case Global.ZEN_MODE_NO_INTERRUPTIONS:
259                 state.contentDescription =
260                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
261                         mContext.getString(R.string.accessibility_quick_settings_dnd_none_on)
262                                 + ", " + state.secondaryLabel;
263                 break;
264             case ZEN_MODE_ALARMS:
265                 state.contentDescription =
266                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
267                         mContext.getString(R.string.accessibility_quick_settings_dnd_alarms_on)
268                                 + ", " + state.secondaryLabel;
269                 break;
270             default:
271                 state.contentDescription = mContext.getString(
272                         R.string.accessibility_quick_settings_dnd);
273                 break;
274         }
275         state.dualLabelContentDescription = mContext.getResources().getString(
276                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
277         state.expandedAccessibilityClassName = Switch.class.getName();
278         state.forceExpandIcon =
279                 mSettingZenDuration.getValue() == Settings.Secure.ZEN_DURATION_PROMPT;
280     }
281 
282     @Override
getMetricsCategory()283     public int getMetricsCategory() {
284         return MetricsEvent.QS_DND;
285     }
286 
287     @Override
handleSetListening(boolean listening)288     public void handleSetListening(boolean listening) {
289         super.handleSetListening(listening);
290         if (mListening == listening) return;
291         mListening = listening;
292         if (mListening) {
293             Prefs.registerListener(mContext, mPrefListener);
294         } else {
295             Prefs.unregisterListener(mContext, mPrefListener);
296         }
297         mSettingZenDuration.setListening(listening);
298     }
299 
300     @Override
handleDestroy()301     protected void handleDestroy() {
302         super.handleDestroy();
303         mSettingZenDuration.setListening(false);
304     }
305 
306     @Override
isAvailable()307     public boolean isAvailable() {
308         return isVisible(mSharedPreferences);
309     }
310 
311     private final OnSharedPreferenceChangeListener mPrefListener
312             = new OnSharedPreferenceChangeListener() {
313         @Override
314         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
315                 @Prefs.Key String key) {
316             if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
317                     Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
318                 refreshState();
319             }
320         }
321     };
322 
323     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
324         public void onZenChanged(int zen) {
325             Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile.");
326             refreshState(zen);
327         }
328     };
329 
330 }
331