• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.volume;
18 
19 import android.animation.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 
ZenModePanel(Context context, AttributeSet attrs)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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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 
createZenButtons()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
onFinishInflate()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 
setEmptyState(int icon, int text)221     public void setEmptyState(int icon, int text) {
222         mEmptyIcon.post(() -> {
223             mEmptyIcon.setImageResource(icon);
224             mEmptyText.setText(text);
225         });
226     }
227 
setAutoText(CharSequence text)228     public void setAutoText(CharSequence text) {
229         mAutoTitle.post(() -> mAutoTitle.setText(text));
230     }
231 
setState(int state)232     public void setState(int state) {
233         if (mState == state) return;
234         transitionFrom(getView(mState), getView(state));
235         mState = state;
236     }
237 
transitionFrom(View from, View to)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 
getView(int state)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
onConfigurationChanged(Configuration newConfig)264     protected void onConfigurationChanged(Configuration newConfig) {
265         super.onConfigurationChanged(newConfig);
266         mConfigurableTexts.update();
267         if (mZenButtons != null) {
268             mZenButtons.update();
269         }
270     }
271 
confirmZenIntroduction()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 
prefKeyForConfirmation(int zen)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 
onAttach()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 
onDetach()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
onVisibilityAggregated(boolean isVisible)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 
setSessionExitCondition(Condition condition)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 
setHidden(boolean hidden)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 
checkForAttachedZenChange()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 
setExpanded(boolean expanded)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 */
setRequestingConditions(final boolean requesting)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 
addZenConditions(int count)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 
init(ZenModeController controller)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 
setExitCondition(Condition exitCondition)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 
getConditionId(Condition condition)421     private static Uri getConditionId(Condition condition) {
422         return condition != null ? condition.id : null;
423     }
424 
getRealConditionId(Condition condition)425     private Uri getRealConditionId(Condition condition) {
426         return isForever(condition) ? null : getConditionId(condition);
427     }
428 
sameConditionId(Condition lhs, Condition rhs)429     private static boolean sameConditionId(Condition lhs, Condition rhs) {
430         return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
431     }
432 
copy(Condition condition)433     private static Condition copy(Condition condition) {
434         return condition == null ? null : condition.copy();
435     }
436 
setCallback(Callback callback)437     public void setCallback(Callback callback) {
438         mCallback = callback;
439     }
440 
handleUpdateManualRule(ZenRule rule)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 
createCondition(Uri conditionId)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 
handleUpdateZen(int zen)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 
handleExitConditionChanged(Condition exitCondition)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 
getSelectedCondition()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 
getSelectedZen(int defValue)510     private int getSelectedZen(int defValue) {
511         final Object zen = mZenButtons.getSelectedValue();
512         return zen != null ? (Integer) zen : defValue;
513     }
514 
updateWidgets()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 
computeAlarmWarningText(boolean zenNone)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 
setToMidnight(Calendar calendar)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 
getConditionTagAt(int index)665     private ConditionTag getConditionTagAt(int index) {
666         return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
667     }
668 
getVisibleConditions()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 
hideAllConditions()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 
ensureSelection()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 
isCountdown(Condition c)715     private static boolean isCountdown(Condition c) {
716         return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
717     }
718 
isForever(Condition c)719     private boolean isForever(Condition c) {
720         return c != null && mForeverId.equals(c.id);
721     }
722 
bind(final Condition condition, final View row, final int rowId)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 
announceConditionSelection(ConditionTag tag)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 
onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId)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 
select(final Condition condition)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 
fireInteraction()911     private void fireInteraction() {
912         if (mCallback != null) {
913             mCallback.onInteraction();
914         }
915     }
916 
fireExpanded()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 
H()934         private H() {
935             super(Looper.getMainLooper());
936         }
937 
938         @Override
handleMessage(Message msg)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 {
onPrioritySettings()948         void onPrioritySettings();
onInteraction()949         void onInteraction();
onExpanded(boolean expanded)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 
ZenPrefs()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 
trackNoneSelected()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 
getMinuteIndex()989         public int getMinuteIndex() {
990             return mMinuteIndex;
991         }
992 
setMinuteIndex(int minuteIndex)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
onSharedPreferenceChanged(SharedPreferences prefs, String key)1002         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
1003             updateMinuteIndex();
1004             updateNoneSelected();
1005             updateConfirmedPriorityIntroduction();
1006             updateConfirmedSilenceIntroduction();
1007             updateConfirmedAlarmIntroduction();
1008         }
1009 
updateMinuteIndex()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 
clampIndex(int index)1016         private int clampIndex(int index) {
1017             return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
1018         }
1019 
updateNoneSelected()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 
clampNoneSelected(int noneSelected)1026         private int clampNoneSelected(int noneSelected) {
1027             return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
1028         }
1029 
updateConfirmedPriorityIntroduction()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 
updateConfirmedSilenceIntroduction()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 
updateConfirmedAlarmIntroduction()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 
clear()1099         public void clear() {
1100             mTransitioningViews.clear();
1101             mPendingUpdateWidgets = false;
1102         }
1103 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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 
pendingUpdateWidgets()1111         public void pendingUpdateWidgets() {
1112             mPendingUpdateWidgets = true;
1113         }
1114 
isTransitioning()1115         public boolean isTransitioning() {
1116             return !mTransitioningViews.isEmpty();
1117         }
1118 
1119         @Override
startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)1120         public void startTransition(LayoutTransition transition,
1121                 ViewGroup container, View view, int transitionType) {
1122             mTransitioningViews.add(view);
1123             updateTransitioning();
1124         }
1125 
1126         @Override
endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)1127         public void endTransition(LayoutTransition transition,
1128                 ViewGroup container, View view, int transitionType) {
1129             mTransitioningViews.remove(view);
1130             updateTransitioning();
1131         }
1132 
1133         @Override
run()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 
updateTransitioning()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