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