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.LayoutTransition;
     20 import android.animation.LayoutTransition.TransitionListener;
     21 import android.app.ActivityManager;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.SharedPreferences;
     25 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
     26 import android.content.res.Configuration;
     27 import android.net.Uri;
     28 import android.os.AsyncTask;
     29 import android.os.Handler;
     30 import android.os.Looper;
     31 import android.os.Message;
     32 import android.provider.Settings;
     33 import android.provider.Settings.Global;
     34 import android.service.notification.Condition;
     35 import android.service.notification.ZenModeConfig;
     36 import android.service.notification.ZenModeConfig.ZenRule;
     37 import android.text.TextUtils;
     38 import android.text.format.DateFormat;
     39 import android.util.ArraySet;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.util.MathUtils;
     43 import android.view.LayoutInflater;
     44 import android.view.View;
     45 import android.view.ViewGroup;
     46 import android.widget.CompoundButton;
     47 import android.widget.CompoundButton.OnCheckedChangeListener;
     48 import android.widget.ImageView;
     49 import android.widget.LinearLayout;
     50 import android.widget.RadioButton;
     51 import android.widget.RadioGroup;
     52 import android.widget.TextView;
     53 
     54 import com.android.internal.logging.MetricsLogger;
     55 import com.android.internal.logging.MetricsProto.MetricsEvent;
     56 import com.android.systemui.Prefs;
     57 import com.android.systemui.R;
     58 import com.android.systemui.statusbar.policy.ZenModeController;
     59 
     60 import java.io.FileDescriptor;
     61 import java.io.PrintWriter;
     62 import java.util.Arrays;
     63 import java.util.Calendar;
     64 import java.util.GregorianCalendar;
     65 import java.util.Locale;
     66 import java.util.Objects;
     67 
     68 public class ZenModePanel extends LinearLayout {
     69     private static final String TAG = "ZenModePanel";
     70     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     71 
     72     private static final int SECONDS_MS = 1000;
     73     private static final int MINUTES_MS = 60 * SECONDS_MS;
     74 
     75     private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
     76     private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
     77     private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
     78     private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
     79     private static final int FOREVER_CONDITION_INDEX = 0;
     80     private static final int COUNTDOWN_CONDITION_INDEX = 1;
     81     private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
     82     private static final int COUNTDOWN_CONDITION_COUNT = 2;
     83 
     84     public static final Intent ZEN_SETTINGS
     85             = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
     86     public static final Intent ZEN_PRIORITY_SETTINGS
     87             = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
     88 
     89     private final Context mContext;
     90     protected final LayoutInflater mInflater;
     91     private final H mHandler = new H();
     92     private final ZenPrefs mPrefs;
     93     private final TransitionHelper mTransitionHelper = new TransitionHelper();
     94     private final Uri mForeverId;
     95     private final SpTexts mSpTexts;
     96 
     97     private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
     98 
     99     protected SegmentedButtons mZenButtons;
    100     private View mZenIntroduction;
    101     private TextView mZenIntroductionMessage;
    102     private View mZenIntroductionConfirm;
    103     private TextView mZenIntroductionCustomize;
    104     protected LinearLayout mZenConditions;
    105     private TextView mZenAlarmWarning;
    106     private RadioGroup mZenRadioGroup;
    107     private LinearLayout mZenRadioGroupContent;
    108 
    109     private Callback mCallback;
    110     private ZenModeController mController;
    111     private boolean mCountdownConditionSupported;
    112     private boolean mRequestingConditions;
    113     private Condition mExitCondition;
    114     private int mBucketIndex = -1;
    115     private boolean mExpanded;
    116     private boolean mHidden;
    117     private int mSessionZen;
    118     private int mAttachedZen;
    119     private boolean mAttached;
    120     private Condition mSessionExitCondition;
    121     private Condition[] mConditions;
    122     private Condition mTimeCondition;
    123     private boolean mVoiceCapable;
    124 
    125     public ZenModePanel(Context context, AttributeSet attrs) {
    126         super(context, attrs);
    127         mContext = context;
    128         mPrefs = new ZenPrefs();
    129         mInflater = LayoutInflater.from(mContext.getApplicationContext());
    130         mForeverId = Condition.newId(mContext).appendPath("forever").build();
    131         mSpTexts = new SpTexts(mContext);
    132         mVoiceCapable = Util.isVoiceCapable(mContext);
    133         if (DEBUG) Log.d(mTag, "new ZenModePanel");
    134     }
    135 
    136     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    137         pw.println("ZenModePanel state:");
    138         pw.print("  mCountdownConditionSupported="); pw.println(mCountdownConditionSupported);
    139         pw.print("  mRequestingConditions="); pw.println(mRequestingConditions);
    140         pw.print("  mAttached="); pw.println(mAttached);
    141         pw.print("  mHidden="); pw.println(mHidden);
    142         pw.print("  mExpanded="); pw.println(mExpanded);
    143         pw.print("  mSessionZen="); pw.println(mSessionZen);
    144         pw.print("  mAttachedZen="); pw.println(mAttachedZen);
    145         pw.print("  mConfirmedPriorityIntroduction=");
    146         pw.println(mPrefs.mConfirmedPriorityIntroduction);
    147         pw.print("  mConfirmedSilenceIntroduction=");
    148         pw.println(mPrefs.mConfirmedSilenceIntroduction);
    149         pw.print("  mVoiceCapable="); pw.println(mVoiceCapable);
    150         mTransitionHelper.dump(fd, pw, args);
    151     }
    152 
    153     protected void createZenButtons() {
    154         mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
    155         mZenButtons.addButton(R.string.interruption_level_none_twoline,
    156                 R.string.interruption_level_none_with_warning,
    157                 Global.ZEN_MODE_NO_INTERRUPTIONS);
    158         mZenButtons.addButton(R.string.interruption_level_alarms_twoline,
    159                 R.string.interruption_level_alarms,
    160                 Global.ZEN_MODE_ALARMS);
    161         mZenButtons.addButton(R.string.interruption_level_priority_twoline,
    162                 R.string.interruption_level_priority,
    163                 Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
    164         mZenButtons.setCallback(mZenButtonsCallback);
    165     }
    166 
    167     @Override
    168     protected void onFinishInflate() {
    169         super.onFinishInflate();
    170         createZenButtons();
    171         mZenIntroduction = findViewById(R.id.zen_introduction);
    172         mZenIntroductionMessage = (TextView) findViewById(R.id.zen_introduction_message);
    173         mSpTexts.add(mZenIntroductionMessage);
    174         mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm);
    175         mZenIntroductionConfirm.setOnClickListener(new OnClickListener() {
    176             @Override
    177             public void onClick(View v) {
    178                 confirmZenIntroduction();
    179             }
    180         });
    181         mZenIntroductionCustomize = (TextView) findViewById(R.id.zen_introduction_customize);
    182         mZenIntroductionCustomize.setOnClickListener(new OnClickListener() {
    183             @Override
    184             public void onClick(View v) {
    185                 confirmZenIntroduction();
    186                 if (mCallback != null) {
    187                     mCallback.onPrioritySettings();
    188                 }
    189             }
    190         });
    191         mSpTexts.add(mZenIntroductionCustomize);
    192 
    193         mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
    194         mZenAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning);
    195         mZenRadioGroup = (RadioGroup) findViewById(R.id.zen_radio_buttons);
    196         mZenRadioGroupContent = (LinearLayout) findViewById(R.id.zen_radio_buttons_content);
    197     }
    198 
    199     @Override
    200     protected void onConfigurationChanged(Configuration newConfig) {
    201         super.onConfigurationChanged(newConfig);
    202         if (mZenButtons != null) {
    203             mZenButtons.updateLocale();
    204         }
    205     }
    206 
    207     private void confirmZenIntroduction() {
    208         final String prefKey = prefKeyForConfirmation(getSelectedZen(Global.ZEN_MODE_OFF));
    209         if (prefKey == null) return;
    210         if (DEBUG) Log.d(TAG, "confirmZenIntroduction " + prefKey);
    211         Prefs.putBoolean(mContext, prefKey, true);
    212         mHandler.sendEmptyMessage(H.UPDATE_WIDGETS);
    213     }
    214 
    215     private static String prefKeyForConfirmation(int zen) {
    216         switch (zen) {
    217             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
    218                 return Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION;
    219             case Global.ZEN_MODE_NO_INTERRUPTIONS:
    220                 return Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION;
    221             default:
    222                 return null;
    223         }
    224     }
    225 
    226     @Override
    227     protected void onAttachedToWindow() {
    228         super.onAttachedToWindow();
    229         if (DEBUG) Log.d(mTag, "onAttachedToWindow");
    230         mAttached = true;
    231         mAttachedZen = getSelectedZen(-1);
    232         mSessionZen = mAttachedZen;
    233         mTransitionHelper.clear();
    234         mController.addCallback(mZenCallback);
    235         setSessionExitCondition(copy(mExitCondition));
    236         updateWidgets();
    237         setRequestingConditions(!mHidden);
    238     }
    239 
    240     @Override
    241     protected void onDetachedFromWindow() {
    242         super.onDetachedFromWindow();
    243         if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
    244         checkForAttachedZenChange();
    245         mAttached = false;
    246         mAttachedZen = -1;
    247         mSessionZen = -1;
    248         mController.removeCallback(mZenCallback);
    249         setSessionExitCondition(null);
    250         setRequestingConditions(false);
    251         mTransitionHelper.clear();
    252     }
    253 
    254     private void setSessionExitCondition(Condition condition) {
    255         if (Objects.equals(condition, mSessionExitCondition)) return;
    256         if (DEBUG) Log.d(mTag, "mSessionExitCondition=" + getConditionId(condition));
    257         mSessionExitCondition = condition;
    258     }
    259 
    260     public void setHidden(boolean hidden) {
    261         if (mHidden == hidden) return;
    262         if (DEBUG) Log.d(mTag, "hidden=" + hidden);
    263         mHidden = hidden;
    264         setRequestingConditions(mAttached && !mHidden);
    265         updateWidgets();
    266     }
    267 
    268     private void checkForAttachedZenChange() {
    269         final int selectedZen = getSelectedZen(-1);
    270         if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen);
    271         if (selectedZen != mAttachedZen) {
    272             if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen);
    273             if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
    274                 mPrefs.trackNoneSelected();
    275             }
    276         }
    277     }
    278 
    279     private void setExpanded(boolean expanded) {
    280         if (expanded == mExpanded) return;
    281         if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
    282         mExpanded = expanded;
    283         if (mExpanded && isShown()) {
    284             ensureSelection();
    285         }
    286         updateWidgets();
    287         fireExpanded();
    288     }
    289 
    290     /** Start or stop requesting relevant zen mode exit conditions */
    291     private void setRequestingConditions(final boolean requesting) {
    292         if (mRequestingConditions == requesting) return;
    293         if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
    294         mRequestingConditions = requesting;
    295         if (mRequestingConditions) {
    296             mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
    297             if (mTimeCondition != null) {
    298                 mBucketIndex = -1;
    299             } else {
    300                 mBucketIndex = DEFAULT_BUCKET_INDEX;
    301                 mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
    302                         MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
    303             }
    304             if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
    305 
    306             mConditions = null; // reset conditions
    307             handleUpdateConditions();
    308         } else {
    309             hideAllConditions();
    310         }
    311     }
    312 
    313     protected void addZenConditions(int count) {
    314         for (int i = 0; i < count; i++) {
    315             final View rb = mInflater.inflate(R.layout.zen_mode_button, this, false);
    316             rb.setId(i);
    317             mZenRadioGroup.addView(rb);
    318             final View rbc = mInflater.inflate(R.layout.zen_mode_condition, this, false);
    319             rbc.setId(i + count);
    320             mZenRadioGroupContent.addView(rbc);
    321         }
    322     }
    323 
    324     public void init(ZenModeController controller) {
    325         mController = controller;
    326         mCountdownConditionSupported = mController.isCountdownConditionSupported();
    327         final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0;
    328         final int minConditions = 1 /*forever*/ + countdownDelta;
    329         addZenConditions(minConditions);
    330         mSessionZen = getSelectedZen(-1);
    331         handleUpdateManualRule(mController.getManualRule());
    332         if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
    333         hideAllConditions();
    334     }
    335 
    336     public void updateLocale() {
    337         mZenButtons.updateLocale();
    338     }
    339 
    340     private void setExitCondition(Condition exitCondition) {
    341         if (Objects.equals(mExitCondition, exitCondition)) return;
    342         mExitCondition = exitCondition;
    343         if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition));
    344         updateWidgets();
    345     }
    346 
    347     private static Uri getConditionId(Condition condition) {
    348         return condition != null ? condition.id : null;
    349     }
    350 
    351     private Uri getRealConditionId(Condition condition) {
    352         return isForever(condition) ? null : getConditionId(condition);
    353     }
    354 
    355     private static boolean sameConditionId(Condition lhs, Condition rhs) {
    356         return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
    357     }
    358 
    359     private static Condition copy(Condition condition) {
    360         return condition == null ? null : condition.copy();
    361     }
    362 
    363     public void setCallback(Callback callback) {
    364         mCallback = callback;
    365     }
    366 
    367     private void handleUpdateManualRule(ZenRule rule) {
    368         final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
    369         handleUpdateZen(zen);
    370         final Condition c = rule != null ? rule.condition : null;
    371         handleExitConditionChanged(c);
    372     }
    373 
    374     private void handleUpdateZen(int zen) {
    375         if (mSessionZen != -1 && mSessionZen != zen) {
    376             setExpanded(isShown());
    377             mSessionZen = zen;
    378         }
    379         mZenButtons.setSelectedValue(zen, false /* fromClick */);
    380         updateWidgets();
    381         handleUpdateConditions();
    382         if (mExpanded) {
    383             final Condition selected = getSelectedCondition();
    384             if (!Objects.equals(mExitCondition, selected)) {
    385                 select(selected);
    386             }
    387         }
    388     }
    389 
    390     private void handleExitConditionChanged(Condition exitCondition) {
    391         setExitCondition(exitCondition);
    392         if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
    393         final int N = getVisibleConditions();
    394         for (int i = 0; i < N; i++) {
    395             final ConditionTag tag = getConditionTagAt(i);
    396             if (tag != null) {
    397                 if (sameConditionId(tag.condition, mExitCondition)) {
    398                     bind(exitCondition, mZenRadioGroupContent.getChildAt(i), i);
    399                 }
    400             }
    401         }
    402     }
    403 
    404     private Condition getSelectedCondition() {
    405         final int N = getVisibleConditions();
    406         for (int i = 0; i < N; i++) {
    407             final ConditionTag tag = getConditionTagAt(i);
    408             if (tag != null && tag.rb.isChecked()) {
    409                 return tag.condition;
    410             }
    411         }
    412         return null;
    413     }
    414 
    415     private int getSelectedZen(int defValue) {
    416         final Object zen = mZenButtons.getSelectedValue();
    417         return zen != null ? (Integer) zen : defValue;
    418     }
    419 
    420     private void updateWidgets() {
    421         if (mTransitionHelper.isTransitioning()) {
    422             mTransitionHelper.pendingUpdateWidgets();
    423             return;
    424         }
    425         final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
    426         final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
    427         final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
    428         final boolean introduction = (zenImportant && !mPrefs.mConfirmedPriorityIntroduction
    429                         || zenNone && !mPrefs.mConfirmedSilenceIntroduction);
    430 
    431         mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
    432         mZenIntroduction.setVisibility(introduction ? VISIBLE : GONE);
    433         if (introduction) {
    434             mZenIntroductionMessage.setText(zenImportant ? R.string.zen_priority_introduction
    435                     : mVoiceCapable ? R.string.zen_silence_introduction_voice
    436                     : R.string.zen_silence_introduction);
    437             mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE);
    438         }
    439         final String warning = computeAlarmWarningText(zenNone);
    440         mZenAlarmWarning.setVisibility(warning != null ? VISIBLE : GONE);
    441         mZenAlarmWarning.setText(warning);
    442     }
    443 
    444     private String computeAlarmWarningText(boolean zenNone) {
    445         if (!zenNone) {
    446             return null;
    447         }
    448         final long now = System.currentTimeMillis();
    449         final long nextAlarm = mController.getNextAlarm();
    450         if (nextAlarm < now) {
    451             return null;
    452         }
    453         int warningRes = 0;
    454         if (mSessionExitCondition == null || isForever(mSessionExitCondition)) {
    455             warningRes = R.string.zen_alarm_warning_indef;
    456         } else {
    457             final long time = ZenModeConfig.tryParseCountdownConditionId(mSessionExitCondition.id);
    458             if (time > now && nextAlarm < time) {
    459                 warningRes = R.string.zen_alarm_warning;
    460             }
    461         }
    462         if (warningRes == 0) {
    463             return null;
    464         }
    465         final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000;
    466         final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
    467         final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma");
    468         final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
    469         final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm);
    470         final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far;
    471         final String template = getResources().getString(templateRes, formattedTime);
    472         return getResources().getString(warningRes, template);
    473     }
    474 
    475     private static Condition parseExistingTimeCondition(Context context, Condition condition) {
    476         if (condition == null) return null;
    477         final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
    478         if (time == 0) return null;
    479         final long now = System.currentTimeMillis();
    480         final long span = time - now;
    481         if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
    482         return ZenModeConfig.toTimeCondition(context,
    483                 time, Math.round(span / (float) MINUTES_MS), ActivityManager.getCurrentUser(),
    484                 false /*shortVersion*/);
    485     }
    486 
    487     private void handleUpdateConditions() {
    488         if (mTransitionHelper.isTransitioning()) {
    489             return;
    490         }
    491         final int conditionCount = mConditions == null ? 0 : mConditions.length;
    492         if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount);
    493         // forever
    494         bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
    495                 FOREVER_CONDITION_INDEX);
    496         // countdown
    497         if (mCountdownConditionSupported && mTimeCondition != null) {
    498             bind(mTimeCondition, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
    499                     COUNTDOWN_CONDITION_INDEX);
    500         }
    501         // countdown until alarm
    502         if (mCountdownConditionSupported) {
    503             Condition nextAlarmCondition = getTimeUntilNextAlarmCondition();
    504             if (nextAlarmCondition != null) {
    505                 mZenRadioGroup.getChildAt(
    506                         COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(View.VISIBLE);
    507                 mZenRadioGroupContent.getChildAt(
    508                         COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(View.VISIBLE);
    509                 bind(nextAlarmCondition,
    510                         mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX),
    511                         COUNTDOWN_ALARM_CONDITION_INDEX);
    512             } else {
    513                 mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(View.GONE);
    514                 mZenRadioGroupContent.getChildAt(
    515                         COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(View.GONE);
    516             }
    517         }
    518         // ensure something is selected
    519         if (mExpanded && isShown()) {
    520             ensureSelection();
    521         }
    522         mZenConditions.setVisibility(mSessionZen != Global.ZEN_MODE_OFF ? View.VISIBLE : View.GONE);
    523     }
    524 
    525     private Condition forever() {
    526         return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
    527                 Condition.STATE_TRUE, 0 /*flags*/);
    528     }
    529 
    530     private static String foreverSummary(Context context) {
    531         return context.getString(com.android.internal.R.string.zen_mode_forever);
    532     }
    533 
    534     // Returns a time condition if the next alarm is within the next week.
    535     private Condition getTimeUntilNextAlarmCondition() {
    536         GregorianCalendar weekRange = new GregorianCalendar();
    537         final long now = weekRange.getTimeInMillis();
    538         setToMidnight(weekRange);
    539         weekRange.add(Calendar.DATE, 6);
    540         final long nextAlarmMs = mController.getNextAlarm();
    541         if (nextAlarmMs > 0) {
    542             GregorianCalendar nextAlarm = new GregorianCalendar();
    543             nextAlarm.setTimeInMillis(nextAlarmMs);
    544             setToMidnight(nextAlarm);
    545 
    546             if (weekRange.compareTo(nextAlarm) >= 0) {
    547                 return ZenModeConfig.toNextAlarmCondition(mContext, now,
    548                         nextAlarmMs, ActivityManager.getCurrentUser());
    549             }
    550         }
    551         return null;
    552     }
    553 
    554     private void setToMidnight(Calendar calendar) {
    555         calendar.set(Calendar.HOUR_OF_DAY, 0);
    556         calendar.set(Calendar.MINUTE, 0);
    557         calendar.set(Calendar.SECOND, 0);
    558         calendar.set(Calendar.MILLISECOND, 0);
    559     }
    560 
    561     private ConditionTag getConditionTagAt(int index) {
    562         return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
    563     }
    564 
    565     private int getVisibleConditions() {
    566         int rt = 0;
    567         final int N = mZenRadioGroupContent.getChildCount();
    568         for (int i = 0; i < N; i++) {
    569             rt += mZenRadioGroupContent.getChildAt(i).getVisibility() == VISIBLE ? 1 : 0;
    570         }
    571         return rt;
    572     }
    573 
    574     private void hideAllConditions() {
    575         final int N = mZenRadioGroupContent.getChildCount();
    576         for (int i = 0; i < N; i++) {
    577             mZenRadioGroupContent.getChildAt(i).setVisibility(GONE);
    578         }
    579     }
    580 
    581     private void ensureSelection() {
    582         // are we left without anything selected?  if so, set a default
    583         final int visibleConditions = getVisibleConditions();
    584         if (visibleConditions == 0) return;
    585         for (int i = 0; i < visibleConditions; i++) {
    586             final ConditionTag tag = getConditionTagAt(i);
    587             if (tag != null && tag.rb.isChecked()) {
    588                 if (DEBUG) Log.d(mTag, "Not selecting a default, checked=" + tag.condition);
    589                 return;
    590             }
    591         }
    592         final ConditionTag foreverTag = getConditionTagAt(FOREVER_CONDITION_INDEX);
    593         if (foreverTag == null) return;
    594         if (DEBUG) Log.d(mTag, "Selecting a default");
    595         final int favoriteIndex = mPrefs.getMinuteIndex();
    596         if (favoriteIndex == -1 || !mCountdownConditionSupported) {
    597             foreverTag.rb.setChecked(true);
    598         } else {
    599             mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
    600                     MINUTE_BUCKETS[favoriteIndex], ActivityManager.getCurrentUser());
    601             mBucketIndex = favoriteIndex;
    602             bind(mTimeCondition, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
    603                     COUNTDOWN_CONDITION_INDEX);
    604             getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
    605         }
    606     }
    607 
    608     private static boolean isCountdown(Condition c) {
    609         return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
    610     }
    611 
    612     private boolean isForever(Condition c) {
    613         return c != null && mForeverId.equals(c.id);
    614     }
    615 
    616     private void bind(final Condition condition, final View row, final int rowId) {
    617         if (condition == null) throw new IllegalArgumentException("condition must not be null");
    618         final boolean enabled = condition.state == Condition.STATE_TRUE;
    619         final ConditionTag tag =
    620                 row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
    621         row.setTag(tag);
    622         final boolean first = tag.rb == null;
    623         if (tag.rb == null) {
    624             tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowId);
    625         }
    626         tag.condition = condition;
    627         final Uri conditionId = getConditionId(tag.condition);
    628         if (DEBUG) Log.d(mTag, "bind i=" + mZenRadioGroupContent.indexOfChild(row) + " first="
    629                 + first + " condition=" + conditionId);
    630         tag.rb.setEnabled(enabled);
    631         tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    632             @Override
    633             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    634                 if (mExpanded && isChecked) {
    635                     tag.rb.setChecked(true);
    636                     if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId);
    637                     MetricsLogger.action(mContext, MetricsEvent.QS_DND_CONDITION_SELECT);
    638                     select(tag.condition);
    639                     announceConditionSelection(tag);
    640                 }
    641             }
    642         });
    643 
    644         if (tag.lines == null) {
    645             tag.lines = row.findViewById(android.R.id.content);
    646         }
    647         if (tag.line1 == null) {
    648             tag.line1 = (TextView) row.findViewById(android.R.id.text1);
    649             mSpTexts.add(tag.line1);
    650         }
    651         if (tag.line2 == null) {
    652             tag.line2 = (TextView) row.findViewById(android.R.id.text2);
    653             mSpTexts.add(tag.line2);
    654         }
    655         final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1
    656                 : condition.summary;
    657         final String line2 = condition.line2;
    658         tag.line1.setText(line1);
    659         if (TextUtils.isEmpty(line2)) {
    660             tag.line2.setVisibility(GONE);
    661         } else {
    662             tag.line2.setVisibility(VISIBLE);
    663             tag.line2.setText(line2);
    664         }
    665         tag.lines.setEnabled(enabled);
    666         tag.lines.setAlpha(enabled ? 1 : .4f);
    667 
    668         final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
    669         button1.setOnClickListener(new OnClickListener() {
    670             @Override
    671             public void onClick(View v) {
    672                 onClickTimeButton(row, tag, false /*down*/, rowId);
    673             }
    674         });
    675 
    676         final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
    677         button2.setOnClickListener(new OnClickListener() {
    678             @Override
    679             public void onClick(View v) {
    680                 onClickTimeButton(row, tag, true /*up*/, rowId);
    681             }
    682         });
    683         tag.lines.setOnClickListener(new OnClickListener() {
    684             @Override
    685             public void onClick(View v) {
    686                 tag.rb.setChecked(true);
    687             }
    688         });
    689 
    690         final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
    691         if (rowId != COUNTDOWN_ALARM_CONDITION_INDEX && time > 0) {
    692             button1.setVisibility(VISIBLE);
    693             button2.setVisibility(VISIBLE);
    694             if (mBucketIndex > -1) {
    695                 button1.setEnabled(mBucketIndex > 0);
    696                 button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
    697             } else {
    698                 final long span = time - System.currentTimeMillis();
    699                 button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
    700                 final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
    701                         MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
    702                 button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
    703             }
    704 
    705             button1.setAlpha(button1.isEnabled() ? 1f : .5f);
    706             button2.setAlpha(button2.isEnabled() ? 1f : .5f);
    707         } else {
    708             button1.setVisibility(GONE);
    709             button2.setVisibility(GONE);
    710         }
    711         // wire up interaction callbacks for newly-added condition rows
    712         if (first) {
    713             Interaction.register(tag.rb, mInteractionCallback);
    714             Interaction.register(tag.lines, mInteractionCallback);
    715             Interaction.register(button1, mInteractionCallback);
    716             Interaction.register(button2, mInteractionCallback);
    717         }
    718         row.setVisibility(VISIBLE);
    719     }
    720 
    721     private void announceConditionSelection(ConditionTag tag) {
    722         final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
    723         String modeText;
    724         switch(zen) {
    725             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
    726                 modeText = mContext.getString(R.string.interruption_level_priority);
    727                 break;
    728             case Global.ZEN_MODE_NO_INTERRUPTIONS:
    729                 modeText = mContext.getString(R.string.interruption_level_none);
    730                 break;
    731             case Global.ZEN_MODE_ALARMS:
    732                 modeText = mContext.getString(R.string.interruption_level_alarms);
    733                 break;
    734             default:
    735                 return;
    736         }
    737         announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText,
    738                 tag.line1.getText()));
    739     }
    740 
    741     private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
    742         MetricsLogger.action(mContext, MetricsEvent.QS_DND_TIME, up);
    743         Condition newCondition = null;
    744         final int N = MINUTE_BUCKETS.length;
    745         if (mBucketIndex == -1) {
    746             // not on a known index, search for the next or prev bucket by time
    747             final Uri conditionId = getConditionId(tag.condition);
    748             final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
    749             final long now = System.currentTimeMillis();
    750             for (int i = 0; i < N; i++) {
    751                 int j = up ? i : N - 1 - i;
    752                 final int bucketMinutes = MINUTE_BUCKETS[j];
    753                 final long bucketTime = now + bucketMinutes * MINUTES_MS;
    754                 if (up && bucketTime > time || !up && bucketTime < time) {
    755                     mBucketIndex = j;
    756                     newCondition = ZenModeConfig.toTimeCondition(mContext,
    757                             bucketTime, bucketMinutes, ActivityManager.getCurrentUser(),
    758                             false /*shortVersion*/);
    759                     break;
    760                 }
    761             }
    762             if (newCondition == null) {
    763                 mBucketIndex = DEFAULT_BUCKET_INDEX;
    764                 newCondition = ZenModeConfig.toTimeCondition(mContext,
    765                         MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
    766             }
    767         } else {
    768             // on a known index, simply increment or decrement
    769             mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
    770             newCondition = ZenModeConfig.toTimeCondition(mContext,
    771                     MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
    772         }
    773         mTimeCondition = newCondition;
    774         bind(mTimeCondition, row, rowId);
    775         tag.rb.setChecked(true);
    776         select(mTimeCondition);
    777         announceConditionSelection(tag);
    778     }
    779 
    780     private void select(final Condition condition) {
    781         if (DEBUG) Log.d(mTag, "select " + condition);
    782         if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
    783             if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
    784             return;
    785         }
    786         final Uri realConditionId = getRealConditionId(condition);
    787         if (mController != null) {
    788             AsyncTask.execute(new Runnable() {
    789                 @Override
    790                 public void run() {
    791                     mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
    792                 }
    793             });
    794         }
    795         setExitCondition(condition);
    796         if (realConditionId == null) {
    797             mPrefs.setMinuteIndex(-1);
    798         } else if (isCountdown(condition) && mBucketIndex != -1) {
    799             mPrefs.setMinuteIndex(mBucketIndex);
    800         }
    801         setSessionExitCondition(copy(condition));
    802     }
    803 
    804     private void fireInteraction() {
    805         if (mCallback != null) {
    806             mCallback.onInteraction();
    807         }
    808     }
    809 
    810     private void fireExpanded() {
    811         if (mCallback != null) {
    812             mCallback.onExpanded(mExpanded);
    813         }
    814     }
    815 
    816     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
    817         @Override
    818         public void onManualRuleChanged(ZenRule rule) {
    819             mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
    820         }
    821     };
    822 
    823     private final class H extends Handler {
    824         private static final int MANUAL_RULE_CHANGED = 2;
    825         private static final int UPDATE_WIDGETS = 3;
    826 
    827         private H() {
    828             super(Looper.getMainLooper());
    829         }
    830 
    831         @Override
    832         public void handleMessage(Message msg) {
    833             switch (msg.what) {
    834                 case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break;
    835                 case UPDATE_WIDGETS: updateWidgets(); break;
    836             }
    837         }
    838     }
    839 
    840     public interface Callback {
    841         void onPrioritySettings();
    842         void onInteraction();
    843         void onExpanded(boolean expanded);
    844     }
    845 
    846     // used as the view tag on condition rows
    847     private static class ConditionTag {
    848         RadioButton rb;
    849         View lines;
    850         TextView line1;
    851         TextView line2;
    852         Condition condition;
    853     }
    854 
    855     private final class ZenPrefs implements OnSharedPreferenceChangeListener {
    856         private final int mNoneDangerousThreshold;
    857 
    858         private int mMinuteIndex;
    859         private int mNoneSelected;
    860         private boolean mConfirmedPriorityIntroduction;
    861         private boolean mConfirmedSilenceIntroduction;
    862 
    863         private ZenPrefs() {
    864             mNoneDangerousThreshold = mContext.getResources()
    865                     .getInteger(R.integer.zen_mode_alarm_warning_threshold);
    866             Prefs.registerListener(mContext, this);
    867             updateMinuteIndex();
    868             updateNoneSelected();
    869             updateConfirmedPriorityIntroduction();
    870             updateConfirmedSilenceIntroduction();
    871         }
    872 
    873         public void trackNoneSelected() {
    874             mNoneSelected = clampNoneSelected(mNoneSelected + 1);
    875             if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
    876                     + mNoneDangerousThreshold);
    877             Prefs.putInt(mContext, Prefs.Key.DND_NONE_SELECTED, mNoneSelected);
    878         }
    879 
    880         public int getMinuteIndex() {
    881             return mMinuteIndex;
    882         }
    883 
    884         public void setMinuteIndex(int minuteIndex) {
    885             minuteIndex = clampIndex(minuteIndex);
    886             if (minuteIndex == mMinuteIndex) return;
    887             mMinuteIndex = clampIndex(minuteIndex);
    888             if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
    889             Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_BUCKET_INDEX, mMinuteIndex);
    890         }
    891 
    892         @Override
    893         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    894             updateMinuteIndex();
    895             updateNoneSelected();
    896             updateConfirmedPriorityIntroduction();
    897             updateConfirmedSilenceIntroduction();
    898         }
    899 
    900         private void updateMinuteIndex() {
    901             mMinuteIndex = clampIndex(Prefs.getInt(mContext,
    902                     Prefs.Key.DND_FAVORITE_BUCKET_INDEX, DEFAULT_BUCKET_INDEX));
    903             if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
    904         }
    905 
    906         private int clampIndex(int index) {
    907             return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
    908         }
    909 
    910         private void updateNoneSelected() {
    911             mNoneSelected = clampNoneSelected(Prefs.getInt(mContext,
    912                     Prefs.Key.DND_NONE_SELECTED, 0));
    913             if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
    914         }
    915 
    916         private int clampNoneSelected(int noneSelected) {
    917             return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
    918         }
    919 
    920         private void updateConfirmedPriorityIntroduction() {
    921             final boolean confirmed =  Prefs.getBoolean(mContext,
    922                     Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION, false);
    923             if (confirmed == mConfirmedPriorityIntroduction) return;
    924             mConfirmedPriorityIntroduction = confirmed;
    925             if (DEBUG) Log.d(mTag, "Confirmed priority introduction: "
    926                     + mConfirmedPriorityIntroduction);
    927         }
    928 
    929         private void updateConfirmedSilenceIntroduction() {
    930             final boolean confirmed =  Prefs.getBoolean(mContext,
    931                     Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION, false);
    932             if (confirmed == mConfirmedSilenceIntroduction) return;
    933             mConfirmedSilenceIntroduction = confirmed;
    934             if (DEBUG) Log.d(mTag, "Confirmed silence introduction: "
    935                     + mConfirmedSilenceIntroduction);
    936         }
    937     }
    938 
    939     protected final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
    940         @Override
    941         public void onSelected(final Object value, boolean fromClick) {
    942             if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
    943                 final int zen = (Integer) value;
    944                 if (fromClick) {
    945                     MetricsLogger.action(mContext, MetricsEvent.QS_DND_ZEN_SELECT, zen);
    946                 }
    947                 if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + zen);
    948                 final Uri realConditionId = getRealConditionId(mSessionExitCondition);
    949                 AsyncTask.execute(new Runnable() {
    950                     @Override
    951                     public void run() {
    952                         mController.setZen(zen, realConditionId, TAG + ".selectZen");
    953                         if (zen != Global.ZEN_MODE_OFF) {
    954                             Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, zen);
    955                         }
    956                     }
    957                 });
    958             }
    959         }
    960 
    961         @Override
    962         public void onInteraction() {
    963             fireInteraction();
    964         }
    965     };
    966 
    967     private final Interaction.Callback mInteractionCallback = new Interaction.Callback() {
    968         @Override
    969         public void onInteraction() {
    970             fireInteraction();
    971         }
    972     };
    973 
    974     private final class TransitionHelper implements TransitionListener, Runnable {
    975         private final ArraySet<View> mTransitioningViews = new ArraySet<View>();
    976 
    977         private boolean mTransitioning;
    978         private boolean mPendingUpdateWidgets;
    979 
    980         public void clear() {
    981             mTransitioningViews.clear();
    982             mPendingUpdateWidgets = false;
    983         }
    984 
    985         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    986             pw.println("  TransitionHelper state:");
    987             pw.print("    mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets);
    988             pw.print("    mTransitioning="); pw.println(mTransitioning);
    989             pw.print("    mTransitioningViews="); pw.println(mTransitioningViews);
    990         }
    991 
    992         public void pendingUpdateWidgets() {
    993             mPendingUpdateWidgets = true;
    994         }
    995 
    996         public boolean isTransitioning() {
    997             return !mTransitioningViews.isEmpty();
    998         }
    999 
   1000         @Override
   1001         public void startTransition(LayoutTransition transition,
   1002                 ViewGroup container, View view, int transitionType) {
   1003             mTransitioningViews.add(view);
   1004             updateTransitioning();
   1005         }
   1006 
   1007         @Override
   1008         public void endTransition(LayoutTransition transition,
   1009                 ViewGroup container, View view, int transitionType) {
   1010             mTransitioningViews.remove(view);
   1011             updateTransitioning();
   1012         }
   1013 
   1014         @Override
   1015         public void run() {
   1016             if (DEBUG) Log.d(mTag, "TransitionHelper run"
   1017                     + " mPendingUpdateWidgets=" + mPendingUpdateWidgets);
   1018             if (mPendingUpdateWidgets) {
   1019                 updateWidgets();
   1020             }
   1021             mPendingUpdateWidgets = false;
   1022         }
   1023 
   1024         private void updateTransitioning() {
   1025             final boolean transitioning = isTransitioning();
   1026             if (mTransitioning == transitioning) return;
   1027             mTransitioning = transitioning;
   1028             if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning);
   1029             if (!mTransitioning) {
   1030                 if (mPendingUpdateWidgets) {
   1031                     mHandler.post(this);
   1032                 } else {
   1033                     mPendingUpdateWidgets = false;
   1034                 }
   1035             }
   1036         }
   1037     }
   1038 }
   1039