Home | History | Annotate | Download | only in volume
      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 
    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
    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
    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
    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 
    180     public void setHidden(boolean hidden) {
    181         if (mHidden == hidden) return;
    182         mHidden = hidden;
    183         updateWidgets();
    184     }
    185 
    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 
    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 */
    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 
    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 
    244     public void updateLocale() {
    245         mZenButtons.updateLocale();
    246     }
    247 
    248     private void setExitCondition(Condition exitCondition) {
    249         if (sameConditionId(mExitCondition, exitCondition)) return;
    250         mExitCondition = exitCondition;
    251         refreshExitConditionText();
    252         updateWidgets();
    253     }
    254 
    255     private static Uri getConditionId(Condition condition) {
    256         return condition != null ? condition.id : null;
    257     }
    258 
    259     private static boolean sameConditionId(Condition lhs, Condition rhs) {
    260         return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
    261     }
    262 
    263     private static Condition copy(Condition condition) {
    264         return condition == null ? null : condition.copy();
    265     }
    266 
    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 
    279     public void setCallback(Callback callback) {
    280         mCallback = callback;
    281     }
    282 
    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 
    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 
    308     private int getSelectedZen(int defValue) {
    309         final Object zen = mZenButtons.getSelectedValue();
    310         return zen != null ? (Integer) zen : defValue;
    311     }
    312 
    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 
    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 
    347     private void handleUpdateConditions(Condition[] conditions) {
    348         mConditions = conditions;
    349         handleUpdateConditions();
    350     }
    351 
    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 
    376     private static boolean isDowntime(Condition c) {
    377         return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c));
    378     }
    379 
    380     private ConditionTag getConditionTagAt(int index) {
    381         return (ConditionTag) mZenConditions.getChildAt(index).getTag();
    382     }
    383 
    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 
    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 
    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 
    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 
    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 
    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 
    578     private void fireMoreSettings() {
    579         if (mCallback != null) {
    580             mCallback.onMoreSettings();
    581         }
    582     }
    583 
    584     private void fireInteraction() {
    585         if (mCallback != null) {
    586             mCallback.onInteraction();
    587         }
    588     }
    589 
    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 
    617         private H() {
    618             super(Looper.getMainLooper());
    619         }
    620 
    621         @Override
    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 {
    634         void onMoreSettings();
    635         void onInteraction();
    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 
    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 
    663         public boolean isNoneDangerous() {
    664             return mNoneSelected < mNoneDangerousThreshold;
    665         }
    666 
    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 
    674         public int getMinuteIndex() {
    675             return mMinuteIndex;
    676         }
    677 
    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
    687         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    688             updateMinuteIndex();
    689             updateNoneSelected();
    690         }
    691 
    692         private SharedPreferences prefs() {
    693             return mContext.getSharedPreferences(ZenModePanel.class.getSimpleName(), 0);
    694         }
    695 
    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 
    701         private int clampIndex(int index) {
    702             return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
    703         }
    704 
    705         private void updateNoneSelected() {
    706             mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0));
    707             if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
    708         }
    709 
    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