ZenModePanel.java revision 661f2cf45860d2e10924e6b69966a9afe255f28b
1/*
2 * Copyright (C) 2014 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.volume;
18
19import android.app.ActivityManager;
20import android.content.Context;
21import android.content.Intent;
22import android.content.SharedPreferences;
23import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
24import android.content.res.Resources;
25import android.net.Uri;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.provider.Settings;
30import android.provider.Settings.Global;
31import android.service.notification.Condition;
32import android.service.notification.ZenModeConfig;
33import android.text.TextUtils;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.MathUtils;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.widget.CompoundButton;
40import android.widget.CompoundButton.OnCheckedChangeListener;
41import android.widget.ImageView;
42import android.widget.LinearLayout;
43import android.widget.RadioButton;
44import android.widget.TextView;
45
46import com.android.systemui.R;
47import com.android.systemui.statusbar.policy.ZenModeController;
48
49import java.util.Arrays;
50import java.util.Objects;
51
52public class ZenModePanel extends LinearLayout {
53    private static final String TAG = "ZenModePanel";
54    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
55
56    private static final int SECONDS_MS = 1000;
57    private static final int MINUTES_MS = 60 * SECONDS_MS;
58
59    private static final int[] MINUTE_BUCKETS = DEBUG
60            ? new int[] { 0, 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 }
61            : ZenModeConfig.MINUTE_BUCKETS;
62    private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
63    private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
64    private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
65    private static final int FOREVER_CONDITION_INDEX = 0;
66    private static final int TIME_CONDITION_INDEX = 1;
67    private static final int FIRST_CONDITION_INDEX = 2;
68    private static final long SELECT_DEFAULT_DELAY = 300;
69
70    public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
71
72    private final Context mContext;
73    private final LayoutInflater mInflater;
74    private final H mHandler = new H();
75    private final Prefs mPrefs;
76    private final IconPulser mIconPulser;
77    private final int mSubheadWarningColor;
78    private final int mSubheadColor;
79
80    private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
81
82    private SegmentedButtons mZenButtons;
83    private View mZenSubhead;
84    private TextView mZenSubheadCollapsed;
85    private TextView mZenSubheadExpanded;
86    private View mMoreSettings;
87    private LinearLayout mZenConditions;
88
89    private Callback mCallback;
90    private ZenModeController mController;
91    private boolean mRequestingConditions;
92    private Condition mExitCondition;
93    private String mExitConditionText;
94    private int mBucketIndex = -1;
95    private boolean mExpanded;
96    private boolean mHidden = false;
97    private int mSessionZen;
98    private int mAttachedZen;
99    private Condition mSessionExitCondition;
100    private Condition[] mConditions;
101    private Condition mTimeCondition;
102
103    public ZenModePanel(Context context, AttributeSet attrs) {
104        super(context, attrs);
105        mContext = context;
106        mPrefs = new Prefs();
107        mInflater = LayoutInflater.from(mContext.getApplicationContext());
108        mIconPulser = new IconPulser(mContext);
109        final Resources res = mContext.getResources();
110        mSubheadWarningColor = res.getColor(R.color.system_warning_color);
111        mSubheadColor = res.getColor(R.color.qs_subhead);
112        if (DEBUG) Log.d(mTag, "new ZenModePanel");
113    }
114
115    @Override
116    protected void onFinishInflate() {
117        super.onFinishInflate();
118
119        mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
120        mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none,
121                Global.ZEN_MODE_NO_INTERRUPTIONS);
122        mZenButtons.addButton(R.string.interruption_level_priority, R.drawable.ic_zen_important,
123                Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
124        mZenButtons.addButton(R.string.interruption_level_all, R.drawable.ic_zen_all,
125                Global.ZEN_MODE_OFF);
126        mZenButtons.setCallback(mZenButtonsCallback);
127
128        mZenSubhead = findViewById(R.id.zen_subhead);
129
130        mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed);
131        mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() {
132            @Override
133            public void onClick(View v) {
134                setExpanded(true);
135            }
136        });
137        Interaction.register(mZenSubheadCollapsed, mInteractionCallback);
138
139        mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded);
140        Interaction.register(mZenSubheadExpanded, mInteractionCallback);
141
142        mMoreSettings = findViewById(R.id.zen_more_settings);
143        mMoreSettings.setOnClickListener(new View.OnClickListener() {
144            @Override
145            public void onClick(View v) {
146                fireMoreSettings();
147            }
148        });
149        Interaction.register(mMoreSettings, mInteractionCallback);
150
151        mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
152    }
153
154    @Override
155    protected void onAttachedToWindow() {
156        super.onAttachedToWindow();
157        if (DEBUG) Log.d(mTag, "onAttachedToWindow");
158        mAttachedZen = getSelectedZen(-1);
159        mSessionZen = mAttachedZen;
160        mSessionExitCondition = copy(mExitCondition);
161        refreshExitConditionText();
162        updateWidgets();
163    }
164
165    @Override
166    protected void onDetachedFromWindow() {
167        super.onDetachedFromWindow();
168        if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
169        checkForAttachedZenChange();
170        mAttachedZen = -1;
171        mSessionZen = -1;
172        mSessionExitCondition = null;
173        setExpanded(false);
174    }
175
176    public void setHidden(boolean hidden) {
177        if (mHidden == hidden) return;
178        mHidden = hidden;
179        updateWidgets();
180    }
181
182    private void checkForAttachedZenChange() {
183        final int selectedZen = getSelectedZen(-1);
184        if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen);
185        if (selectedZen != mAttachedZen) {
186            if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen);
187            if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
188                mPrefs.trackNoneSelected();
189            }
190        }
191    }
192
193    private void setExpanded(boolean expanded) {
194        if (expanded == mExpanded) return;
195        mExpanded = expanded;
196        updateWidgets();
197        setRequestingConditions(mExpanded);
198        fireExpanded();
199    }
200
201    /** Start or stop requesting relevant zen mode exit conditions */
202    private void setRequestingConditions(boolean requesting) {
203        if (mRequestingConditions == requesting) return;
204        if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
205        mRequestingConditions = requesting;
206        if (mController != null) {
207            mController.requestConditions(mRequestingConditions);
208        }
209        if (mRequestingConditions) {
210            mTimeCondition = parseExistingTimeCondition(mExitCondition);
211            if (mTimeCondition != null) {
212                mBucketIndex = -1;
213            } else {
214                mBucketIndex = DEFAULT_BUCKET_INDEX;
215                mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
216                        MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
217            }
218            if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
219            mConditions = null; // reset conditions
220            handleUpdateConditions();
221        } else {
222            mZenConditions.removeAllViews();
223        }
224    }
225
226    public void init(ZenModeController controller) {
227        mController = controller;
228        setExitCondition(mController.getExitCondition());
229        refreshExitConditionText();
230        mSessionZen = getSelectedZen(-1);
231        handleUpdateZen(mController.getZen());
232        if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
233        mZenConditions.removeAllViews();
234        mController.addCallback(mZenCallback);
235    }
236
237    public void updateLocale() {
238        mZenButtons.updateLocale();
239    }
240
241    private void setExitCondition(Condition exitCondition) {
242        if (Objects.equals(mExitCondition, exitCondition)) return;
243        mExitCondition = exitCondition;
244        refreshExitConditionText();
245        updateWidgets();
246    }
247
248    private static Uri getConditionId(Condition condition) {
249        return condition != null ? condition.id : null;
250    }
251
252    private static boolean sameConditionId(Condition lhs, Condition rhs) {
253        return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
254    }
255
256    private static Condition copy(Condition condition) {
257        return condition == null ? null : condition.copy();
258    }
259
260    private void refreshExitConditionText() {
261        final String forever = mContext.getString(com.android.internal.R.string.zen_mode_forever);
262        if (mExitCondition == null) {
263            mExitConditionText = forever;
264        } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) {
265            final Condition condition = parseExistingTimeCondition(mExitCondition);
266            mExitConditionText = condition != null ? condition.summary : forever;
267        } else {
268            mExitConditionText = mExitCondition.summary;
269        }
270    }
271
272    public void setCallback(Callback callback) {
273        mCallback = callback;
274    }
275
276    public void showSilentHint() {
277        if (DEBUG) Log.d(mTag, "showSilentHint");
278        if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
279        final View noneButton = mZenButtons.getChildAt(0);
280        mIconPulser.start(noneButton);
281    }
282
283    private void handleUpdateZen(int zen) {
284        if (mSessionZen != -1 && mSessionZen != zen) {
285            setExpanded(zen != Global.ZEN_MODE_OFF);
286            mSessionZen = zen;
287        }
288        mZenButtons.setSelectedValue(zen);
289        updateWidgets();
290    }
291
292    private int getSelectedZen(int defValue) {
293        final Object zen = mZenButtons.getSelectedValue();
294        return zen != null ? (Integer) zen : defValue;
295    }
296
297    private void updateWidgets() {
298        final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
299        final boolean zenOff = zen == Global.ZEN_MODE_OFF;
300        final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
301        final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
302        final boolean expanded = !mHidden && mExpanded;
303
304        mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
305        mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE);
306        mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE);
307        mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
308        mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
309        mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
310
311        if (zenNone) {
312            mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
313            mZenSubheadCollapsed.setText(mExitConditionText);
314        } else if (zenImportant) {
315            mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
316            mZenSubheadCollapsed.setText(mExitConditionText);
317        }
318        mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous()
319                ? mSubheadWarningColor : mSubheadColor);
320    }
321
322    private Condition parseExistingTimeCondition(Condition condition) {
323        if (condition == null) return null;
324        final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
325        if (time == 0) return null;
326        final long now = System.currentTimeMillis();
327        final long span = time - now;
328        if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
329        return ZenModeConfig.toTimeCondition(mContext,
330                time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser());
331    }
332
333    private void handleUpdateConditions(Condition[] conditions) {
334        mConditions = conditions;
335        handleUpdateConditions();
336    }
337
338    private void handleUpdateConditions() {
339        final int conditionCount = mConditions == null ? 0 : mConditions.length;
340        if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount);
341        for (int i = mZenConditions.getChildCount() - 1; i >= FIRST_CONDITION_INDEX; i--) {
342            mZenConditions.removeViewAt(i);
343        }
344        // forever
345        bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
346        // countdown
347        bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
348        // provider conditions
349        boolean foundDowntime = false;
350        for (int i = 0; i < conditionCount; i++) {
351            bind(mConditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i));
352            foundDowntime |= isDowntime(mConditions[i]);
353        }
354        // ensure downtime exists, if active
355        if (isDowntime(mSessionExitCondition) && !foundDowntime) {
356            bind(mSessionExitCondition, null);
357        }
358        // ensure something is selected, after waiting for providers to respond
359        mHandler.removeMessages(H.SELECT_DEFAULT);
360        mHandler.sendEmptyMessageDelayed(H.SELECT_DEFAULT, SELECT_DEFAULT_DELAY);
361    }
362
363    private static boolean isDowntime(Condition c) {
364        return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c));
365    }
366
367    private ConditionTag getConditionTagAt(int index) {
368        return (ConditionTag) mZenConditions.getChildAt(index).getTag();
369    }
370
371    private void handleSelectDefault() {
372        if (!mExpanded) return;
373        // are we left without anything selected?  if so, set a default
374        for (int i = 0; i < mZenConditions.getChildCount(); i++) {
375            if (getConditionTagAt(i).rb.isChecked()) {
376                if (DEBUG) Log.d(mTag, "Not selecting a default, checked="
377                        + getConditionTagAt(i).condition);
378                return;
379            }
380        }
381        if (DEBUG) Log.d(mTag, "Selecting a default");
382        final int favoriteIndex = mPrefs.getMinuteIndex();
383        if (favoriteIndex == -1) {
384            getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
385        } else {
386            mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
387                    MINUTE_BUCKETS[favoriteIndex], ActivityManager.getCurrentUser());
388            mBucketIndex = favoriteIndex;
389            bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
390            getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true);
391        }
392    }
393
394    private void handleExitConditionChanged(Condition exitCondition) {
395        setExitCondition(exitCondition);
396        if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
397        final int N = mZenConditions.getChildCount();
398        for (int i = 0; i < N; i++) {
399            final ConditionTag tag = getConditionTagAt(i);
400            tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition));
401        }
402    }
403
404    private void bind(final Condition condition, View convertView) {
405        final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE;
406        final View row;
407        if (convertView == null) {
408            row = mInflater.inflate(R.layout.zen_mode_condition, this, false);
409            if (DEBUG) Log.d(mTag, "Adding new condition view for: " + condition);
410            mZenConditions.addView(row);
411        } else {
412            row = convertView;
413        }
414        final ConditionTag tag =
415                row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
416        row.setTag(tag);
417        if (tag.rb == null) {
418            tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox);
419        }
420        tag.condition = condition;
421        tag.rb.setEnabled(enabled);
422        if (mSessionExitCondition != null
423                && sameConditionId(mSessionExitCondition, tag.condition)) {
424            tag.rb.setChecked(true);
425        }
426        tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
427            @Override
428            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
429                if (mExpanded && isChecked) {
430                    if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition);
431                    final int N = mZenConditions.getChildCount();
432                    for (int i = 0; i < N; i++) {
433                        ConditionTag childTag = getConditionTagAt(i);
434                        if (childTag == tag) continue;
435                        childTag.rb.setChecked(false);
436                    }
437                    select(tag.condition);
438                    announceConditionSelection(tag);
439                }
440            }
441        });
442
443        if (tag.lines == null) {
444            tag.lines = row.findViewById(android.R.id.content);
445        }
446        if (tag.line1 == null) {
447            tag.line1 = (TextView) row.findViewById(android.R.id.text1);
448        }
449        if (tag.line2 == null) {
450            tag.line2 = (TextView) row.findViewById(android.R.id.text2);
451        }
452        final String line1, line2;
453        if (condition == null) {
454            line1 = mContext.getString(com.android.internal.R.string.zen_mode_forever);
455            line2 = null;
456        } else {
457            line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1 : condition.summary;
458            line2 = condition.line2;
459        }
460        tag.line1.setText(line1);
461        if (TextUtils.isEmpty(line2)) {
462            tag.line2.setVisibility(GONE);
463        } else {
464            tag.line2.setVisibility(VISIBLE);
465            tag.line2.setText(line2);
466        }
467        tag.lines.setEnabled(enabled);
468        tag.lines.setAlpha(enabled ? 1 : .4f);
469
470        final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
471        button1.setOnClickListener(new OnClickListener() {
472            @Override
473            public void onClick(View v) {
474                onClickTimeButton(row, tag, false /*down*/);
475            }
476        });
477
478        final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
479        button2.setOnClickListener(new OnClickListener() {
480            @Override
481            public void onClick(View v) {
482                onClickTimeButton(row, tag, true /*up*/);
483            }
484        });
485        tag.lines.setOnClickListener(new OnClickListener() {
486            @Override
487            public void onClick(View v) {
488                tag.rb.setChecked(true);
489            }
490        });
491
492        final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition));
493        if (time > 0) {
494            if (mBucketIndex > -1) {
495                button1.setEnabled(mBucketIndex > 0);
496                button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
497            } else {
498                final long span = time - System.currentTimeMillis();
499                button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
500                final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
501                        MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
502                button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
503            }
504
505            button1.setAlpha(button1.isEnabled() ? 1f : .5f);
506            button2.setAlpha(button2.isEnabled() ? 1f : .5f);
507        } else {
508            button1.setVisibility(View.GONE);
509            button2.setVisibility(View.GONE);
510        }
511        // wire up interaction callbacks for newly-added condition rows
512        if (convertView == null) {
513            Interaction.register(tag.rb, mInteractionCallback);
514            Interaction.register(tag.lines, mInteractionCallback);
515            Interaction.register(button1, mInteractionCallback);
516            Interaction.register(button2, mInteractionCallback);
517        }
518    }
519
520    private void announceConditionSelection(ConditionTag tag) {
521        final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
522        String modeText;
523        switch(zen) {
524            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
525                modeText = mContext.getString(R.string.zen_important_interruptions);
526                break;
527            case Global.ZEN_MODE_NO_INTERRUPTIONS:
528                modeText = mContext.getString(R.string.zen_no_interruptions);
529                break;
530             default:
531                return;
532        }
533        announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText,
534                tag.line1.getText()));
535    }
536
537    private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
538        Condition newCondition = null;
539        final int N = MINUTE_BUCKETS.length;
540        if (mBucketIndex == -1) {
541            // not on a known index, search for the next or prev bucket by time
542            final Uri conditionId = getConditionId(tag.condition);
543            final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
544            final long now = System.currentTimeMillis();
545            for (int i = 0; i < N; i++) {
546                int j = up ? i : N - 1 - i;
547                final int bucketMinutes = MINUTE_BUCKETS[j];
548                final long bucketTime = now + bucketMinutes * MINUTES_MS;
549                if (up && bucketTime > time || !up && bucketTime < time) {
550                    mBucketIndex = j;
551                    newCondition = ZenModeConfig.toTimeCondition(mContext,
552                            bucketTime, bucketMinutes, now, ActivityManager.getCurrentUser());
553                    break;
554                }
555            }
556            if (newCondition == null) {
557                mBucketIndex = DEFAULT_BUCKET_INDEX;
558                newCondition = ZenModeConfig.toTimeCondition(mContext,
559                        MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
560            }
561        } else {
562            // on a known index, simply increment or decrement
563            mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
564            newCondition = ZenModeConfig.toTimeCondition(mContext,
565                    MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
566        }
567        mTimeCondition = newCondition;
568        bind(mTimeCondition, row);
569        tag.rb.setChecked(true);
570        select(mTimeCondition);
571        announceConditionSelection(tag);
572    }
573
574    private void select(Condition condition) {
575        if (DEBUG) Log.d(mTag, "select " + condition);
576        if (mController != null) {
577            mController.setExitCondition(condition);
578        }
579        setExitCondition(condition);
580        if (condition == null) {
581            mPrefs.setMinuteIndex(-1);
582        } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) {
583            mPrefs.setMinuteIndex(mBucketIndex);
584        }
585        mSessionExitCondition = copy(condition);
586    }
587
588    private void fireMoreSettings() {
589        if (mCallback != null) {
590            mCallback.onMoreSettings();
591        }
592    }
593
594    private void fireInteraction() {
595        if (mCallback != null) {
596            mCallback.onInteraction();
597        }
598    }
599
600    private void fireExpanded() {
601        if (mCallback != null) {
602            mCallback.onExpanded(mExpanded);
603        }
604    }
605
606    private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
607        @Override
608        public void onZenChanged(int zen) {
609            mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
610        }
611        @Override
612        public void onConditionsChanged(Condition[] conditions) {
613            mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
614        }
615
616        @Override
617        public void onExitConditionChanged(Condition exitCondition) {
618            mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
619        }
620    };
621
622    private final class H extends Handler {
623        private static final int UPDATE_CONDITIONS = 1;
624        private static final int EXIT_CONDITION_CHANGED = 2;
625        private static final int UPDATE_ZEN = 3;
626        private static final int SELECT_DEFAULT = 4;
627
628        private H() {
629            super(Looper.getMainLooper());
630        }
631
632        @Override
633        public void handleMessage(Message msg) {
634            if (msg.what == UPDATE_CONDITIONS) {
635                handleUpdateConditions((Condition[]) msg.obj);
636            } else if (msg.what == EXIT_CONDITION_CHANGED) {
637                handleExitConditionChanged((Condition) msg.obj);
638            } else if (msg.what == UPDATE_ZEN) {
639                handleUpdateZen(msg.arg1);
640            } else if (msg.what == SELECT_DEFAULT) {
641                handleSelectDefault();
642            }
643        }
644    }
645
646    public interface Callback {
647        void onMoreSettings();
648        void onInteraction();
649        void onExpanded(boolean expanded);
650    }
651
652    // used as the view tag on condition rows
653    private static class ConditionTag {
654        RadioButton rb;
655        View lines;
656        TextView line1;
657        TextView line2;
658        Condition condition;
659    }
660
661    private final class Prefs implements OnSharedPreferenceChangeListener {
662        private static final String KEY_MINUTE_INDEX = "minuteIndex";
663        private static final String KEY_NONE_SELECTED = "noneSelected";
664
665        private final int mNoneDangerousThreshold;
666
667        private int mMinuteIndex;
668        private int mNoneSelected;
669
670        private Prefs() {
671            mNoneDangerousThreshold = mContext.getResources()
672                    .getInteger(R.integer.zen_mode_alarm_warning_threshold);
673            prefs().registerOnSharedPreferenceChangeListener(this);
674            updateMinuteIndex();
675            updateNoneSelected();
676        }
677
678        public boolean isNoneDangerous() {
679            return mNoneSelected < mNoneDangerousThreshold;
680        }
681
682        public void trackNoneSelected() {
683            mNoneSelected = clampNoneSelected(mNoneSelected + 1);
684            if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
685                    + mNoneDangerousThreshold);
686            prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply();
687        }
688
689        public int getMinuteIndex() {
690            return mMinuteIndex;
691        }
692
693        public void setMinuteIndex(int minuteIndex) {
694            minuteIndex = clampIndex(minuteIndex);
695            if (minuteIndex == mMinuteIndex) return;
696            mMinuteIndex = clampIndex(minuteIndex);
697            if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
698            prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply();
699        }
700
701        @Override
702        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
703            updateMinuteIndex();
704            updateNoneSelected();
705        }
706
707        private SharedPreferences prefs() {
708            return mContext.getSharedPreferences(mContext.getPackageName(), 0);
709        }
710
711        private void updateMinuteIndex() {
712            mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
713            if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
714        }
715
716        private int clampIndex(int index) {
717            return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
718        }
719
720        private void updateNoneSelected() {
721            mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0));
722            if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
723        }
724
725        private int clampNoneSelected(int noneSelected) {
726            return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
727        }
728    }
729
730    private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
731        @Override
732        public void onSelected(Object value) {
733            if (value != null && mZenButtons.isShown()) {
734                if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
735                mController.setZen((Integer) value);
736            }
737        }
738
739        @Override
740        public void onInteraction() {
741            fireInteraction();
742        }
743    };
744
745    private final Interaction.Callback mInteractionCallback = new Interaction.Callback() {
746        @Override
747        public void onInteraction() {
748            fireInteraction();
749        }
750    };
751}
752