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.ActivityManager;
23 import android.app.Dialog;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.SharedPreferences;
29 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
30 import android.content.pm.ApplicationInfo;
31 import android.content.pm.PackageManager;
32 import android.net.Uri;
33 import android.os.UserManager;
34 import android.provider.Settings;
35 import android.provider.Settings.Global;
36 import android.service.notification.ZenModeConfig;
37 import android.service.notification.ZenModeConfig.ZenRule;
38 import android.service.quicksettings.Tile;
39 import android.text.TextUtils;
40 import android.util.Slog;
41 import android.view.LayoutInflater;
42 import android.view.View;
43 import android.view.View.OnAttachStateChangeListener;
44 import android.view.ViewGroup;
45 import android.view.WindowManager;
46 import android.widget.Switch;
47 import android.widget.Toast;
48 
49 import com.android.internal.logging.MetricsLogger;
50 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
51 import com.android.settingslib.notification.EnableZenModeDialog;
52 import com.android.systemui.Dependency;
53 import com.android.systemui.Prefs;
54 import com.android.systemui.R;
55 import com.android.systemui.SysUIToast;
56 import com.android.systemui.plugins.ActivityStarter;
57 import com.android.systemui.plugins.qs.DetailAdapter;
58 import com.android.systemui.plugins.qs.QSTile.BooleanState;
59 import com.android.systemui.qs.QSHost;
60 import com.android.systemui.qs.tileimpl.QSTileImpl;
61 import com.android.systemui.statusbar.phone.SystemUIDialog;
62 import com.android.systemui.statusbar.policy.ZenModeController;
63 import com.android.systemui.volume.ZenModePanel;
64 
65 /** Quick settings tile: Do not disturb **/
66 public class DndTile extends QSTileImpl<BooleanState> {
67 
68     private static final Intent ZEN_SETTINGS =
69             new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
70 
71     private static final Intent ZEN_PRIORITY_SETTINGS =
72             new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
73 
74     private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
75     private static final String EXTRA_VISIBLE = "visible";
76 
77     private final ZenModeController mController;
78     private final DndDetailAdapter mDetailAdapter;
79 
80     private boolean mListening;
81     private boolean mShowingDetail;
82     private boolean mReceiverRegistered;
83 
DndTile(QSHost host)84     public DndTile(QSHost host) {
85         super(host);
86         mController = Dependency.get(ZenModeController.class);
87         mDetailAdapter = new DndDetailAdapter();
88         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
89         mReceiverRegistered = true;
90     }
91 
92     @Override
handleDestroy()93     protected void handleDestroy() {
94         super.handleDestroy();
95         if (mReceiverRegistered) {
96             mContext.unregisterReceiver(mReceiver);
97             mReceiverRegistered = false;
98         }
99     }
100 
setVisible(Context context, boolean visible)101     public static void setVisible(Context context, boolean visible) {
102         Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
103     }
104 
isVisible(Context context)105     public static boolean isVisible(Context context) {
106         return Prefs.getBoolean(context, Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
107     }
108 
setCombinedIcon(Context context, boolean combined)109     public static void setCombinedIcon(Context context, boolean combined) {
110         Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
111     }
112 
isCombinedIcon(Context context)113     public static boolean isCombinedIcon(Context context) {
114         return Prefs.getBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON,
115                 false /* defaultValue */);
116     }
117 
118     @Override
getDetailAdapter()119     public DetailAdapter getDetailAdapter() {
120         return mDetailAdapter;
121     }
122 
123     @Override
newTileState()124     public BooleanState newTileState() {
125         return new BooleanState();
126     }
127 
128     @Override
getLongClickIntent()129     public Intent getLongClickIntent() {
130         return ZEN_SETTINGS;
131     }
132 
133     @Override
handleClick()134     protected void handleClick() {
135         // Zen is currently on
136         if (mState.value) {
137             mController.setZen(ZEN_MODE_OFF, null, TAG);
138         } else {
139             showDetail(true);
140         }
141     }
142 
143     @Override
showDetail(boolean show)144     public void showDetail(boolean show) {
145         int zenDuration = Settings.Global.getInt(mContext.getContentResolver(),
146                 Settings.Global.ZEN_DURATION, 0);
147         boolean showOnboarding = Settings.Global.getInt(mContext.getContentResolver(),
148                 Settings.Global.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0;
149         if (showOnboarding) {
150             // don't show on-boarding again or notification ever
151             Settings.Global.putInt(mContext.getContentResolver(),
152                     Global.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
153             // turn on DND
154             mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
155             // show on-boarding screen
156             Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
157             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
158             Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
159         } else {
160             switch (zenDuration) {
161                 case Settings.Global.ZEN_DURATION_PROMPT:
162                     mUiHandler.post(() -> {
163                         Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
164                         mDialog.getWindow().setType(
165                                 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
166                         SystemUIDialog.setShowForAllUsers(mDialog, true);
167                         SystemUIDialog.registerDismissListener(mDialog);
168                         SystemUIDialog.setWindowOnTop(mDialog);
169                         mUiHandler.post(() -> mDialog.show());
170                         mHost.collapsePanels();
171                     });
172                     break;
173                 case Settings.Global.ZEN_DURATION_FOREVER:
174                     mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
175                     break;
176                 default:
177                     Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
178                             ActivityManager.getCurrentUser(), true).id;
179                     mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
180                             conditionId, TAG);
181             }
182         }
183     }
184 
185     @Override
handleSecondaryClick()186     protected void handleSecondaryClick() {
187         if (mController.isVolumeRestricted()) {
188             // Collapse the panels, so the user can see the toast.
189             mHost.collapsePanels();
190             SysUIToast.makeText(mContext, mContext.getString(
191                     com.android.internal.R.string.error_message_change_not_allowed),
192                     Toast.LENGTH_LONG).show();
193             return;
194         }
195         if (!mState.value) {
196             // Because of the complexity of the zen panel, it needs to be shown after
197             // we turn on zen below.
198             mController.addCallback(new ZenModeController.Callback() {
199                 @Override
200                 public void onZenChanged(int zen) {
201                     mController.removeCallback(this);
202                     showDetail(true);
203                 }
204             });
205             mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
206         } else {
207             showDetail(true);
208         }
209     }
210 
211     @Override
getTileLabel()212     public CharSequence getTileLabel() {
213         return mContext.getString(R.string.quick_settings_dnd_label);
214     }
215 
216     @Override
handleUpdateState(BooleanState state, Object arg)217     protected void handleUpdateState(BooleanState state, Object arg) {
218         final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
219         final boolean newValue = zen != ZEN_MODE_OFF;
220         final boolean valueChanged = state.value != newValue;
221         if (state.slash == null) state.slash = new SlashState();
222         state.dualTarget = true;
223         state.value = newValue;
224         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
225         state.slash.isSlashed = !state.value;
226         state.label = getTileLabel();
227         state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext,
228                 zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
229         state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
230         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
231         switch (zen) {
232             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
233                 state.contentDescription =
234                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", "
235                         + state.secondaryLabel;
236                 break;
237             case Global.ZEN_MODE_NO_INTERRUPTIONS:
238                 state.contentDescription =
239                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
240                         mContext.getString(R.string.accessibility_quick_settings_dnd_none_on)
241                                 + ", " + state.secondaryLabel;
242                 break;
243             case ZEN_MODE_ALARMS:
244                 state.contentDescription =
245                         mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
246                         mContext.getString(R.string.accessibility_quick_settings_dnd_alarms_on)
247                                 + ", " + state.secondaryLabel;
248                 break;
249             default:
250                 state.contentDescription = mContext.getString(
251                         R.string.accessibility_quick_settings_dnd);
252                 break;
253         }
254         if (valueChanged) {
255             fireToggleStateChanged(state.value);
256         }
257         state.dualLabelContentDescription = mContext.getResources().getString(
258                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
259         state.expandedAccessibilityClassName = Switch.class.getName();
260     }
261 
262     @Override
getMetricsCategory()263     public int getMetricsCategory() {
264         return MetricsEvent.QS_DND;
265     }
266 
267     @Override
composeChangeAnnouncement()268     protected String composeChangeAnnouncement() {
269         if (mState.value) {
270             return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on);
271         } else {
272             return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_off);
273         }
274     }
275 
276     @Override
handleSetListening(boolean listening)277     public void handleSetListening(boolean listening) {
278         if (mListening == listening) return;
279         mListening = listening;
280         if (mListening) {
281             mController.addCallback(mZenCallback);
282             Prefs.registerListener(mContext, mPrefListener);
283         } else {
284             mController.removeCallback(mZenCallback);
285             Prefs.unregisterListener(mContext, mPrefListener);
286         }
287     }
288 
289     @Override
isAvailable()290     public boolean isAvailable() {
291         return isVisible(mContext);
292     }
293 
294     private final OnSharedPreferenceChangeListener mPrefListener
295             = new OnSharedPreferenceChangeListener() {
296         @Override
297         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
298                 @Prefs.Key String key) {
299             if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
300                     Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
301                 refreshState();
302             }
303         }
304     };
305 
306     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
307         public void onZenChanged(int zen) {
308             refreshState(zen);
309             if (isShowingDetail()) {
310                 mDetailAdapter.updatePanel();
311             }
312         }
313 
314         @Override
315         public void onConfigChanged(ZenModeConfig config) {
316             if (isShowingDetail()) {
317                 mDetailAdapter.updatePanel();
318             }
319         }
320     };
321 
322     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
323         @Override
324         public void onReceive(Context context, Intent intent) {
325             final boolean visible = intent.getBooleanExtra(EXTRA_VISIBLE, false);
326             setVisible(mContext, visible);
327             refreshState();
328         }
329     };
330 
331     private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {
332 
333         private ZenModePanel mZenPanel;
334         private boolean mAuto;
335 
336         @Override
getTitle()337         public CharSequence getTitle() {
338             return mContext.getString(R.string.quick_settings_dnd_label);
339         }
340 
341         @Override
getToggleState()342         public Boolean getToggleState() {
343             return mState.value;
344         }
345 
346         @Override
getSettingsIntent()347         public Intent getSettingsIntent() {
348             return ZEN_SETTINGS;
349         }
350 
351         @Override
setToggleState(boolean state)352         public void setToggleState(boolean state) {
353             MetricsLogger.action(mContext, MetricsEvent.QS_DND_TOGGLE, state);
354             if (!state) {
355                 mController.setZen(ZEN_MODE_OFF, null, TAG);
356                 mAuto = false;
357             } else {
358                 mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
359             }
360         }
361 
362         @Override
getMetricsCategory()363         public int getMetricsCategory() {
364             return MetricsEvent.QS_DND_DETAILS;
365         }
366 
367         @Override
createDetailView(Context context, View convertView, ViewGroup parent)368         public View createDetailView(Context context, View convertView, ViewGroup parent) {
369             mZenPanel = convertView != null ? (ZenModePanel) convertView
370                     : (ZenModePanel) LayoutInflater.from(context).inflate(
371                             R.layout.zen_mode_panel, parent, false);
372             if (convertView == null) {
373                 mZenPanel.init(mController);
374                 mZenPanel.addOnAttachStateChangeListener(this);
375                 mZenPanel.setCallback(mZenModePanelCallback);
376                 mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_detail_empty, R.string.dnd_is_off);
377             }
378             updatePanel();
379             return mZenPanel;
380         }
381 
updatePanel()382         private void updatePanel() {
383             if (mZenPanel == null) return;
384             mAuto = false;
385             if (mController.getZen() == ZEN_MODE_OFF) {
386                 mZenPanel.setState(ZenModePanel.STATE_OFF);
387             } else {
388                 ZenModeConfig config = mController.getConfig();
389                 String summary = "";
390                 if (config.manualRule != null && config.manualRule.enabler != null) {
391                     summary = getOwnerCaption(config.manualRule.enabler);
392                 }
393                 for (ZenRule automaticRule : config.automaticRules.values()) {
394                     if (automaticRule.isAutomaticActive()) {
395                         if (summary.isEmpty()) {
396                             summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule,
397                                     automaticRule.name);
398                         } else {
399                             summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule_app);
400                         }
401                     }
402                 }
403                 if (summary.isEmpty()) {
404                     mZenPanel.setState(ZenModePanel.STATE_MODIFY);
405                 } else {
406                     mAuto = true;
407                     mZenPanel.setState(ZenModePanel.STATE_AUTO_RULE);
408                     mZenPanel.setAutoText(summary);
409                 }
410             }
411         }
412 
getOwnerCaption(String owner)413         private String getOwnerCaption(String owner) {
414             final PackageManager pm = mContext.getPackageManager();
415             try {
416                 final ApplicationInfo info = pm.getApplicationInfo(owner, 0);
417                 if (info != null) {
418                     final CharSequence seq = info.loadLabel(pm);
419                     if (seq != null) {
420                         final String str = seq.toString().trim();
421                         return mContext.getString(R.string.qs_dnd_prompt_app, str);
422                     }
423                 }
424             } catch (Throwable e) {
425                 Slog.w(TAG, "Error loading owner caption", e);
426             }
427             return "";
428         }
429 
430         @Override
onViewAttachedToWindow(View v)431         public void onViewAttachedToWindow(View v) {
432             mShowingDetail = true;
433         }
434 
435         @Override
onViewDetachedFromWindow(View v)436         public void onViewDetachedFromWindow(View v) {
437             mShowingDetail = false;
438             mZenPanel = null;
439         }
440     }
441 
442     private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
443         @Override
444         public void onPrioritySettings() {
445             Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
446                     ZEN_PRIORITY_SETTINGS, 0);
447         }
448 
449         @Override
450         public void onInteraction() {
451             // noop
452         }
453 
454         @Override
455         public void onExpanded(boolean expanded) {
456             // noop
457         }
458     };
459 
460 }
461