blob: 297bfdb97bba6f6782fa5b421df58905bee960aa [file] [log] [blame]
/*
* Copyright (C) 2020 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.app;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import androidx.core.text.BidiFormatter;
import androidx.lifecycle.LifecycleObserver;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.widget.PrimarySwitchPreference;
import com.android.settingslib.RestrictedSwitchPreference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Populates the PreferenceCategory with notification channels associated with the given app.
* Users can allow/disallow notification channels from bypassing DND on a single settings
* page.
*/
public class AppChannelsBypassingDndPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, LifecycleObserver {
private static final String KEY = "zen_mode_bypassing_app_channels_list";
private static final String ARG_FROM_SETTINGS = "fromSettings";
private RestrictedSwitchPreference mAllNotificationsToggle;
private PreferenceCategory mPreferenceCategory;
private List<NotificationChannel> mChannels = new ArrayList<>();
public AppChannelsBypassingDndPreferenceController(
Context context,
NotificationBackend backend) {
super(context, backend);
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceCategory = screen.findPreference(KEY);
mAllNotificationsToggle = new RestrictedSwitchPreference(mPreferenceCategory.getContext());
mAllNotificationsToggle.setTitle(R.string.zen_mode_bypassing_app_channels_toggle_all);
mAllNotificationsToggle.setDisabledByAdmin(mAdmin);
mAllNotificationsToggle.setEnabled(
(mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin()));
mAllNotificationsToggle.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference pref) {
SwitchPreference preference = (SwitchPreference) pref;
final boolean bypassDnd = preference.isChecked();
for (NotificationChannel channel : mChannels) {
if (showNotification(channel) && isChannelConfigurable(channel)) {
channel.setBypassDnd(bypassDnd);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
}
}
// the 0th index is the mAllNotificationsToggle which allows users to
// toggle all notifications from this app to bypass DND
for (int i = 1; i < mPreferenceCategory.getPreferenceCount(); i++) {
PrimarySwitchPreference childPreference =
(PrimarySwitchPreference) mPreferenceCategory.getPreference(i);
childPreference.setChecked(showNotificationInDnd(mChannels.get(i - 1)));
}
return true;
}
});
loadAppChannels();
super.displayPreference(screen);
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
return mAppRow != null;
}
@Override
public void updateState(Preference preference) {
if (mAppRow != null) {
loadAppChannels();
}
}
private void loadAppChannels() {
// Load channel settings
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... unused) {
List<NotificationChannel> newChannelList = new ArrayList<>();
List<NotificationChannelGroup> mChannelGroupList = mBackend.getGroups(mAppRow.pkg,
mAppRow.uid).getList();
for (NotificationChannelGroup channelGroup : mChannelGroupList) {
for (NotificationChannel channel : channelGroup.getChannels()) {
if (!isConversation(channel)) {
newChannelList.add(channel);
}
}
}
Collections.sort(newChannelList, CHANNEL_COMPARATOR);
mChannels = newChannelList;
return null;
}
@Override
protected void onPostExecute(Void unused) {
if (mContext == null) {
return;
}
populateList();
}
}.execute();
}
private void populateList() {
if (mPreferenceCategory == null) {
return;
}
mPreferenceCategory.removeAll();
mPreferenceCategory.addPreference(mAllNotificationsToggle);
for (NotificationChannel channel : mChannels) {
PrimarySwitchPreference channelPreference = new PrimarySwitchPreference(mContext);
channelPreference.setDisabledByAdmin(mAdmin);
channelPreference.setSwitchEnabled(
(mAdmin == null || !channelPreference.isDisabledByAdmin())
&& isChannelConfigurable(channel)
&& showNotification(channel));
channelPreference.setTitle(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
channelPreference.setChecked(showNotificationInDnd(channel));
channelPreference.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object val) {
boolean switchOn = (Boolean) val;
channel.setBypassDnd(switchOn);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
return true;
}
});
Bundle channelArgs = new Bundle();
channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
channelArgs.putBoolean(ARG_FROM_SETTINGS, true);
channelPreference.setOnPreferenceClickListener(preference -> {
new SubSettingLauncher(mContext)
.setDestination(ChannelNotificationSettings.class.getName())
.setArguments(channelArgs)
.setUserHandle(UserHandle.of(mAppRow.userId))
.setTitleRes(com.android.settings.R.string.notification_channel_title)
.setSourceMetricsCategory(SettingsEnums.DND_APPS_BYPASSING)
.launch();
return true;
});
mPreferenceCategory.addPreference(channelPreference);
}
mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
}
private boolean areAllChannelsBypassing() {
boolean allChannelsBypassing = true;
for (NotificationChannel channel : mChannels) {
if (showNotification(channel)) {
allChannelsBypassing &= showNotificationInDnd(channel);
}
}
return allChannelsBypassing;
}
/**
* Whether notifications from this channel would show if DND were on.
*/
private boolean showNotificationInDnd(NotificationChannel channel) {
return channel.canBypassDnd() && showNotification(channel);
}
/**
* Whether notifications from this channel would show if DND weren't on.
*/
private boolean showNotification(NotificationChannel channel) {
return channel.getImportance() != IMPORTANCE_NONE;
}
/**
* Whether this notification channel is representing a conversation.
*/
private boolean isConversation(NotificationChannel channel) {
return channel.getConversationId() != null && !channel.isDemoted();
}
}