ZenModePanel.java revision 4dd81467e33a694138da6916fc68ca79501a9429
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.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.content.Context;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
25import android.content.res.Resources;
26import android.net.Uri;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.provider.Settings;
31import android.provider.Settings.Global;
32import android.service.notification.Condition;
33import android.service.notification.ZenModeConfig;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.MathUtils;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.animation.AnimationUtils;
40import android.view.animation.Interpolator;
41import android.widget.CompoundButton;
42import android.widget.CompoundButton.OnCheckedChangeListener;
43import android.widget.ImageView;
44import android.widget.LinearLayout;
45import android.widget.RadioButton;
46import android.widget.TextView;
47
48import com.android.systemui.R;
49import com.android.systemui.statusbar.policy.ZenModeController;
50
51import java.util.Arrays;
52import java.util.Objects;
53
54public class ZenModePanel extends LinearLayout {
55    private static final String TAG = "ZenModePanel";
56    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
57
58    private static final int SECONDS_MS = 1000;
59    private static final int MINUTES_MS = 60 * SECONDS_MS;
60
61    private static final int[] MINUTE_BUCKETS = DEBUG
62            ? new int[] { 0, 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 }
63            : ZenModeConfig.MINUTE_BUCKETS;
64    private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
65    private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
66    private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
67    private static final int FOREVER_CONDITION_INDEX = 0;
68    private static final int TIME_CONDITION_INDEX = 1;
69    private static final int FIRST_CONDITION_INDEX = 2;
70    private static final float SILENT_HINT_PULSE_SCALE = 1.1f;
71
72    public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
73
74    private final Context mContext;
75    private final LayoutInflater mInflater;
76    private final H mHandler = new H();
77    private final Prefs mPrefs;
78    private final Interpolator mFastOutSlowInInterpolator;
79    private final int mSubheadWarningColor;
80    private final int mSubheadColor;
81
82    private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
83
84    private SegmentedButtons mZenButtons;
85    private View mZenSubhead;
86    private TextView mZenSubheadCollapsed;
87    private TextView mZenSubheadExpanded;
88    private View mMoreSettings;
89    private LinearLayout mZenConditions;
90
91    private Callback mCallback;
92    private ZenModeController mController;
93    private boolean mRequestingConditions;
94    private Condition mExitCondition;
95    private String mExitConditionText;
96    private int mBucketIndex = -1;
97    private boolean mExpanded;
98    private boolean mHidden = false;
99    private int mSessionZen;
100    private int mAttachedZen;
101    private Condition mSessionExitCondition;
102    private Condition[] mConditions;
103    private Condition mTimeCondition;
104
105    public ZenModePanel(Context context, AttributeSet attrs) {
106        super(context, attrs);
107        mContext = context;
108        mPrefs = new Prefs();
109        mInflater = LayoutInflater.from(mContext.getApplicationContext());
110        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
111                android.R.interpolator.fast_out_slow_in);
112        final Resources res = mContext.getResources();
113        mSubheadWarningColor = res.getColor(R.color.system_warning_color);
114        mSubheadColor = res.getColor(R.color.qs_subhead);
115        if (DEBUG) Log.d(mTag, "new ZenModePanel");
116    }
117
118    @Override
119    protected void onFinishInflate() {
120        super.onFinishInflate();
121
122        mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
123        mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS);
124        mZenButtons.addButton(R.string.interruption_level_priority,
125                Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
126        mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
127        mZenButtons.setCallback(mZenButtonsCallback);
128
129        mZenSubhead = findViewById(R.id.zen_subhead);
130
131        mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed);
132        mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() {
133            @Override
134            public void onClick(View v) {
135                setExpanded(true);
136                fireInteraction();
137            }
138        });
139
140        mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded);
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                fireInteraction();
148            }
149        });
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(MINUTE_BUCKETS[mBucketIndex]);
216            }
217            if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
218            mConditions = null; // reset conditions
219            handleUpdateConditions();
220        } else {
221            mZenConditions.removeAllViews();
222        }
223    }
224
225    public void init(ZenModeController controller) {
226        mController = controller;
227        setExitCondition(mController.getExitCondition());
228        refreshExitConditionText();
229        mSessionZen = getSelectedZen(-1);
230        handleUpdateZen(mController.getZen());
231        if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
232        mZenConditions.removeAllViews();
233        mController.addCallback(mZenCallback);
234    }
235
236    private void setExitCondition(Condition exitCondition) {
237        if (sameConditionId(mExitCondition, exitCondition)) return;
238        mExitCondition = exitCondition;
239        refreshExitConditionText();
240        updateWidgets();
241    }
242
243    private static Uri getConditionId(Condition condition) {
244        return condition != null ? condition.id : null;
245    }
246
247    private static boolean sameConditionId(Condition lhs, Condition rhs) {
248        return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
249    }
250
251    private static Condition copy(Condition condition) {
252        return condition == null ? null : condition.copy();
253    }
254
255    private void refreshExitConditionText() {
256        final String forever = mContext.getString(com.android.internal.R.string.zen_mode_forever);
257        if (mExitCondition == null) {
258            mExitConditionText = forever;
259        } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) {
260            final Condition condition = parseExistingTimeCondition(mExitCondition);
261            mExitConditionText = condition != null ? condition.summary : forever;
262        } else {
263            mExitConditionText = mExitCondition.summary;
264        }
265    }
266
267    public void setCallback(Callback callback) {
268        mCallback = callback;
269    }
270
271    public void showSilentHint() {
272        if (DEBUG) Log.d(mTag, "showSilentHint");
273        if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
274        final View noneButton = mZenButtons.getChildAt(0);
275        if (noneButton.getScaleX() != 1) return;  // already running
276        noneButton.animate().cancel();
277        noneButton.animate().scaleX(SILENT_HINT_PULSE_SCALE).scaleY(SILENT_HINT_PULSE_SCALE)
278                .setInterpolator(mFastOutSlowInInterpolator)
279                .setListener(new AnimatorListenerAdapter() {
280                    @Override
281                    public void onAnimationEnd(Animator animation) {
282                        noneButton.animate().scaleX(1).scaleY(1).setListener(null);
283                    }
284                });
285    }
286
287    private void handleUpdateZen(int zen) {
288        if (mSessionZen != -1 && mSessionZen != zen) {
289            setExpanded(zen != Global.ZEN_MODE_OFF);
290            mSessionZen = zen;
291        }
292        mZenButtons.setSelectedValue(zen);
293        updateWidgets();
294    }
295
296    private int getSelectedZen(int defValue) {
297        final Object zen = mZenButtons.getSelectedValue();
298        return zen != null ? (Integer) zen : defValue;
299    }
300
301    private void updateWidgets() {
302        final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
303        final boolean zenOff = zen == Global.ZEN_MODE_OFF;
304        final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
305        final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
306        final boolean expanded = !mHidden && mExpanded;
307
308        mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
309        mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE);
310        mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE);
311        mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
312        mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
313        mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
314
315        if (zenNone) {
316            mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
317            mZenSubheadCollapsed.setText(mExitConditionText);
318        } else if (zenImportant) {
319            mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
320            mZenSubheadCollapsed.setText(mExitConditionText);
321        }
322        mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous()
323                ? mSubheadWarningColor : mSubheadColor);
324    }
325
326    private Condition parseExistingTimeCondition(Condition condition) {
327        if (condition == null) return null;
328        final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
329        if (time == 0) return null;
330        final long span = time - System.currentTimeMillis();
331        if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
332        return ZenModeConfig.toTimeCondition(time, Math.round(span / (float) MINUTES_MS));
333    }
334
335    private void handleUpdateConditions(Condition[] conditions) {
336        mConditions = conditions;
337        handleUpdateConditions();
338    }
339
340    private void handleUpdateConditions() {
341        final int conditionCount = mConditions == null ? 0 : mConditions.length;
342        if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount);
343        for (int i = mZenConditions.getChildCount() - 1; i >= FIRST_CONDITION_INDEX; i--) {
344            mZenConditions.removeViewAt(i);
345        }
346        // forever
347        bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
348        // countdown
349        bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
350        // provider conditions
351        boolean foundDowntime = false;
352        for (int i = 0; i < conditionCount; i++) {
353            bind(mConditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i));
354            foundDowntime |= isDowntime(mConditions[i]);
355        }
356        // ensure downtime exists, if active
357        if (isDowntime(mSessionExitCondition) && !foundDowntime) {
358            bind(mSessionExitCondition, null);
359        }
360        // ensure something is selected
361        checkForDefault();
362    }
363
364    private static boolean isDowntime(Condition c) {
365        return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c));
366    }
367
368    private ConditionTag getConditionTagAt(int index) {
369        return (ConditionTag) mZenConditions.getChildAt(index).getTag();
370    }
371
372    private void checkForDefault() {
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(MINUTE_BUCKETS[favoriteIndex]);
387            mBucketIndex = favoriteIndex;
388            bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
389            getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true);
390        }
391    }
392
393    private void handleExitConditionChanged(Condition exitCondition) {
394        setExitCondition(exitCondition);
395        if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
396        final int N = mZenConditions.getChildCount();
397        for (int i = 0; i < N; i++) {
398            final ConditionTag tag = getConditionTagAt(i);
399            tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition));
400        }
401    }
402
403    private void bind(final Condition condition, View convertView) {
404        final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE;
405        final View row;
406        if (convertView == null) {
407            row = mInflater.inflate(R.layout.zen_mode_condition, this, false);
408            if (DEBUG) Log.d(mTag, "Adding new condition view for: " + condition);
409            mZenConditions.addView(row);
410        } else {
411            row = convertView;
412        }
413        final ConditionTag tag =
414                row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
415        row.setTag(tag);
416        if (tag.rb == null) {
417            tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox);
418        }
419        tag.condition = condition;
420        tag.rb.setEnabled(enabled);
421        if (sameConditionId(mSessionExitCondition, tag.condition)) {
422            tag.rb.setChecked(true);
423        }
424        tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
425            @Override
426            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
427                if (mExpanded && isChecked) {
428                    if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition);
429                    final int N = mZenConditions.getChildCount();
430                    for (int i = 0; i < N; i++) {
431                        ConditionTag childTag = getConditionTagAt(i);
432                        if (childTag == tag) continue;
433                        childTag.rb.setChecked(false);
434                    }
435                    select(tag.condition);
436                    fireInteraction();
437                }
438            }
439        });
440        final TextView title = (TextView) row.findViewById(android.R.id.title);
441        if (condition == null) {
442            title.setText(mContext.getString(com.android.internal.R.string.zen_mode_forever));
443        } else {
444            title.setText(condition.summary);
445        }
446        title.setEnabled(enabled);
447        title.setAlpha(enabled ? 1 : .4f);
448        final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
449        button1.setOnClickListener(new OnClickListener() {
450            @Override
451            public void onClick(View v) {
452                onClickTimeButton(row, tag, false /*down*/);
453            }
454        });
455
456        final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
457        button2.setOnClickListener(new OnClickListener() {
458            @Override
459            public void onClick(View v) {
460                onClickTimeButton(row, tag, true /*up*/);
461            }
462        });
463        title.setOnClickListener(new OnClickListener() {
464            @Override
465            public void onClick(View v) {
466                tag.rb.setChecked(true);
467                fireInteraction();
468            }
469        });
470
471        final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition));
472        if (time > 0) {
473            if (mBucketIndex > -1) {
474                button1.setEnabled(mBucketIndex > 0);
475                button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
476            } else {
477                final long span = time - System.currentTimeMillis();
478                button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
479                final Condition maxCondition = ZenModeConfig.toTimeCondition(MAX_BUCKET_MINUTES);
480                button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
481            }
482
483            button1.setAlpha(button1.isEnabled() ? 1f : .5f);
484            button2.setAlpha(button2.isEnabled() ? 1f : .5f);
485        } else {
486            button1.setVisibility(View.GONE);
487            button2.setVisibility(View.GONE);
488        }
489    }
490
491    private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
492        Condition newCondition = null;
493        final int N = MINUTE_BUCKETS.length;
494        if (mBucketIndex == -1) {
495            // not on a known index, search for the next or prev bucket by time
496            final Uri conditionId = getConditionId(tag.condition);
497            final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
498            final long now = System.currentTimeMillis();
499            for (int i = 0; i < N; i++) {
500                int j = up ? i : N - 1 - i;
501                final int bucketMinutes = MINUTE_BUCKETS[j];
502                final long bucketTime = now + bucketMinutes * MINUTES_MS;
503                if (up && bucketTime > time || !up && bucketTime < time) {
504                    mBucketIndex = j;
505                    newCondition = ZenModeConfig.toTimeCondition(bucketTime, bucketMinutes);
506                    break;
507                }
508            }
509            if (newCondition == null) {
510                mBucketIndex = DEFAULT_BUCKET_INDEX;
511                newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
512            }
513        } else {
514            // on a known index, simply increment or decrement
515            mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
516            newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
517        }
518        mTimeCondition = newCondition;
519        bind(mTimeCondition, row);
520        tag.rb.setChecked(true);
521        select(mTimeCondition);
522        fireInteraction();
523    }
524
525    private void select(Condition condition) {
526        if (DEBUG) Log.d(mTag, "select " + condition);
527        if (mController != null) {
528            mController.setExitCondition(condition);
529        }
530        setExitCondition(condition);
531        if (condition == null) {
532            mPrefs.setMinuteIndex(-1);
533        } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) {
534            mPrefs.setMinuteIndex(mBucketIndex);
535        }
536        mSessionExitCondition = copy(condition);
537    }
538
539    private void fireMoreSettings() {
540        if (mCallback != null) {
541            mCallback.onMoreSettings();
542        }
543    }
544
545    private void fireInteraction() {
546        if (mCallback != null) {
547            mCallback.onInteraction();
548        }
549    }
550
551    private void fireExpanded() {
552        if (mCallback != null) {
553            mCallback.onExpanded(mExpanded);
554        }
555    }
556
557    private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
558        @Override
559        public void onZenChanged(int zen) {
560            mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
561        }
562        @Override
563        public void onConditionsChanged(Condition[] conditions) {
564            mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
565        }
566
567        @Override
568        public void onExitConditionChanged(Condition exitCondition) {
569            mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
570        }
571    };
572
573    private final class H extends Handler {
574        private static final int UPDATE_CONDITIONS = 1;
575        private static final int EXIT_CONDITION_CHANGED = 2;
576        private static final int UPDATE_ZEN = 3;
577
578        private H() {
579            super(Looper.getMainLooper());
580        }
581
582        @Override
583        public void handleMessage(Message msg) {
584            if (msg.what == UPDATE_CONDITIONS) {
585                handleUpdateConditions((Condition[]) msg.obj);
586            } else if (msg.what == EXIT_CONDITION_CHANGED) {
587                handleExitConditionChanged((Condition) msg.obj);
588            } else if (msg.what == UPDATE_ZEN) {
589                handleUpdateZen(msg.arg1);
590            }
591        }
592    }
593
594    public interface Callback {
595        void onMoreSettings();
596        void onInteraction();
597        void onExpanded(boolean expanded);
598    }
599
600    // used as the view tag on condition rows
601    private static class ConditionTag {
602        RadioButton rb;
603        Condition condition;
604    }
605
606    private final class Prefs implements OnSharedPreferenceChangeListener {
607        private static final String KEY_MINUTE_INDEX = "minuteIndex";
608        private static final String KEY_NONE_SELECTED = "noneSelected";
609
610        private final int mNoneDangerousThreshold;
611
612        private int mMinuteIndex;
613        private int mNoneSelected;
614
615        private Prefs() {
616            mNoneDangerousThreshold = mContext.getResources()
617                    .getInteger(R.integer.zen_mode_alarm_warning_threshold);
618            prefs().registerOnSharedPreferenceChangeListener(this);
619            updateMinuteIndex();
620            updateNoneSelected();
621        }
622
623        public boolean isNoneDangerous() {
624            return mNoneSelected < mNoneDangerousThreshold;
625        }
626
627        public void trackNoneSelected() {
628            mNoneSelected = clampNoneSelected(mNoneSelected + 1);
629            if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
630                    + mNoneDangerousThreshold);
631            prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply();
632        }
633
634        public int getMinuteIndex() {
635            return mMinuteIndex;
636        }
637
638        public void setMinuteIndex(int minuteIndex) {
639            minuteIndex = clampIndex(minuteIndex);
640            if (minuteIndex == mMinuteIndex) return;
641            mMinuteIndex = clampIndex(minuteIndex);
642            if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
643            prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply();
644        }
645
646        @Override
647        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
648            updateMinuteIndex();
649            updateNoneSelected();
650        }
651
652        private SharedPreferences prefs() {
653            return mContext.getSharedPreferences(ZenModePanel.class.getSimpleName(), 0);
654        }
655
656        private void updateMinuteIndex() {
657            mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
658            if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
659        }
660
661        private int clampIndex(int index) {
662            return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
663        }
664
665        private void updateNoneSelected() {
666            mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0));
667            if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
668        }
669
670        private int clampNoneSelected(int noneSelected) {
671            return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
672        }
673    }
674
675    private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
676        @Override
677        public void onSelected(Object value) {
678            if (value != null && mZenButtons.isShown()) {
679                if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
680                mController.setZen((Integer) value);
681            }
682        }
683    };
684}
685