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