blob: c1681c7c36d2bc623be17ad009c51b73c4b07c30 [file] [log] [blame]
John Spurlock86005342014-05-23 11:58:00 -04001/*
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
17package com.android.systemui.volume;
18
John Spurlockae641c92014-06-30 18:11:40 -040019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
John Spurlock86005342014-05-23 11:58:00 -040021import android.content.Context;
22import android.content.Intent;
John Spurlock856edeb2014-06-01 20:36:47 -040023import android.content.SharedPreferences;
24import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
John Spurlockb71e68f2014-07-17 23:09:40 -040025import android.content.res.Resources;
John Spurlock86005342014-05-23 11:58:00 -040026import android.net.Uri;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.provider.Settings;
John Spurlockae641c92014-06-30 18:11:40 -040031import android.provider.Settings.Global;
John Spurlock86005342014-05-23 11:58:00 -040032import android.service.notification.Condition;
John Spurlock856edeb2014-06-01 20:36:47 -040033import android.service.notification.ZenModeConfig;
John Spurlock86005342014-05-23 11:58:00 -040034import android.util.AttributeSet;
John Spurlock856edeb2014-06-01 20:36:47 -040035import android.util.Log;
John Spurlock8f8ecd62014-08-27 17:46:03 -040036import android.util.MathUtils;
John Spurlock86005342014-05-23 11:58:00 -040037import android.view.LayoutInflater;
38import android.view.View;
John Spurlockae641c92014-06-30 18:11:40 -040039import android.view.animation.AnimationUtils;
40import android.view.animation.Interpolator;
John Spurlock86005342014-05-23 11:58:00 -040041import android.widget.CompoundButton;
42import android.widget.CompoundButton.OnCheckedChangeListener;
43import android.widget.ImageView;
44import android.widget.LinearLayout;
45import android.widget.RadioButton;
46import android.widget.TextView;
47
48import com.android.systemui.R;
49import com.android.systemui.statusbar.policy.ZenModeController;
50
51import java.util.Arrays;
John Spurlock856edeb2014-06-01 20:36:47 -040052import java.util.Objects;
John Spurlock86005342014-05-23 11:58:00 -040053
54public class ZenModePanel extends LinearLayout {
John Spurlockae641c92014-06-30 18:11:40 -040055 private static final String TAG = "ZenModePanel";
56 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
John Spurlock856edeb2014-06-01 20:36:47 -040057
John Spurlock50806fc2014-07-15 10:22:02 -040058 private static final int SECONDS_MS = 1000;
59 private static final int MINUTES_MS = 60 * SECONDS_MS;
60
John Spurlockae641c92014-06-30 18:11:40 -040061 private static final int[] MINUTE_BUCKETS = DEBUG
John Spurlock50806fc2014-07-15 10:22:02 -040062 ? new int[] { 0, 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 }
Jason Monk4dd81462014-09-08 10:13:59 -040063 : ZenModeConfig.MINUTE_BUCKETS;
John Spurlock856edeb2014-06-01 20:36:47 -040064 private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
65 private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
66 private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
John Spurlockae641c92014-06-30 18:11:40 -040067 private static final int FOREVER_CONDITION_INDEX = 0;
68 private static final int TIME_CONDITION_INDEX = 1;
69 private static final int FIRST_CONDITION_INDEX = 2;
70 private static final float SILENT_HINT_PULSE_SCALE = 1.1f;
John Spurlock856edeb2014-06-01 20:36:47 -040071
John Spurlock86005342014-05-23 11:58:00 -040072 public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
73
John Spurlock7f1df5e2014-05-31 19:11:40 -040074 private final Context mContext;
John Spurlock86005342014-05-23 11:58:00 -040075 private final LayoutInflater mInflater;
John Spurlock86005342014-05-23 11:58:00 -040076 private final H mHandler = new H();
John Spurlock8f8ecd62014-08-27 17:46:03 -040077 private final Prefs mPrefs;
John Spurlockae641c92014-06-30 18:11:40 -040078 private final Interpolator mFastOutSlowInInterpolator;
John Spurlock8f8ecd62014-08-27 17:46:03 -040079 private final int mSubheadWarningColor;
80 private final int mSubheadColor;
John Spurlock27c7b922014-09-23 14:59:51 -040081 private final ZenToast mZenToast;
John Spurlock856edeb2014-06-01 20:36:47 -040082
John Spurlockeb2727b2014-07-19 23:11:36 -040083 private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
John Spurlockae641c92014-06-30 18:11:40 -040084
85 private SegmentedButtons mZenButtons;
86 private View mZenSubhead;
87 private TextView mZenSubheadCollapsed;
88 private TextView mZenSubheadExpanded;
John Spurlock7f8f22a2014-07-02 18:54:17 -040089 private View mMoreSettings;
John Spurlockae641c92014-06-30 18:11:40 -040090 private LinearLayout mZenConditions;
John Spurlockae641c92014-06-30 18:11:40 -040091
John Spurlock86005342014-05-23 11:58:00 -040092 private Callback mCallback;
93 private ZenModeController mController;
94 private boolean mRequestingConditions;
John Spurlock4db0d982014-08-13 09:19:03 -040095 private Condition mExitCondition;
96 private String mExitConditionText;
John Spurlock856edeb2014-06-01 20:36:47 -040097 private int mBucketIndex = -1;
John Spurlockae641c92014-06-30 18:11:40 -040098 private boolean mExpanded;
John Spurlockeb2727b2014-07-19 23:11:36 -040099 private boolean mHidden = false;
John Spurlockfbb3d6f2014-07-17 11:17:11 -0400100 private int mSessionZen;
John Spurlock8f8ecd62014-08-27 17:46:03 -0400101 private int mAttachedZen;
John Spurlock4db0d982014-08-13 09:19:03 -0400102 private Condition mSessionExitCondition;
John Spurlock4db0d982014-08-13 09:19:03 -0400103 private Condition[] mConditions;
104 private Condition mTimeCondition;
John Spurlock86005342014-05-23 11:58:00 -0400105
106 public ZenModePanel(Context context, AttributeSet attrs) {
107 super(context, attrs);
John Spurlock7f1df5e2014-05-31 19:11:40 -0400108 mContext = context;
John Spurlock8f8ecd62014-08-27 17:46:03 -0400109 mPrefs = new Prefs();
John Spurlockf7d22132014-07-10 19:03:00 -0400110 mInflater = LayoutInflater.from(mContext.getApplicationContext());
John Spurlockae641c92014-06-30 18:11:40 -0400111 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
112 android.R.interpolator.fast_out_slow_in);
John Spurlockb71e68f2014-07-17 23:09:40 -0400113 final Resources res = mContext.getResources();
John Spurlock8f8ecd62014-08-27 17:46:03 -0400114 mSubheadWarningColor = res.getColor(R.color.system_warning_color);
115 mSubheadColor = res.getColor(R.color.qs_subhead);
John Spurlock27c7b922014-09-23 14:59:51 -0400116 mZenToast = new ZenToast(mContext);
John Spurlock856edeb2014-06-01 20:36:47 -0400117 if (DEBUG) Log.d(mTag, "new ZenModePanel");
118 }
119
John Spurlock86005342014-05-23 11:58:00 -0400120 @Override
121 protected void onFinishInflate() {
122 super.onFinishInflate();
John Spurlockae641c92014-06-30 18:11:40 -0400123
124 mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
John Spurlock4291fb72014-09-16 17:02:23 -0400125 mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS);
126 mZenButtons.addButton(R.string.interruption_level_priority,
127 Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
128 mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
John Spurlockae641c92014-06-30 18:11:40 -0400129 mZenButtons.setCallback(mZenButtonsCallback);
130
131 mZenSubhead = findViewById(R.id.zen_subhead);
132
133 mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed);
134 mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() {
135 @Override
136 public void onClick(View v) {
137 setExpanded(true);
138 fireInteraction();
139 }
140 });
141
142 mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded);
John Spurlockae641c92014-06-30 18:11:40 -0400143
144 mMoreSettings = findViewById(R.id.zen_more_settings);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400145 mMoreSettings.setOnClickListener(new View.OnClickListener() {
John Spurlock86005342014-05-23 11:58:00 -0400146 @Override
147 public void onClick(View v) {
148 fireMoreSettings();
John Spurlockae641c92014-06-30 18:11:40 -0400149 fireInteraction();
John Spurlock86005342014-05-23 11:58:00 -0400150 }
151 });
John Spurlockae641c92014-06-30 18:11:40 -0400152
153 mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
John Spurlock86005342014-05-23 11:58:00 -0400154 }
155
156 @Override
John Spurlock856edeb2014-06-01 20:36:47 -0400157 protected void onAttachedToWindow() {
158 super.onAttachedToWindow();
159 if (DEBUG) Log.d(mTag, "onAttachedToWindow");
John Spurlock27c7b922014-09-23 14:59:51 -0400160 mZenToast.hide();
John Spurlock8f8ecd62014-08-27 17:46:03 -0400161 mAttachedZen = getSelectedZen(-1);
162 mSessionZen = mAttachedZen;
John Spurlock4db0d982014-08-13 09:19:03 -0400163 mSessionExitCondition = copy(mExitCondition);
John Spurlockae641c92014-06-30 18:11:40 -0400164 refreshExitConditionText();
John Spurlock50806fc2014-07-15 10:22:02 -0400165 updateWidgets();
John Spurlock856edeb2014-06-01 20:36:47 -0400166 }
167
168 @Override
169 protected void onDetachedFromWindow() {
170 super.onDetachedFromWindow();
171 if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
John Spurlock8f8ecd62014-08-27 17:46:03 -0400172 checkForAttachedZenChange();
173 mAttachedZen = -1;
John Spurlockfbb3d6f2014-07-17 11:17:11 -0400174 mSessionZen = -1;
John Spurlock4db0d982014-08-13 09:19:03 -0400175 mSessionExitCondition = null;
John Spurlockae641c92014-06-30 18:11:40 -0400176 setExpanded(false);
John Spurlock856edeb2014-06-01 20:36:47 -0400177 }
178
John Spurlockeb2727b2014-07-19 23:11:36 -0400179 public void setHidden(boolean hidden) {
180 if (mHidden == hidden) return;
181 mHidden = hidden;
182 updateWidgets();
183 }
184
John Spurlock8f8ecd62014-08-27 17:46:03 -0400185 private void checkForAttachedZenChange() {
186 final int selectedZen = getSelectedZen(-1);
187 if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen);
188 if (selectedZen != mAttachedZen) {
189 if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen);
190 if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
191 mPrefs.trackNoneSelected();
192 }
John Spurlock27c7b922014-09-23 14:59:51 -0400193 if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS
194 || selectedZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
195 mZenToast.show(selectedZen);
196 }
John Spurlock8f8ecd62014-08-27 17:46:03 -0400197 }
198 }
199
John Spurlockae641c92014-06-30 18:11:40 -0400200 private void setExpanded(boolean expanded) {
201 if (expanded == mExpanded) return;
202 mExpanded = expanded;
203 updateWidgets();
204 setRequestingConditions(mExpanded);
205 fireExpanded();
John Spurlock86005342014-05-23 11:58:00 -0400206 }
207
208 /** Start or stop requesting relevant zen mode exit conditions */
209 private void setRequestingConditions(boolean requesting) {
210 if (mRequestingConditions == requesting) return;
John Spurlock856edeb2014-06-01 20:36:47 -0400211 if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
John Spurlock86005342014-05-23 11:58:00 -0400212 mRequestingConditions = requesting;
John Spurlock856edeb2014-06-01 20:36:47 -0400213 if (mController != null) {
214 mController.requestConditions(mRequestingConditions);
John Spurlock86005342014-05-23 11:58:00 -0400215 }
John Spurlock856edeb2014-06-01 20:36:47 -0400216 if (mRequestingConditions) {
John Spurlock4db0d982014-08-13 09:19:03 -0400217 mTimeCondition = parseExistingTimeCondition(mExitCondition);
218 if (mTimeCondition != null) {
John Spurlock856edeb2014-06-01 20:36:47 -0400219 mBucketIndex = -1;
220 } else {
221 mBucketIndex = DEFAULT_BUCKET_INDEX;
Jason Monk4dd81462014-09-08 10:13:59 -0400222 mTimeCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
John Spurlock856edeb2014-06-01 20:36:47 -0400223 }
224 if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
John Spurlock4db0d982014-08-13 09:19:03 -0400225 mConditions = null; // reset conditions
226 handleUpdateConditions();
John Spurlock856edeb2014-06-01 20:36:47 -0400227 } else {
John Spurlockae641c92014-06-30 18:11:40 -0400228 mZenConditions.removeAllViews();
John Spurlock856edeb2014-06-01 20:36:47 -0400229 }
John Spurlock86005342014-05-23 11:58:00 -0400230 }
231
John Spurlockeb2727b2014-07-19 23:11:36 -0400232 public void init(ZenModeController controller) {
John Spurlock86005342014-05-23 11:58:00 -0400233 mController = controller;
John Spurlock4db0d982014-08-13 09:19:03 -0400234 setExitCondition(mController.getExitCondition());
John Spurlockae641c92014-06-30 18:11:40 -0400235 refreshExitConditionText();
John Spurlockfbb3d6f2014-07-17 11:17:11 -0400236 mSessionZen = getSelectedZen(-1);
John Spurlockae641c92014-06-30 18:11:40 -0400237 handleUpdateZen(mController.getZen());
John Spurlock4db0d982014-08-13 09:19:03 -0400238 if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
John Spurlockae641c92014-06-30 18:11:40 -0400239 mZenConditions.removeAllViews();
John Spurlock856edeb2014-06-01 20:36:47 -0400240 mController.addCallback(mZenCallback);
John Spurlockae641c92014-06-30 18:11:40 -0400241 }
242
Jason Monke2f47712014-09-09 09:35:55 -0400243 public void updateLocale() {
John Spurlock4291fb72014-09-16 17:02:23 -0400244 mZenButtons.updateLocale();
Jason Monke2f47712014-09-09 09:35:55 -0400245 }
246
John Spurlock4db0d982014-08-13 09:19:03 -0400247 private void setExitCondition(Condition exitCondition) {
248 if (sameConditionId(mExitCondition, exitCondition)) return;
249 mExitCondition = exitCondition;
John Spurlockae641c92014-06-30 18:11:40 -0400250 refreshExitConditionText();
John Spurlock89f060a2014-07-16 21:03:15 -0400251 updateWidgets();
John Spurlockae641c92014-06-30 18:11:40 -0400252 }
253
John Spurlock4db0d982014-08-13 09:19:03 -0400254 private static Uri getConditionId(Condition condition) {
255 return condition != null ? condition.id : null;
256 }
257
258 private static boolean sameConditionId(Condition lhs, Condition rhs) {
259 return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
260 }
261
262 private static Condition copy(Condition condition) {
263 return condition == null ? null : condition.copy();
264 }
265
John Spurlockae641c92014-06-30 18:11:40 -0400266 private void refreshExitConditionText() {
Jason Monk4dd81462014-09-08 10:13:59 -0400267 final String forever = mContext.getString(com.android.internal.R.string.zen_mode_forever);
John Spurlock4db0d982014-08-13 09:19:03 -0400268 if (mExitCondition == null) {
John Spurlockbc5858f2014-07-11 14:33:18 -0400269 mExitConditionText = forever;
John Spurlock4db0d982014-08-13 09:19:03 -0400270 } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) {
271 final Condition condition = parseExistingTimeCondition(mExitCondition);
John Spurlockbc5858f2014-07-11 14:33:18 -0400272 mExitConditionText = condition != null ? condition.summary : forever;
John Spurlockae641c92014-06-30 18:11:40 -0400273 } else {
John Spurlock4db0d982014-08-13 09:19:03 -0400274 mExitConditionText = mExitCondition.summary;
John Spurlock856edeb2014-06-01 20:36:47 -0400275 }
John Spurlock86005342014-05-23 11:58:00 -0400276 }
277
278 public void setCallback(Callback callback) {
279 mCallback = callback;
280 }
281
John Spurlockae641c92014-06-30 18:11:40 -0400282 public void showSilentHint() {
283 if (DEBUG) Log.d(mTag, "showSilentHint");
284 if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
285 final View noneButton = mZenButtons.getChildAt(0);
286 if (noneButton.getScaleX() != 1) return; // already running
287 noneButton.animate().cancel();
288 noneButton.animate().scaleX(SILENT_HINT_PULSE_SCALE).scaleY(SILENT_HINT_PULSE_SCALE)
289 .setInterpolator(mFastOutSlowInInterpolator)
290 .setListener(new AnimatorListenerAdapter() {
291 @Override
292 public void onAnimationEnd(Animator animation) {
293 noneButton.animate().scaleX(1).scaleY(1).setListener(null);
294 }
295 });
296 }
297
298 private void handleUpdateZen(int zen) {
John Spurlockfbb3d6f2014-07-17 11:17:11 -0400299 if (mSessionZen != -1 && mSessionZen != zen) {
John Spurlockae641c92014-06-30 18:11:40 -0400300 setExpanded(zen != Global.ZEN_MODE_OFF);
John Spurlockfbb3d6f2014-07-17 11:17:11 -0400301 mSessionZen = zen;
John Spurlockae641c92014-06-30 18:11:40 -0400302 }
303 mZenButtons.setSelectedValue(zen);
304 updateWidgets();
305 }
306
307 private int getSelectedZen(int defValue) {
308 final Object zen = mZenButtons.getSelectedValue();
309 return zen != null ? (Integer) zen : defValue;
310 }
311
312 private void updateWidgets() {
313 final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
314 final boolean zenOff = zen == Global.ZEN_MODE_OFF;
315 final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
316 final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
John Spurlockeb2727b2014-07-19 23:11:36 -0400317 final boolean expanded = !mHidden && mExpanded;
John Spurlockae641c92014-06-30 18:11:40 -0400318
John Spurlockeb2727b2014-07-19 23:11:36 -0400319 mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
John Spurlock89fe1cb2014-08-06 10:30:42 -0400320 mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE);
John Spurlockeb2727b2014-07-19 23:11:36 -0400321 mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE);
322 mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
323 mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
324 mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
John Spurlockae641c92014-06-30 18:11:40 -0400325
326 if (zenNone) {
John Spurlock86d04fd2014-07-10 18:37:45 -0400327 mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
John Spurlockae641c92014-06-30 18:11:40 -0400328 mZenSubheadCollapsed.setText(mExitConditionText);
329 } else if (zenImportant) {
330 mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
331 mZenSubheadCollapsed.setText(mExitConditionText);
332 }
John Spurlock8f8ecd62014-08-27 17:46:03 -0400333 mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous()
334 ? mSubheadWarningColor : mSubheadColor);
John Spurlock89f060a2014-07-16 21:03:15 -0400335 }
336
John Spurlock4db0d982014-08-13 09:19:03 -0400337 private Condition parseExistingTimeCondition(Condition condition) {
338 if (condition == null) return null;
339 final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
John Spurlock856edeb2014-06-01 20:36:47 -0400340 if (time == 0) return null;
341 final long span = time - System.currentTimeMillis();
342 if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
Jason Monk4dd81462014-09-08 10:13:59 -0400343 return ZenModeConfig.toTimeCondition(time, Math.round(span / (float) MINUTES_MS));
John Spurlock86005342014-05-23 11:58:00 -0400344 }
345
346 private void handleUpdateConditions(Condition[] conditions) {
John Spurlock4db0d982014-08-13 09:19:03 -0400347 mConditions = conditions;
348 handleUpdateConditions();
349 }
350
351 private void handleUpdateConditions() {
352 final int conditionCount = mConditions == null ? 0 : mConditions.length;
353 if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount);
354 for (int i = mZenConditions.getChildCount() - 1; i >= FIRST_CONDITION_INDEX; i--) {
John Spurlockae641c92014-06-30 18:11:40 -0400355 mZenConditions.removeViewAt(i);
John Spurlock86005342014-05-23 11:58:00 -0400356 }
John Spurlock4db0d982014-08-13 09:19:03 -0400357 // forever
John Spurlockae641c92014-06-30 18:11:40 -0400358 bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
John Spurlock4db0d982014-08-13 09:19:03 -0400359 // countdown
360 bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
361 // provider conditions
362 boolean foundDowntime = false;
363 for (int i = 0; i < conditionCount; i++) {
364 bind(mConditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i));
365 foundDowntime |= isDowntime(mConditions[i]);
John Spurlock86005342014-05-23 11:58:00 -0400366 }
John Spurlock4db0d982014-08-13 09:19:03 -0400367 // ensure downtime exists, if active
368 if (isDowntime(mSessionExitCondition) && !foundDowntime) {
369 bind(mSessionExitCondition, null);
370 }
371 // ensure something is selected
372 checkForDefault();
373 }
374
375 private static boolean isDowntime(Condition c) {
376 return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c));
John Spurlock86005342014-05-23 11:58:00 -0400377 }
378
John Spurlock856edeb2014-06-01 20:36:47 -0400379 private ConditionTag getConditionTagAt(int index) {
John Spurlockae641c92014-06-30 18:11:40 -0400380 return (ConditionTag) mZenConditions.getChildAt(index).getTag();
John Spurlock856edeb2014-06-01 20:36:47 -0400381 }
382
383 private void checkForDefault() {
384 // are we left without anything selected? if so, set a default
John Spurlockae641c92014-06-30 18:11:40 -0400385 for (int i = 0; i < mZenConditions.getChildCount(); i++) {
John Spurlock856edeb2014-06-01 20:36:47 -0400386 if (getConditionTagAt(i).rb.isChecked()) {
John Spurlockfbb3d6f2014-07-17 11:17:11 -0400387 if (DEBUG) Log.d(mTag, "Not selecting a default, checked="
John Spurlock4db0d982014-08-13 09:19:03 -0400388 + getConditionTagAt(i).condition);
John Spurlock856edeb2014-06-01 20:36:47 -0400389 return;
390 }
391 }
392 if (DEBUG) Log.d(mTag, "Selecting a default");
John Spurlock8f8ecd62014-08-27 17:46:03 -0400393 final int favoriteIndex = mPrefs.getMinuteIndex();
John Spurlock856edeb2014-06-01 20:36:47 -0400394 if (favoriteIndex == -1) {
John Spurlockae641c92014-06-30 18:11:40 -0400395 getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
John Spurlock856edeb2014-06-01 20:36:47 -0400396 } else {
Jason Monk4dd81462014-09-08 10:13:59 -0400397 mTimeCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[favoriteIndex]);
John Spurlock856edeb2014-06-01 20:36:47 -0400398 mBucketIndex = favoriteIndex;
John Spurlock4db0d982014-08-13 09:19:03 -0400399 bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
John Spurlockae641c92014-06-30 18:11:40 -0400400 getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true);
John Spurlock856edeb2014-06-01 20:36:47 -0400401 }
402 }
403
John Spurlock4db0d982014-08-13 09:19:03 -0400404 private void handleExitConditionChanged(Condition exitCondition) {
405 setExitCondition(exitCondition);
406 if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
John Spurlockae641c92014-06-30 18:11:40 -0400407 final int N = mZenConditions.getChildCount();
John Spurlock856edeb2014-06-01 20:36:47 -0400408 for (int i = 0; i < N; i++) {
409 final ConditionTag tag = getConditionTagAt(i);
John Spurlock4db0d982014-08-13 09:19:03 -0400410 tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition));
John Spurlock856edeb2014-06-01 20:36:47 -0400411 }
John Spurlock86005342014-05-23 11:58:00 -0400412 }
413
414 private void bind(final Condition condition, View convertView) {
415 final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE;
416 final View row;
417 if (convertView == null) {
418 row = mInflater.inflate(R.layout.zen_mode_condition, this, false);
John Spurlock856edeb2014-06-01 20:36:47 -0400419 if (DEBUG) Log.d(mTag, "Adding new condition view for: " + condition);
John Spurlockae641c92014-06-30 18:11:40 -0400420 mZenConditions.addView(row);
John Spurlock86005342014-05-23 11:58:00 -0400421 } else {
422 row = convertView;
423 }
John Spurlock856edeb2014-06-01 20:36:47 -0400424 final ConditionTag tag =
425 row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
426 row.setTag(tag);
427 if (tag.rb == null) {
428 tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox);
429 }
John Spurlock4db0d982014-08-13 09:19:03 -0400430 tag.condition = condition;
John Spurlock856edeb2014-06-01 20:36:47 -0400431 tag.rb.setEnabled(enabled);
John Spurlock4db0d982014-08-13 09:19:03 -0400432 if (sameConditionId(mSessionExitCondition, tag.condition)) {
John Spurlock50806fc2014-07-15 10:22:02 -0400433 tag.rb.setChecked(true);
434 }
John Spurlock856edeb2014-06-01 20:36:47 -0400435 tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
John Spurlock86005342014-05-23 11:58:00 -0400436 @Override
437 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
John Spurlockae641c92014-06-30 18:11:40 -0400438 if (mExpanded && isChecked) {
John Spurlock4db0d982014-08-13 09:19:03 -0400439 if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition);
John Spurlockae641c92014-06-30 18:11:40 -0400440 final int N = mZenConditions.getChildCount();
John Spurlock856edeb2014-06-01 20:36:47 -0400441 for (int i = 0; i < N; i++) {
442 ConditionTag childTag = getConditionTagAt(i);
443 if (childTag == tag) continue;
444 childTag.rb.setChecked(false);
John Spurlock86005342014-05-23 11:58:00 -0400445 }
John Spurlock4db0d982014-08-13 09:19:03 -0400446 select(tag.condition);
John Spurlock86005342014-05-23 11:58:00 -0400447 fireInteraction();
448 }
449 }
450 });
451 final TextView title = (TextView) row.findViewById(android.R.id.title);
452 if (condition == null) {
Jason Monk4dd81462014-09-08 10:13:59 -0400453 title.setText(mContext.getString(com.android.internal.R.string.zen_mode_forever));
John Spurlock86005342014-05-23 11:58:00 -0400454 } else {
455 title.setText(condition.summary);
456 }
457 title.setEnabled(enabled);
John Spurlockae641c92014-06-30 18:11:40 -0400458 title.setAlpha(enabled ? 1 : .4f);
John Spurlock86005342014-05-23 11:58:00 -0400459 final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
460 button1.setOnClickListener(new OnClickListener() {
461 @Override
462 public void onClick(View v) {
John Spurlock856edeb2014-06-01 20:36:47 -0400463 onClickTimeButton(row, tag, false /*down*/);
John Spurlock86005342014-05-23 11:58:00 -0400464 }
465 });
466
467 final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
468 button2.setOnClickListener(new OnClickListener() {
469 @Override
470 public void onClick(View v) {
John Spurlock856edeb2014-06-01 20:36:47 -0400471 onClickTimeButton(row, tag, true /*up*/);
John Spurlock86005342014-05-23 11:58:00 -0400472 }
473 });
474 title.setOnClickListener(new OnClickListener() {
475 @Override
476 public void onClick(View v) {
John Spurlock856edeb2014-06-01 20:36:47 -0400477 tag.rb.setChecked(true);
John Spurlock86005342014-05-23 11:58:00 -0400478 fireInteraction();
479 }
480 });
John Spurlock856edeb2014-06-01 20:36:47 -0400481
John Spurlock4db0d982014-08-13 09:19:03 -0400482 final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition));
John Spurlock856edeb2014-06-01 20:36:47 -0400483 if (time > 0) {
484 if (mBucketIndex > -1) {
485 button1.setEnabled(mBucketIndex > 0);
486 button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
487 } else {
488 final long span = time - System.currentTimeMillis();
489 button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
Jason Monk4dd81462014-09-08 10:13:59 -0400490 final Condition maxCondition = ZenModeConfig.toTimeCondition(MAX_BUCKET_MINUTES);
John Spurlock856edeb2014-06-01 20:36:47 -0400491 button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
492 }
493
494 button1.setAlpha(button1.isEnabled() ? 1f : .5f);
495 button2.setAlpha(button2.isEnabled() ? 1f : .5f);
John Spurlock86005342014-05-23 11:58:00 -0400496 } else {
497 button1.setVisibility(View.GONE);
498 button2.setVisibility(View.GONE);
499 }
John Spurlock856edeb2014-06-01 20:36:47 -0400500 }
501
502 private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
503 Condition newCondition = null;
504 final int N = MINUTE_BUCKETS.length;
505 if (mBucketIndex == -1) {
506 // not on a known index, search for the next or prev bucket by time
John Spurlock4db0d982014-08-13 09:19:03 -0400507 final Uri conditionId = getConditionId(tag.condition);
508 final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
John Spurlock856edeb2014-06-01 20:36:47 -0400509 final long now = System.currentTimeMillis();
510 for (int i = 0; i < N; i++) {
511 int j = up ? i : N - 1 - i;
512 final int bucketMinutes = MINUTE_BUCKETS[j];
513 final long bucketTime = now + bucketMinutes * MINUTES_MS;
514 if (up && bucketTime > time || !up && bucketTime < time) {
515 mBucketIndex = j;
Jason Monk4dd81462014-09-08 10:13:59 -0400516 newCondition = ZenModeConfig.toTimeCondition(bucketTime, bucketMinutes);
John Spurlock856edeb2014-06-01 20:36:47 -0400517 break;
518 }
519 }
520 if (newCondition == null) {
521 mBucketIndex = DEFAULT_BUCKET_INDEX;
Jason Monk4dd81462014-09-08 10:13:59 -0400522 newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
John Spurlock856edeb2014-06-01 20:36:47 -0400523 }
524 } else {
525 // on a known index, simply increment or decrement
526 mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
Jason Monk4dd81462014-09-08 10:13:59 -0400527 newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
John Spurlock856edeb2014-06-01 20:36:47 -0400528 }
John Spurlock4db0d982014-08-13 09:19:03 -0400529 mTimeCondition = newCondition;
530 bind(mTimeCondition, row);
John Spurlock856edeb2014-06-01 20:36:47 -0400531 tag.rb.setChecked(true);
John Spurlock4db0d982014-08-13 09:19:03 -0400532 select(mTimeCondition);
John Spurlock856edeb2014-06-01 20:36:47 -0400533 fireInteraction();
534 }
535
John Spurlock4db0d982014-08-13 09:19:03 -0400536 private void select(Condition condition) {
537 if (DEBUG) Log.d(mTag, "select " + condition);
John Spurlock856edeb2014-06-01 20:36:47 -0400538 if (mController != null) {
John Spurlock4db0d982014-08-13 09:19:03 -0400539 mController.setExitCondition(condition);
John Spurlock856edeb2014-06-01 20:36:47 -0400540 }
John Spurlock4db0d982014-08-13 09:19:03 -0400541 setExitCondition(condition);
542 if (condition == null) {
John Spurlock8f8ecd62014-08-27 17:46:03 -0400543 mPrefs.setMinuteIndex(-1);
John Spurlock4db0d982014-08-13 09:19:03 -0400544 } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) {
John Spurlock8f8ecd62014-08-27 17:46:03 -0400545 mPrefs.setMinuteIndex(mBucketIndex);
John Spurlock86005342014-05-23 11:58:00 -0400546 }
John Spurlock4db0d982014-08-13 09:19:03 -0400547 mSessionExitCondition = copy(condition);
John Spurlock86005342014-05-23 11:58:00 -0400548 }
549
550 private void fireMoreSettings() {
551 if (mCallback != null) {
552 mCallback.onMoreSettings();
553 }
554 }
555
556 private void fireInteraction() {
557 if (mCallback != null) {
558 mCallback.onInteraction();
559 }
560 }
561
John Spurlockae641c92014-06-30 18:11:40 -0400562 private void fireExpanded() {
563 if (mCallback != null) {
564 mCallback.onExpanded(mExpanded);
565 }
566 }
567
John Spurlock86005342014-05-23 11:58:00 -0400568 private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
569 @Override
John Spurlockae641c92014-06-30 18:11:40 -0400570 public void onZenChanged(int zen) {
571 mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
572 }
573 @Override
John Spurlock86005342014-05-23 11:58:00 -0400574 public void onConditionsChanged(Condition[] conditions) {
575 mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
576 }
John Spurlock856edeb2014-06-01 20:36:47 -0400577
578 @Override
John Spurlock4db0d982014-08-13 09:19:03 -0400579 public void onExitConditionChanged(Condition exitCondition) {
580 mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
John Spurlock856edeb2014-06-01 20:36:47 -0400581 }
John Spurlock86005342014-05-23 11:58:00 -0400582 };
583
584 private final class H extends Handler {
585 private static final int UPDATE_CONDITIONS = 1;
John Spurlock856edeb2014-06-01 20:36:47 -0400586 private static final int EXIT_CONDITION_CHANGED = 2;
John Spurlockae641c92014-06-30 18:11:40 -0400587 private static final int UPDATE_ZEN = 3;
John Spurlock86005342014-05-23 11:58:00 -0400588
589 private H() {
590 super(Looper.getMainLooper());
591 }
592
593 @Override
594 public void handleMessage(Message msg) {
595 if (msg.what == UPDATE_CONDITIONS) {
John Spurlock89f060a2014-07-16 21:03:15 -0400596 handleUpdateConditions((Condition[]) msg.obj);
John Spurlock856edeb2014-06-01 20:36:47 -0400597 } else if (msg.what == EXIT_CONDITION_CHANGED) {
John Spurlock4db0d982014-08-13 09:19:03 -0400598 handleExitConditionChanged((Condition) msg.obj);
John Spurlockae641c92014-06-30 18:11:40 -0400599 } else if (msg.what == UPDATE_ZEN) {
600 handleUpdateZen(msg.arg1);
John Spurlock86005342014-05-23 11:58:00 -0400601 }
602 }
603 }
604
605 public interface Callback {
606 void onMoreSettings();
607 void onInteraction();
John Spurlockae641c92014-06-30 18:11:40 -0400608 void onExpanded(boolean expanded);
John Spurlock86005342014-05-23 11:58:00 -0400609 }
John Spurlock856edeb2014-06-01 20:36:47 -0400610
611 // used as the view tag on condition rows
612 private static class ConditionTag {
613 RadioButton rb;
John Spurlock4db0d982014-08-13 09:19:03 -0400614 Condition condition;
John Spurlock856edeb2014-06-01 20:36:47 -0400615 }
616
John Spurlock8f8ecd62014-08-27 17:46:03 -0400617 private final class Prefs implements OnSharedPreferenceChangeListener {
John Spurlock856edeb2014-06-01 20:36:47 -0400618 private static final String KEY_MINUTE_INDEX = "minuteIndex";
John Spurlock8f8ecd62014-08-27 17:46:03 -0400619 private static final String KEY_NONE_SELECTED = "noneSelected";
620
621 private final int mNoneDangerousThreshold;
John Spurlock856edeb2014-06-01 20:36:47 -0400622
623 private int mMinuteIndex;
John Spurlock8f8ecd62014-08-27 17:46:03 -0400624 private int mNoneSelected;
John Spurlock856edeb2014-06-01 20:36:47 -0400625
John Spurlock8f8ecd62014-08-27 17:46:03 -0400626 private Prefs() {
627 mNoneDangerousThreshold = mContext.getResources()
628 .getInteger(R.integer.zen_mode_alarm_warning_threshold);
John Spurlock856edeb2014-06-01 20:36:47 -0400629 prefs().registerOnSharedPreferenceChangeListener(this);
630 updateMinuteIndex();
John Spurlock8f8ecd62014-08-27 17:46:03 -0400631 updateNoneSelected();
632 }
633
634 public boolean isNoneDangerous() {
635 return mNoneSelected < mNoneDangerousThreshold;
636 }
637
638 public void trackNoneSelected() {
639 mNoneSelected = clampNoneSelected(mNoneSelected + 1);
640 if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
641 + mNoneDangerousThreshold);
642 prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply();
John Spurlock856edeb2014-06-01 20:36:47 -0400643 }
644
645 public int getMinuteIndex() {
646 return mMinuteIndex;
647 }
648
649 public void setMinuteIndex(int minuteIndex) {
John Spurlock8f8ecd62014-08-27 17:46:03 -0400650 minuteIndex = clampIndex(minuteIndex);
John Spurlock856edeb2014-06-01 20:36:47 -0400651 if (minuteIndex == mMinuteIndex) return;
John Spurlock8f8ecd62014-08-27 17:46:03 -0400652 mMinuteIndex = clampIndex(minuteIndex);
John Spurlock856edeb2014-06-01 20:36:47 -0400653 if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
654 prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply();
655 }
656
657 @Override
658 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
659 updateMinuteIndex();
John Spurlock8f8ecd62014-08-27 17:46:03 -0400660 updateNoneSelected();
John Spurlock856edeb2014-06-01 20:36:47 -0400661 }
662
663 private SharedPreferences prefs() {
664 return mContext.getSharedPreferences(ZenModePanel.class.getSimpleName(), 0);
665 }
666
667 private void updateMinuteIndex() {
John Spurlock8f8ecd62014-08-27 17:46:03 -0400668 mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
John Spurlock856edeb2014-06-01 20:36:47 -0400669 if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
670 }
671
John Spurlock8f8ecd62014-08-27 17:46:03 -0400672 private int clampIndex(int index) {
673 return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
674 }
675
676 private void updateNoneSelected() {
677 mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0));
678 if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
679 }
680
681 private int clampNoneSelected(int noneSelected) {
682 return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
John Spurlock856edeb2014-06-01 20:36:47 -0400683 }
684 }
John Spurlockae641c92014-06-30 18:11:40 -0400685
686 private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
687 @Override
688 public void onSelected(Object value) {
689 if (value != null && mZenButtons.isShown()) {
690 if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
691 mController.setZen((Integer) value);
John Spurlockae641c92014-06-30 18:11:40 -0400692 }
693 }
694 };
John Spurlock86005342014-05-23 11:58:00 -0400695}