Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2345b63c authored by Matías Hernández's avatar Matías Hernández Committed by Android (Google) Code Review
Browse files

Merge "Support adding an automatic schedule to previously-manual system-owned modes" into main

parents 700fff1e 0bf4899f
Loading
Loading
Loading
Loading
+62 −0
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2024 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:attr/selectableItemBackground"
    android:gravity="center_vertical"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:paddingTop="8dp"
    android:paddingBottom="8dp"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_gravity="center" />

    <RelativeLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:paddingStart="16dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal"
            android:singleLine="true"
            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/subtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignStart="@id/title"
            android:layout_below="@id/title"
            android:maxLines="2"
            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead"
            android:textColor="?android:attr/textColorSecondary"
            android:textSize="14sp" />

    </RelativeLayout>

</LinearLayout>
 No newline at end of file
+13 −0
Original line number Original line Diff line number Diff line
@@ -7927,6 +7927,19 @@
    <!-- Zen Modes: Summary for the Do not Disturb option and associated settings page. [CHAR LIMIT=240]-->
    <!-- Zen Modes: Summary for the Do not Disturb option and associated settings page. [CHAR LIMIT=240]-->
    <string name="zen_mode_settings_summary">Only get notified by important people and apps</string>
    <string name="zen_mode_settings_summary">Only get notified by important people and apps</string>
    <!-- Zen Modes: Option to add an automatic schedule for a mode. [CHAR_LIMIT=40] -->
    <string name="zen_mode_select_schedule">Select activation type</string>
    <!-- Zen Modes: Option to choose a time-based schedule for a mode. [CHAR_LIMIT=40] -->
    <string name="zen_mode_select_schedule_time">Time</string>
    <!-- Zen Modes: Example text for the option to choose a time-based schedule for a mode. [CHAR_LIMIT=60] -->
    <string name="zen_mode_select_schedule_time_example">Ex. \"9:30 – 5:00 PM\"</string>
    <!-- Zen Modes: Option to choose a calendar-events-based schedule for a mode. [CHAR_LIMIT=40] -->
    <string name="zen_mode_select_schedule_calendar">Calendar</string>
    <!-- Zen Modes: Example text for the option to choose a calendar-events-based schedule for a mode. [CHAR_LIMIT=60] -->
    <string name="zen_mode_select_schedule_calendar_example">Ex. \"Personal calendar\"</string>
    <!-- Subtitle for the Do not Disturb slice. [CHAR LIMIT=50]-->
    <!-- Subtitle for the Do not Disturb slice. [CHAR LIMIT=50]-->
    <string name="zen_mode_slice_subtitle">Limit interruptions</string>
    <string name="zen_mode_slice_subtitle">Limit interruptions</string>
+56 −0
Original line number Original line Diff line number Diff line
@@ -18,6 +18,12 @@ package com.android.settings.notification.modes;


import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;

import static com.google.common.base.Preconditions.checkState;


import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNull;


@@ -26,7 +32,10 @@ import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.service.notification.SystemZenRules;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.service.notification.ZenPolicy;
import android.util.Log;
import android.util.Log;


@@ -204,6 +213,44 @@ class ZenMode {
                : new ZenDeviceEffects.Builder().build();
                : new ZenDeviceEffects.Builder().build();
    }
    }


    public void setCustomModeConditionId(Context context, Uri conditionId) {
        checkState(SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()),
                "Trying to change condition of non-system-owned rule %s (to %s)",
                mRule, conditionId);

        Uri oldCondition = mRule.getConditionId();
        mRule.setConditionId(conditionId);

        ZenModeConfig.ScheduleInfo scheduleInfo = tryParseScheduleConditionId(conditionId);
        if (scheduleInfo != null) {
            mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME);
            mRule.setOwner(ZenModeConfig.getScheduleConditionProvider());
            mRule.setTriggerDescription(
                    getTriggerDescriptionForScheduleTime(context, scheduleInfo));
            return;
        }

        ZenModeConfig.EventInfo eventInfo = tryParseEventConditionId(conditionId);
        if (eventInfo != null) {
            mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR);
            mRule.setOwner(ZenModeConfig.getEventConditionProvider());
            mRule.setTriggerDescription(getTriggerDescriptionForScheduleEvent(context, eventInfo));
            return;
        }

        if (ZenModeConfig.isValidCustomManualConditionId(conditionId)) {
            mRule.setType(AutomaticZenRule.TYPE_OTHER);
            mRule.setOwner(ZenModeConfig.getCustomManualConditionProvider());
            mRule.setTriggerDescription("");
            return;
        }

        Log.wtf(TAG, String.format(
                "Changed condition of rule %s (%s -> %s) but cannot recognize which kind of "
                        + "condition it was!",
                mRule, oldCondition, conditionId));
    }

    public boolean canEditName() {
    public boolean canEditName() {
        return !isManualDnd();
        return !isManualDnd();
    }
    }
