• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.settingslib.notification;
18 
19 import android.app.ActivityManager;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.provider.Settings;
25 import android.service.notification.Condition;
26 import android.service.notification.ZenModeConfig;
27 import android.support.annotation.VisibleForTesting;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.widget.CompoundButton;
31 import android.widget.ImageView;
32 import android.widget.LinearLayout;
33 import android.widget.RadioButton;
34 import android.widget.RadioGroup;
35 import android.widget.ScrollView;
36 import android.widget.TextView;
37 
38 import com.android.internal.logging.MetricsLogger;
39 import com.android.internal.logging.nano.MetricsProto;
40 import com.android.internal.policy.PhoneWindow;
41 import com.android.settingslib.R;
42 
43 import java.util.Arrays;
44 
45 public class ZenDurationDialog {
46     private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
47     @VisibleForTesting protected static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
48     @VisibleForTesting protected static final int MAX_BUCKET_MINUTES =
49             MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
50     private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
51     @VisibleForTesting protected int mBucketIndex = -1;
52 
53     @VisibleForTesting protected static final int FOREVER_CONDITION_INDEX = 0;
54     @VisibleForTesting protected static final int COUNTDOWN_CONDITION_INDEX = 1;
55     @VisibleForTesting protected static final int ALWAYS_ASK_CONDITION_INDEX = 2;
56 
57     @VisibleForTesting protected Context mContext;
58     @VisibleForTesting protected LinearLayout mZenRadioGroupContent;
59     private RadioGroup mZenRadioGroup;
60     private int MAX_MANUAL_DND_OPTIONS = 3;
61 
62     @VisibleForTesting protected LayoutInflater mLayoutInflater;
63 
ZenDurationDialog(Context context)64     public ZenDurationDialog(Context context) {
65         mContext = context;
66     }
67 
createDialog()68     public Dialog createDialog() {
69         int zenDuration = Settings.Global.getInt(
70                 mContext.getContentResolver(), Settings.Global.ZEN_DURATION,
71                 Settings.Global.ZEN_DURATION_FOREVER);
72 
73         final AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
74                 .setTitle(R.string.zen_mode_duration_settings_title)
75                 .setNegativeButton(R.string.cancel, null)
76                 .setPositiveButton(R.string.okay,
77                         new DialogInterface.OnClickListener() {
78                             @Override
79                             public void onClick(DialogInterface dialog, int which) {
80                                 updateZenDuration(zenDuration);
81                             }
82                         });
83 
84         View contentView = getContentView();
85         setupRadioButtons(zenDuration);
86         builder.setView(contentView);
87         return builder.create();
88     }
89 
90     @VisibleForTesting
updateZenDuration(int currZenDuration)91     protected void updateZenDuration(int currZenDuration) {
92         final int checkedRadioButtonId = mZenRadioGroup.getCheckedRadioButtonId();
93 
94         int newZenDuration = Settings.Global.getInt(
95                 mContext.getContentResolver(), Settings.Global.ZEN_DURATION,
96                 Settings.Global.ZEN_DURATION_FOREVER);
97         switch (checkedRadioButtonId) {
98             case FOREVER_CONDITION_INDEX:
99                 newZenDuration = Settings.Global.ZEN_DURATION_FOREVER;
100                 MetricsLogger.action(mContext,
101                         MetricsProto.MetricsEvent.
102                                 NOTIFICATION_ZEN_MODE_DURATION_FOREVER);
103                 break;
104             case COUNTDOWN_CONDITION_INDEX:
105                 ConditionTag tag = getConditionTagAt(checkedRadioButtonId);
106                 newZenDuration = tag.countdownZenDuration;
107                 MetricsLogger.action(mContext,
108                         MetricsProto.MetricsEvent.
109                                 NOTIFICATION_ZEN_MODE_DURATION_TIME,
110                         newZenDuration);
111                 break;
112             case ALWAYS_ASK_CONDITION_INDEX:
113                 newZenDuration = Settings.Global.ZEN_DURATION_PROMPT;
114                 MetricsLogger.action(mContext,
115                         MetricsProto.MetricsEvent.
116                                 NOTIFICATION_ZEN_MODE_DURATION_PROMPT);
117                 break;
118         }
119 
120         if (currZenDuration != newZenDuration) {
121             Settings.Global.putInt(mContext.getContentResolver(),
122                     Settings.Global.ZEN_DURATION, newZenDuration);
123         }
124     }
125 
126     @VisibleForTesting
getContentView()127     protected View getContentView() {
128         if (mLayoutInflater == null) {
129             mLayoutInflater = new PhoneWindow(mContext).getLayoutInflater();
130         }
131         View contentView = mLayoutInflater.inflate(R.layout.zen_mode_duration_dialog,
132                 null);
133         ScrollView container = (ScrollView) contentView.findViewById(R.id.zen_duration_container);
134 
135         mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons);
136         mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content);
137 
138         for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) {
139             final View radioButton = mLayoutInflater.inflate(R.layout.zen_mode_radio_button,
140                     mZenRadioGroup, false);
141             mZenRadioGroup.addView(radioButton);
142             radioButton.setId(i);
143 
144             final View radioButtonContent = mLayoutInflater.inflate(R.layout.zen_mode_condition,
145                     mZenRadioGroupContent, false);
146             radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS);
147             mZenRadioGroupContent.addView(radioButtonContent);
148         }
149 
150         return contentView;
151     }
152 
153     @VisibleForTesting
setupRadioButtons(int zenDuration)154     protected void setupRadioButtons(int zenDuration) {
155         int checkedIndex = ALWAYS_ASK_CONDITION_INDEX;
156         if (zenDuration == 0) {
157             checkedIndex = FOREVER_CONDITION_INDEX;
158         } else if (zenDuration > 0) {
159             checkedIndex = COUNTDOWN_CONDITION_INDEX;
160         }
161 
162         bindTag(zenDuration, mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
163                 FOREVER_CONDITION_INDEX);
164         bindTag(zenDuration, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
165                 COUNTDOWN_CONDITION_INDEX);
166         bindTag(zenDuration, mZenRadioGroupContent.getChildAt(ALWAYS_ASK_CONDITION_INDEX),
167                 ALWAYS_ASK_CONDITION_INDEX);
168         getConditionTagAt(checkedIndex).rb.setChecked(true);
169     }
170 
bindTag(final int currZenDuration, final View row, final int rowIndex)171     private void bindTag(final int currZenDuration, final View row, final int rowIndex) {
172         final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() :
173                 new ConditionTag();
174         row.setTag(tag);
175 
176         if (tag.rb == null) {
177             tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowIndex);
178         }
179 
180         // if duration is set to forever or always prompt, then countdown time defaults to 1 hour
181         if (currZenDuration <= 0) {
182             tag.countdownZenDuration = MINUTE_BUCKETS[DEFAULT_BUCKET_INDEX];
183         } else {
184             tag.countdownZenDuration = currZenDuration;
185         }
186 
187         tag.rb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
188             @Override
189             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
190                 if (isChecked) {
191                     tag.rb.setChecked(true);
192                 }
193             }
194         });
195 
196         updateUi(tag, row, rowIndex);
197     }
198 
199     @VisibleForTesting
getConditionTagAt(int index)200     protected ConditionTag getConditionTagAt(int index) {
201         return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
202     }
203 
204 
setupUi(ConditionTag tag, View row)205     private void setupUi(ConditionTag tag, View row) {
206         if (tag.lines == null) {
207             tag.lines = row.findViewById(android.R.id.content);
208             tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
209         }
210 
211         if (tag.line1 == null) {
212             tag.line1 = (TextView) row.findViewById(android.R.id.text1);
213         }
214 
215         // text2 is not used in zen duration dialog
216         row.findViewById(android.R.id.text2).setVisibility(View.GONE);
217 
218         tag.lines.setOnClickListener(new View.OnClickListener() {
219             @Override
220             public void onClick(View v) {
221                 tag.rb.setChecked(true);
222             }
223         });
224     }
225 
updateButtons(ConditionTag tag, View row, int rowIndex)226     private void updateButtons(ConditionTag tag, View row, int rowIndex) {
227         // minus button
228         final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
229         button1.setOnClickListener(new View.OnClickListener() {
230             @Override
231             public void onClick(View v) {
232                 onClickTimeButton(row, tag, false /*down*/, rowIndex);
233             }
234         });
235 
236         // plus button
237         final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
238         button2.setOnClickListener(new View.OnClickListener() {
239             @Override
240             public void onClick(View v) {
241                 onClickTimeButton(row, tag, true /*up*/, rowIndex);
242             }
243         });
244 
245         final long time = tag.countdownZenDuration;
246         if (rowIndex == COUNTDOWN_CONDITION_INDEX) {
247             button1.setVisibility(View.VISIBLE);
248             button2.setVisibility(View.VISIBLE);
249 
250             button1.setEnabled(time > MIN_BUCKET_MINUTES);
251             button2.setEnabled(tag.countdownZenDuration != MAX_BUCKET_MINUTES);
252 
253             button1.setAlpha(button1.isEnabled() ? 1f : .5f);
254             button2.setAlpha(button2.isEnabled() ? 1f : .5f);
255         } else {
256             button1.setVisibility(View.GONE);
257             button2.setVisibility(View.GONE);
258         }
259     }
260 
261     @VisibleForTesting
updateUi(ConditionTag tag, View row, int rowIndex)262     protected void updateUi(ConditionTag tag, View row, int rowIndex) {
263         if (tag.lines == null) {
264             setupUi(tag, row);
265         }
266 
267         updateButtons(tag, row, rowIndex);
268 
269         String radioContentText = "";
270         switch (rowIndex) {
271             case FOREVER_CONDITION_INDEX:
272                 radioContentText = mContext.getString(
273                         com.android.internal.R.string.zen_mode_forever);
274                 break;
275             case COUNTDOWN_CONDITION_INDEX:
276                 Condition condition = ZenModeConfig.toTimeCondition(mContext,
277                         tag.countdownZenDuration, ActivityManager.getCurrentUser(), false);
278                 radioContentText = condition.line1;
279                 break;
280             case ALWAYS_ASK_CONDITION_INDEX:
281                 radioContentText = mContext.getString(
282                         R.string.zen_mode_duration_always_prompt_title);
283                 break;
284         }
285 
286         tag.line1.setText(radioContentText);
287     }
288 
289     @VisibleForTesting
onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId)290     protected void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
291         int newDndTimeDuration = -1;
292         final int N = MINUTE_BUCKETS.length;
293         if (mBucketIndex == -1) {
294             // not on a known index, search for the next or prev bucket by time
295             final long time = tag.countdownZenDuration;
296             for (int i = 0; i < N; i++) {
297                 int j = up ? i : N - 1 - i;
298                 final int bucketMinutes = MINUTE_BUCKETS[j];
299                 if (up && bucketMinutes > time || !up && bucketMinutes < time) {
300                     mBucketIndex = j;
301                     newDndTimeDuration = bucketMinutes;
302                     break;
303                 }
304             }
305             if (newDndTimeDuration == -1) {
306                 mBucketIndex = DEFAULT_BUCKET_INDEX;
307                 newDndTimeDuration = MINUTE_BUCKETS[mBucketIndex];
308             }
309         } else {
310             // on a known index, simply increment or decrement
311             mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
312             newDndTimeDuration = MINUTE_BUCKETS[mBucketIndex];
313         }
314         tag.countdownZenDuration = newDndTimeDuration;
315         bindTag(newDndTimeDuration, row, rowId);
316         tag.rb.setChecked(true);
317     }
318 
319     // used as the view tag on condition rows
320     @VisibleForTesting
321     protected static class ConditionTag {
322         public RadioButton rb;
323         public View lines;
324         public TextView line1;
325         public int countdownZenDuration; // only important for countdown radio button
326     }
327 }
328