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