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