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