@@ -224,6 +271,15 @@ class ZenMode {
        return mIsActive;
        return mIsActive;
    }
    }


    public boolean isSystemOwned() {
        return SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName());
    }

    @AutomaticZenRule.Type
    public int getType() {
        return mRule.getType();
    }

    @Override
    @Override
    public boolean equals(@Nullable Object obj) {
    public boolean equals(@Nullable Object obj) {
        return obj instanceof ZenMode other
        return obj instanceof ZenMode other
+1 −1
Original line number Original line Diff line number Diff line
@@ -52,7 +52,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
        prefControllers.add(new ZenModeDisplayLinkPreferenceController(
        prefControllers.add(new ZenModeDisplayLinkPreferenceController(
                context, "mode_display_settings", mBackend, mHelperBackend));
                context, "mode_display_settings", mBackend, mHelperBackend));
        prefControllers.add(new ZenModeSetTriggerLinkPreferenceController(context,
        prefControllers.add(new ZenModeSetTriggerLinkPreferenceController(context,
                "zen_automatic_trigger_category", mBackend));
                "zen_automatic_trigger_category", this, mBackend));
        return prefControllers;
        return prefControllers;
    }
    }


+149 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.settings.notification.modes;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import android.app.Dialog;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.service.notification.ZenModeConfig;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;

import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.dashboard.DashboardFragment;

import com.google.common.collect.ImmutableList;

public class ZenModeScheduleChooserDialog extends InstrumentedDialogFragment {

    private static final String TAG = "ZenModeScheduleChooserDialog";

    static final int OPTION_TIME = 0;
    static final int OPTION_CALENDAR = 1;

    private record ScheduleOption(@StringRes int nameResId, @StringRes int exampleResId,
                                  @DrawableRes int iconResId) {}

    private static final ImmutableList<ScheduleOption> SCHEDULE_OPTIONS = ImmutableList.of(
            new ScheduleOption(R.string.zen_mode_select_schedule_time,
                    R.string.zen_mode_select_schedule_time_example,
                    com.android.internal.R.drawable.ic_zen_mode_type_schedule_time),
            new ScheduleOption(R.string.zen_mode_select_schedule_calendar,
                    R.string.zen_mode_select_schedule_calendar_example,
                    com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar));

    private OnScheduleOptionListener mOptionListener;

    interface OnScheduleOptionListener {
        void onScheduleSelected(Uri conditionId);
    }

    @Override
    public int getMetricsCategory() {
        // TODO: b/332937635 - Update metrics category
        return 0;
    }

    static void show(DashboardFragment parent, OnScheduleOptionListener optionListener) {
        ZenModeScheduleChooserDialog dialog = new ZenModeScheduleChooserDialog();
        dialog.mOptionListener = optionListener;
        dialog.setTargetFragment(parent, 0);
        dialog.show(parent.getParentFragmentManager(), TAG);
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        checkState(getContext() != null);
        return new AlertDialog.Builder(getContext())
                .setTitle(R.string.zen_mode_choose_rule_type)
                .setAdapter(new OptionsAdapter(getContext()),
                        (dialog, which) -> onScheduleTypeSelected(which))
                .setNegativeButton(R.string.cancel, null)
                .create();
    }

    private static class OptionsAdapter extends ArrayAdapter<ScheduleOption> {

        private final LayoutInflater mInflater;

        OptionsAdapter(@NonNull Context context) {
            super(context, R.layout.zen_mode_type_item, SCHEDULE_OPTIONS);
            mInflater = LayoutInflater.from(context);
        }

        @NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.zen_mode_type_item, parent, false);
            }
            // No need for holder pattern since we have only 2 items.
            ImageView imageView = checkNotNull(convertView.findViewById(R.id.icon));
            TextView title = checkNotNull(convertView.findViewById(R.id.title));
            TextView subtitle = checkNotNull(convertView.findViewById(R.id.subtitle));

            ScheduleOption option = checkNotNull(getItem(position));
            imageView.setImageResource(option.iconResId());
            title.setText(option.nameResId());
            subtitle.setText(option.exampleResId());

            return convertView;
        }
    }

    private void onScheduleTypeSelected(int whichOption) {
        Uri conditionId = switch (whichOption) {
            case OPTION_TIME -> getDefaultScheduleTimeCondition();
            case OPTION_CALENDAR -> getDefaultScheduleCalendarCondition();
            default -> ZenModeConfig.toCustomManualConditionId();
        };

        mOptionListener.onScheduleSelected(conditionId);
    }

    private static Uri getDefaultScheduleTimeCondition() {
        ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo();
        schedule.days = ZenModeConfig.ALL_DAYS;
        schedule.startHour = 9;
        schedule.startMinute = 30;
        schedule.endHour = 17;
        return ZenModeConfig.toScheduleConditionId(schedule);
    }

    private static Uri getDefaultScheduleCalendarCondition() {
        ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo();
        eventInfo.calendarId = null; // All calendars of the current user.
        eventInfo.reply = ZenModeConfig.EventInfo.REPLY_ANY_EXCEPT_NO;
        return ZenModeConfig.toEventConditionId(eventInfo);
    }
}
Loading