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