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