| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settingslib.notification; |
| |
| import android.app.ActivityManager; |
| import android.app.Dialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.provider.Settings; |
| import android.service.notification.Condition; |
| import android.service.notification.ZenModeConfig; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.widget.CompoundButton; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.RadioButton; |
| import android.widget.RadioGroup; |
| import android.widget.ScrollView; |
| import android.widget.TextView; |
| |
| import androidx.annotation.VisibleForTesting; |
| import androidx.appcompat.app.AlertDialog; |
| |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto; |
| import com.android.internal.policy.PhoneWindow; |
| import com.android.settingslib.R; |
| |
| import java.util.Arrays; |
| |
| public class ZenDurationDialog { |
| private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS; |
| @VisibleForTesting protected static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0]; |
| @VisibleForTesting protected static final int MAX_BUCKET_MINUTES = |
| MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1]; |
| private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60); |
| @VisibleForTesting protected int mBucketIndex = -1; |
| |
| @VisibleForTesting protected static final int FOREVER_CONDITION_INDEX = 0; |
| @VisibleForTesting protected static final int COUNTDOWN_CONDITION_INDEX = 1; |
| @VisibleForTesting protected static final int ALWAYS_ASK_CONDITION_INDEX = 2; |
| |
| @VisibleForTesting protected Context mContext; |
| @VisibleForTesting protected LinearLayout mZenRadioGroupContent; |
| private RadioGroup mZenRadioGroup; |
| private int MAX_MANUAL_DND_OPTIONS = 3; |
| |
| @VisibleForTesting protected LayoutInflater mLayoutInflater; |
| |
| public ZenDurationDialog(Context context) { |
| mContext = context; |
| } |
| |
| public Dialog createDialog() { |
| final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); |
| setupDialog(builder); |
| return builder.create(); |
| } |
| |
| public void setupDialog(AlertDialog.Builder builder) { |
| int zenDuration = Settings.Secure.getInt( |
| mContext.getContentResolver(), Settings.Secure.ZEN_DURATION, |
| Settings.Secure.ZEN_DURATION_FOREVER); |
| |
| builder.setTitle(R.string.zen_mode_duration_settings_title) |
| .setNegativeButton(R.string.cancel, null) |
| .setPositiveButton(R.string.okay, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| updateZenDuration(zenDuration); |
| } |
| }); |
| |
| View contentView = getContentView(); |
| setupRadioButtons(zenDuration); |
| builder.setView(contentView); |
| } |
| |
| @VisibleForTesting |
| protected void updateZenDuration(int currZenDuration) { |
| final int checkedRadioButtonId = mZenRadioGroup.getCheckedRadioButtonId(); |
| |
| int newZenDuration = Settings.Secure.getInt( |
| mContext.getContentResolver(), Settings.Secure.ZEN_DURATION, |
| Settings.Secure.ZEN_DURATION_FOREVER); |
| switch (checkedRadioButtonId) { |
| case FOREVER_CONDITION_INDEX: |
| newZenDuration = Settings.Secure.ZEN_DURATION_FOREVER; |
| MetricsLogger.action(mContext, |
| MetricsProto.MetricsEvent. |
| NOTIFICATION_ZEN_MODE_DURATION_FOREVER); |
| break; |
| case COUNTDOWN_CONDITION_INDEX: |
| ConditionTag tag = getConditionTagAt(checkedRadioButtonId); |
| newZenDuration = tag.countdownZenDuration; |
| MetricsLogger.action(mContext, |
| MetricsProto.MetricsEvent. |
| NOTIFICATION_ZEN_MODE_DURATION_TIME, |
| newZenDuration); |
| break; |
| case ALWAYS_ASK_CONDITION_INDEX: |
| newZenDuration = Settings.Secure.ZEN_DURATION_PROMPT; |
| MetricsLogger.action(mContext, |
| MetricsProto.MetricsEvent. |
| NOTIFICATION_ZEN_MODE_DURATION_PROMPT); |
| break; |
| } |
| |
| if (currZenDuration != newZenDuration) { |
| Settings.Secure.putInt(mContext.getContentResolver(), |
| Settings.Secure.ZEN_DURATION, newZenDuration); |
| } |
| } |
| |
| @VisibleForTesting |
| protected View getContentView() { |
| if (mLayoutInflater == null) { |
| mLayoutInflater = new PhoneWindow(mContext).getLayoutInflater(); |
| } |
| View contentView = mLayoutInflater.inflate(R.layout.zen_mode_duration_dialog, |
| null); |
| ScrollView container = (ScrollView) contentView.findViewById(R.id.zen_duration_container); |
| |
| mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons); |
| mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content); |
| |
| for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) { |
| final View radioButton = mLayoutInflater.inflate(R.layout.zen_mode_radio_button, |
| mZenRadioGroup, false); |
| mZenRadioGroup.addView(radioButton); |
| radioButton.setId(i); |
| |
| final View radioButtonContent = mLayoutInflater.inflate(R.layout.zen_mode_condition, |
| mZenRadioGroupContent, false); |
| radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS); |
| mZenRadioGroupContent.addView(radioButtonContent); |
| } |
| |
| return contentView; |
| } |
| |
| @VisibleForTesting |
| protected void setupRadioButtons(int zenDuration) { |
| int checkedIndex = ALWAYS_ASK_CONDITION_INDEX; |
| if (zenDuration == 0) { |
| checkedIndex = FOREVER_CONDITION_INDEX; |
| } else if (zenDuration > 0) { |
| checkedIndex = COUNTDOWN_CONDITION_INDEX; |
| } |
| |
| bindTag(zenDuration, mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX), |
| FOREVER_CONDITION_INDEX); |
| bindTag(zenDuration, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX), |
| COUNTDOWN_CONDITION_INDEX); |
| bindTag(zenDuration, mZenRadioGroupContent.getChildAt(ALWAYS_ASK_CONDITION_INDEX), |
| ALWAYS_ASK_CONDITION_INDEX); |
| getConditionTagAt(checkedIndex).rb.setChecked(true); |
| } |
| |
| private void bindTag(final int currZenDuration, final View row, final int rowIndex) { |
| final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() : |
| new ConditionTag(); |
| row.setTag(tag); |
| |
| if (tag.rb == null) { |
| tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowIndex); |
| } |
| |
| // if duration is set to forever or always prompt, then countdown time defaults to 1 hour |
| if (currZenDuration <= 0) { |
| tag.countdownZenDuration = MINUTE_BUCKETS[DEFAULT_BUCKET_INDEX]; |
| } else { |
| tag.countdownZenDuration = currZenDuration; |
| } |
| |
| tag.rb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { |
| @Override |
| public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { |
| if (isChecked) { |
| tag.rb.setChecked(true); |
| } |
| } |
| }); |
| |
| updateUi(tag, row, rowIndex); |
| } |
| |
| @VisibleForTesting |
| protected ConditionTag getConditionTagAt(int index) { |
| return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag(); |
| } |
| |
| |
| private void setupUi(ConditionTag tag, View row) { |
| if (tag.lines == null) { |
| tag.lines = row.findViewById(android.R.id.content); |
| } |
| |
| if (tag.line1 == null) { |
| tag.line1 = (TextView) row.findViewById(android.R.id.text1); |
| } |
| |
| // text2 is not used in zen duration dialog |
| row.findViewById(android.R.id.text2).setVisibility(View.GONE); |
| |
| tag.lines.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| tag.rb.setChecked(true); |
| } |
| }); |
| } |
| |
| private void updateButtons(ConditionTag tag, View row, int rowIndex) { |
| // minus button |
| final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); |
| button1.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| onClickTimeButton(row, tag, false /*down*/, rowIndex); |
| } |
| }); |
| |
| // plus button |
| final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); |
| button2.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| onClickTimeButton(row, tag, true /*up*/, rowIndex); |
| } |
| }); |
| |
| final long time = tag.countdownZenDuration; |
| if (rowIndex == COUNTDOWN_CONDITION_INDEX) { |
| button1.setVisibility(View.VISIBLE); |
| button2.setVisibility(View.VISIBLE); |
| |
| button1.setEnabled(time > MIN_BUCKET_MINUTES); |
| button2.setEnabled(tag.countdownZenDuration != MAX_BUCKET_MINUTES); |
| |
| button1.setAlpha(button1.isEnabled() ? 1f : .5f); |
| button2.setAlpha(button2.isEnabled() ? 1f : .5f); |
| } else { |
| button1.setVisibility(View.GONE); |
| button2.setVisibility(View.GONE); |
| } |
| } |
| |
| @VisibleForTesting |
| protected void updateUi(ConditionTag tag, View row, int rowIndex) { |
| if (tag.lines == null) { |
| setupUi(tag, row); |
| } |
| |
| updateButtons(tag, row, rowIndex); |
| |
| String radioContentText = ""; |
| switch (rowIndex) { |
| case FOREVER_CONDITION_INDEX: |
| radioContentText = mContext.getString(R.string.zen_mode_forever); |
| break; |
| case COUNTDOWN_CONDITION_INDEX: |
| Condition condition = ZenModeConfig.toTimeCondition(mContext, |
| tag.countdownZenDuration, ActivityManager.getCurrentUser(), false); |
| radioContentText = condition.line1; |
| break; |
| case ALWAYS_ASK_CONDITION_INDEX: |
| radioContentText = mContext.getString( |
| R.string.zen_mode_duration_always_prompt_title); |
| break; |
| } |
| |
| tag.line1.setText(radioContentText); |
| } |
| |
| @VisibleForTesting |
| protected void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) { |
| int newDndTimeDuration = -1; |
| final int N = MINUTE_BUCKETS.length; |
| if (mBucketIndex == -1) { |
| // not on a known index, search for the next or prev bucket by time |
| final long time = tag.countdownZenDuration; |
| for (int i = 0; i < N; i++) { |
| int j = up ? i : N - 1 - i; |
| final int bucketMinutes = MINUTE_BUCKETS[j]; |
| if (up && bucketMinutes > time || !up && bucketMinutes < time) { |
| mBucketIndex = j; |
| newDndTimeDuration = bucketMinutes; |
| break; |
| } |
| } |
| if (newDndTimeDuration == -1) { |
| mBucketIndex = DEFAULT_BUCKET_INDEX; |
| newDndTimeDuration = MINUTE_BUCKETS[mBucketIndex]; |
| } |
| } else { |
| // on a known index, simply increment or decrement |
| mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); |
| newDndTimeDuration = MINUTE_BUCKETS[mBucketIndex]; |
| } |
| tag.countdownZenDuration = newDndTimeDuration; |
| bindTag(newDndTimeDuration, row, rowId); |
| tag.rb.setChecked(true); |
| } |
| |
| // used as the view tag on condition rows |
| @VisibleForTesting |
| protected static class ConditionTag { |
| public RadioButton rb; |
| public View lines; |
| public TextView line1; |
| public int countdownZenDuration; // only important for countdown radio button |
| } |
| } |