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.view.View;
39 import android.widget.Switch;
40 
41 import androidx.annotation.Nullable;
42 
43 import com.android.internal.jank.InteractionJankMonitor;
44 import com.android.internal.logging.MetricsLogger;
45 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
46 import com.android.settingslib.notification.EnableZenModeDialog;
47 import com.android.systemui.Prefs;
48 import com.android.systemui.R;
49 import com.android.systemui.animation.DialogCuj;
50 import com.android.systemui.animation.DialogLaunchAnimator;
51 import com.android.systemui.dagger.qualifiers.Background;
52 import com.android.systemui.dagger.qualifiers.Main;
53 import com.android.systemui.plugins.ActivityStarter;
54 import com.android.systemui.plugins.FalsingManager;
55 import com.android.systemui.plugins.qs.QSTile.BooleanState;
56 import com.android.systemui.plugins.statusbar.StatusBarStateController;
57 import com.android.systemui.qs.QSHost;
58 import com.android.systemui.qs.QsEventLogger;
59 import com.android.systemui.qs.SettingObserver;
60 import com.android.systemui.qs.logging.QSLogger;
61 import com.android.systemui.qs.tileimpl.QSTileImpl;
62 import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
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 SettingObserver mSettingZenDuration;
85     private final DialogLaunchAnimator mDialogLaunchAnimator;
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, DialogLaunchAnimator dialogLaunchAnimator )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             DialogLaunchAnimator dialogLaunchAnimator
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         mDialogLaunchAnimator = dialogLaunchAnimator;
112         mSettingZenDuration = new SettingObserver(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 View view)150     protected void handleClick(@Nullable View view) {
151         // Zen is currently on
152         if (mState.value) {
153             mController.setZen(ZEN_MODE_OFF, null, TAG);
154         } else {
155             enableZenMode(view);
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 View view)165     private void enableZenMode(@Nullable View view) {
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 (view != null) {
187                             mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
188                                             InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
189                                             INTERACTION_JANK_TAG),
190                                     /* animateBackgroundBoundsChange= */ false);
191                         } else {
192                             dialog.show();
193                         }
194                     });
195                     break;
196                 case Settings.Secure.ZEN_DURATION_FOREVER:
197                     mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
198                     break;
199                 default:
200                     Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
201                             mHost.getUserId(), true).id;
202                     mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
203                             conditionId, TAG);
204             }
205         }
206     }
207 
makeZenModeDialog()208     private Dialog makeZenModeDialog() {
209         AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
210                 true /* cancelIsNeutral */,
211                 mQSZenDialogMetricsLogger).createDialog();
212         SystemUIDialog.applyFlags(dialog);
213         SystemUIDialog.setShowForAllUsers(dialog, true);
214         SystemUIDialog.registerDismissListener(dialog);
215         SystemUIDialog.setDialogSize(dialog);
216         return dialog;
217     }
218 
219     @Override
handleSecondaryClick(@ullable View view)220     protected void handleSecondaryClick(@Nullable View view) {
221         handleLongClick(view);
222     }
223 
224     @Override
getTileLabel()225     public CharSequence getTileLabel() {
226         return mContext.getString(R.string.quick_settings_dnd_label);
227     }
228 
229     @Override
handleUpdateState(BooleanState state, Object arg)230     protected void handleUpdateState(BooleanState state, Object arg) {
231         if (mController == null) return;
232         final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
233         final boolean newValue = zen != ZEN_MODE_OFF;
234         state.dualTarget = true;
235         state.value = newValue;
236         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
237         state.icon = ResourceIcon.get(state.value
238                 ? R.drawable.qs_dnd_icon_on
239                 : R.drawable.qs_dnd_icon_off);
240         state.label = getTileLabel();
241         state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext,
242                 zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
243         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
244         // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier
245         // to understand.
246         switch (zen) {
247             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
248                 state.contentDescription =
249                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", "
250                         + state.secondaryLabel;
251                 break;
252             case Global.ZEN_MODE_NO_INTERRUPTIONS:
253                 state.contentDescription =
254                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
255                         mContext.getString(R.string.accessibility_quick_settings_dnd_none_on)
256                                 + ", " + state.secondaryLabel;
257                 break;
258             case ZEN_MODE_ALARMS:
259                 state.contentDescription =
260                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
261                         mContext.getString(R.string.accessibility_quick_settings_dnd_alarms_on)
262                                 + ", " + state.secondaryLabel;
263                 break;
264             default:
265                 state.contentDescription = mContext.getString(
266                         R.string.accessibility_quick_settings_dnd);
267                 break;
268         }
269         state.dualLabelContentDescription = mContext.getResources().getString(
270                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
271         state.expandedAccessibilityClassName = Switch.class.getName();
272         state.forceExpandIcon =
273                 mSettingZenDuration.getValue() == Settings.Secure.ZEN_DURATION_PROMPT;
274     }
275 
276     @Override
getMetricsCategory()277     public int getMetricsCategory() {
278         return MetricsEvent.QS_DND;
279     }
280 
281     @Override
handleSetListening(boolean listening)282     public void handleSetListening(boolean listening) {
283         super.handleSetListening(listening);
284         if (mListening == listening) return;
285         mListening = listening;
286         if (mListening) {
287             Prefs.registerListener(mContext, mPrefListener);
288         } else {
289             Prefs.unregisterListener(mContext, mPrefListener);
290         }
291         mSettingZenDuration.setListening(listening);
292     }
293 
294     @Override
handleDestroy()295     protected void handleDestroy() {
296         super.handleDestroy();
297         mSettingZenDuration.setListening(false);
298     }
299 
300     @Override
isAvailable()301     public boolean isAvailable() {
302         return isVisible(mSharedPreferences);
303     }
304 
305     private final OnSharedPreferenceChangeListener mPrefListener
306             = new OnSharedPreferenceChangeListener() {
307         @Override
308         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
309                 @Prefs.Key String key) {
310             if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
311                     Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
312                 refreshState();
313             }
314         }
315     };
316 
317     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
318         public void onZenChanged(int zen) {
319             Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile.");
320             refreshState(zen);
321         }
322     };
323 
324 }
325