Merge "PreferenceControllers are the way of the future."

This commit is contained in:
Julia Reynolds
2017-11-08 13:33:40 +00:00
committed by Android (Google) Code Review
44 changed files with 5332 additions and 1024 deletions

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2017 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;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.NotificationChannel;
import android.content.Context;
import android.support.v7.preference.Preference;
import android.util.Log;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.RestrictedSwitchPreference;
public class AllowSoundPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String TAG = "AllowSoundPrefContr";
private static final String KEY_IMPORTANCE = "allow_sound";
private NotificationSettingsBase.ImportanceListener mImportanceListener;
public AllowSoundPreferenceController(Context context,
NotificationSettingsBase.ImportanceListener importanceListener,
NotificationBackend backend) {
super(context, backend);
mImportanceListener = importanceListener;
}
@Override
public String getPreferenceKey() {
return KEY_IMPORTANCE;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
return mChannel != null && NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId());
}
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin());
pref.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT
|| mChannel.getImportance() == IMPORTANCE_UNSPECIFIED);
} else { Log.i(TAG, "tried to updatestate on a null channel?!"); }
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final int importance =
((Boolean) newValue ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_LOW);
mChannel.setImportance(importance);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
saveChannel();
mImportanceListener.onImportanceChanged();
}
return true;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.support.v7.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
/**
* Controls link to reach more preference settings inside the app.
*/
public class AppLinkPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String TAG = "AppLinkPrefContr";
private static final String KEY_APP_LINK = "app_link";
public AppLinkPreferenceController(Context context) {
super(context, null);
}
@Override
public String getPreferenceKey() {
return KEY_APP_LINK;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
return mAppRow.settingsIntent != null;
}
public void updateState(Preference preference) {
if (mAppRow != null) {
preference.setIntent(mAppRow.settingsIntent);
}
}
}

View File

@@ -16,12 +16,10 @@
package com.android.settings.notification;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
@@ -29,43 +27,29 @@ import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceGroup;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Switch;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.notification.NotificationBackend.AppRow;
import com.android.settings.widget.EntityHeaderController;
import com.android.settings.widget.MasterSwitchPreference;
import com.android.settings.widget.SwitchBar;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
/** These settings are per app, so should not be returned in global search results. */
public class AppNotificationSettings extends NotificationSettingsBase {
private static final String TAG = "AppNotificationSettings";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static String KEY_GENERAL_CATEGORY = "categories";
private static String KEY_DELETED = "deleted";
private List<NotificationChannelGroup> mChannelGroupList;
private List<PreferenceCategory> mChannelGroups = new ArrayList();
private FooterPreference mDeletedChannels;
@Override
public int getMetricsCategory() {
@@ -84,24 +68,13 @@ public class AppNotificationSettings extends NotificationSettingsBase {
if (getPreferenceScreen() != null) {
getPreferenceScreen().removeAll();
mChannelGroups.clear();
mDeletedChannels = null;
mShowLegacyChannelConfig = false;
mDynamicPreferences.clear();
}
addPreferencesFromResource(R.xml.notification_settings);
getPreferenceScreen().setOrderingAsAdded(true);
setupBlock();
addHeaderPref();
mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid);
if (mShowLegacyChannelConfig) {
mChannel = mBackend.getChannel(
mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID);
populateDefaultChannelPrefs();
addPreferencesFromResource(R.xml.channel_notification_settings);
} else {
addPreferencesFromResource(R.xml.upgraded_app_notification_settings);
setupBadge();
addPreferencesFromResource(R.xml.app_notification_settings);
// Load channel settings
new AsyncTask<Void, Void, Void>() {
@Override
@@ -117,41 +90,59 @@ public class AppNotificationSettings extends NotificationSettingsBase {
return;
}
populateList();
addAppLinkPref();
}
}.execute();
}
getPreferenceScreen().setOrderingAsAdded(true);
updateDependents(mAppRow.banned);
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
}
private void addHeaderPref() {
ArrayMap<String, AppRow> rows = new ArrayMap<>();
rows.put(mAppRow.pkg, mAppRow);
collectConfigActivities(rows);
final Activity activity = getActivity();
final Preference pref = EntityHeaderController
.newInstance(activity, this /* fragment */, null /* header */)
.setRecyclerView(getListView(), getLifecycle())
.setIcon(mAppRow.icon)
.setLabel(mAppRow.label)
.setPackageName(mAppRow.pkg)
.setUid(mAppRow.uid)
.setHasAppInfoLink(true)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE)
.done(activity, getPrefContext());
pref.setKey(KEY_HEADER);
getPreferenceScreen().addPreference(pref);
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.notification_settings;
}
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
mControllers = new ArrayList<>();
mControllers.add(new HeaderPreferenceController(context, this));
mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend));
mControllers.add(new BadgePreferenceController(context, mBackend));
mControllers.add(new AllowSoundPreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new ImportancePreferenceController(context));
mControllers.add(new SoundPreferenceController(context, this,
mImportanceListener, mBackend));
mControllers.add(new LightsPreferenceController(context, mBackend));
mControllers.add(new VibrationPreferenceController(context, mBackend));
mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context),
mBackend));
mControllers.add(new DndPreferenceController(context, getLifecycle(), mBackend));
mControllers.add(new AppLinkPreferenceController(context));
mControllers.add(new DescriptionPreferenceController(context));
mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
return new ArrayList<>(mControllers);
}
private void populateList() {
if (!mChannelGroups.isEmpty()) {
if (!mDynamicPreferences.isEmpty()) {
// If there's anything in mChannelGroups, we've called populateChannelList twice.
// Clear out existing channels and log.
Log.w(TAG, "Notification channel group posted twice to settings - old size " +
mChannelGroups.size() + ", new size " + mChannelGroupList.size());
for (Preference p : mChannelGroups) {
mDynamicPreferences.size() + ", new size " + mChannelGroupList.size());
for (Preference p : mDynamicPreferences) {
getPreferenceScreen().removePreference(p);
}
}
@@ -160,7 +151,7 @@ public class AppNotificationSettings extends NotificationSettingsBase {
groupCategory.setTitle(R.string.notification_channels);
groupCategory.setKey(KEY_GENERAL_CATEGORY);
getPreferenceScreen().addPreference(groupCategory);
mChannelGroups.add(groupCategory);
mDynamicPreferences.add(groupCategory);
Preference empty = new Preference(getPrefContext());
empty.setTitle(R.string.no_channels);
@@ -168,20 +159,8 @@ public class AppNotificationSettings extends NotificationSettingsBase {
groupCategory.addPreference(empty);
} else {
populateGroupList();
int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid);
if (deletedChannelCount > 0 &&
getPreferenceScreen().findPreference(KEY_DELETED) == null) {
mDeletedChannels = new FooterPreference(getPrefContext());
mDeletedChannels.setSelectable(false);
mDeletedChannels.setTitle(getResources().getQuantityString(
R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount));
mDeletedChannels.setEnabled(false);
mDeletedChannels.setKey(KEY_DELETED);
mDeletedChannels.setOrder(ORDER_LAST);
getPreferenceScreen().addPreference(mDeletedChannels);
}
mImportanceListener.onImportanceChanged();
}
updateDependents(mAppRow.banned);
}
private void populateGroupList() {
@@ -190,7 +169,7 @@ public class AppNotificationSettings extends NotificationSettingsBase {
groupCategory.setKey(KEY_GENERAL_CATEGORY);
groupCategory.setOrderingAsAdded(true);
getPreferenceScreen().addPreference(groupCategory);
mChannelGroups.add(groupCategory);
mDynamicPreferences.add(groupCategory);
for (NotificationChannelGroup group : mChannelGroupList) {
final List<NotificationChannel> channels = group.getChannels();
int N = channels.size();
@@ -240,91 +219,6 @@ public class AppNotificationSettings extends NotificationSettingsBase {
parent.addPreference(groupPref);
}
void setupBadge() {
mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
if (mChannel == null) {
mBadge.setChecked(mAppRow.showBadge);
} else {
mBadge.setChecked(mChannel.canShowBadge());
}
mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean value = (Boolean) newValue;
if (mChannel == null) {
mBackend.setShowBadge(mPkg, mUid, value);
} else {
mChannel.setShowBadge(value);
mChannel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
mBackend.updateChannel(mPkg, mUid, mChannel);
}
return true;
}
});
}
protected void setupBlock() {
View switchBarContainer = LayoutInflater.from(
getPrefContext()).inflate(R.layout.styled_switch_bar, null);
mSwitchBar = switchBarContainer.findViewById(R.id.switch_bar);
mSwitchBar.show();
mSwitchBar.setDisabledByAdmin(mSuspendedAppsAdmin);
mSwitchBar.setChecked(!mAppRow.banned);
mSwitchBar.addOnSwitchChangeListener(new SwitchBar.OnSwitchChangeListener() {
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
if (mShowLegacyChannelConfig && mChannel != null) {
final int importance = isChecked ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE;
mImportanceToggle.setChecked(importance == IMPORTANCE_UNSPECIFIED);
mChannel.setImportance(importance);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
mBackend.updateChannel(mPkg, mUid, mChannel);
}
mBackend.setNotificationsEnabledForPackage(mPkgInfo.packageName, mUid, isChecked);
mAppRow.banned = true;
updateDependents(!isChecked);
}
});
mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer);
mBlockBar.setOrder(ORDER_FIRST);
mBlockBar.setKey(KEY_BLOCK);
getPreferenceScreen().addPreference(mBlockBar);
if (mAppRow.systemApp && !mAppRow.banned) {
setVisible(mBlockBar, false);
}
setupBlockDesc(R.string.app_notifications_off_desc);
}
protected void updateDependents(boolean banned) {
for (PreferenceCategory category : mChannelGroups) {
setVisible(category, !banned);
}
if (mDeletedChannels != null) {
setVisible(mDeletedChannels, !banned);
}
setVisible(mBlockedDesc, banned);
setVisible(mBadge, !banned);
if (mShowLegacyChannelConfig) {
setVisible(mImportanceToggle, !banned);
setVisible(mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
|| (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
&& mDndVisualEffectsSuppressed));
setVisible(mVisibilityOverride, !banned &&
checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) && isLockScreenSecure());
}
if (mAppLink != null) {
setVisible(mAppLink, !banned);
}
if (mAppRow.systemApp && !mAppRow.banned) {
setVisible(mBlockBar, false);
}
}
private Comparator<NotificationChannelGroup> mChannelGroupComparator =
new Comparator<NotificationChannelGroup>() {

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2017 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;
import static android.provider.Settings.Secure.NOTIFICATION_BADGING;
import android.content.Context;
import android.provider.Settings;
import android.support.v7.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.RestrictedSwitchPreference;
public class BadgePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String TAG = "BadgePrefContr";
private static final String KEY_BADGE = "badge";
private static final int SYSTEM_WIDE_ON = 1;
private static final int SYSTEM_WIDE_OFF = 0;
public BadgePreferenceController(Context context,
NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY_BADGE;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mAppRow == null && mChannel == null) {
return false;
}
if (Settings.Secure.getInt(mContext.getContentResolver(),
NOTIFICATION_BADGING, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
return false;
}
if (mChannel != null && !mAppRow.showBadge) {
return false;
}
return true;
}
public void updateState(Preference preference) {
if (mAppRow != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
if (mChannel != null) {
pref.setChecked(mChannel.canShowBadge());
pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin());
} else {
pref.setChecked(mAppRow.showBadge);
}
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean showBadge = (Boolean) newValue;
if (mChannel != null) {
mChannel.setShowBadge(showBadge);
saveChannel();
} else if (mAppRow != null){
mAppRow.showBadge = showBadge;
mBackend.setShowBadge(mAppRow.pkg, mAppRow.uid, showBadge);
}
return true;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2017 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;
import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.NotificationManager;
import android.content.Context;
import android.support.v7.preference.Preference;
import android.widget.Switch;
import com.android.settings.R;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.SwitchBar;
public class BlockPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, SwitchBar.OnSwitchChangeListener {
private static final String KEY_BLOCK = "block";
private NotificationSettingsBase.ImportanceListener mImportanceListener;
public BlockPreferenceController(Context context,
NotificationSettingsBase.ImportanceListener importanceListener,
NotificationBackend backend) {
super(context, backend);
mImportanceListener = importanceListener;
}
@Override
public String getPreferenceKey() {
return KEY_BLOCK;
}
@Override
public boolean isAvailable() {
if (mAppRow == null) {
return false;
}
if (mChannel != null) {
return isChannelBlockable();
} else if (mChannelGroup != null && mChannelGroup.getGroup() != null) {
return isChannelGroupBlockable();
} else {
return !mAppRow.systemApp || (mAppRow.systemApp && mAppRow.banned);
}
}
public void updateState(Preference preference) {
LayoutPreference pref = (LayoutPreference) preference;
SwitchBar bar = pref.findViewById(R.id.switch_bar);
if (bar != null) {
bar.show();
try {
bar.addOnSwitchChangeListener(this);
} catch (IllegalStateException e) {
// an exception is thrown if you try to add the listener twice
}
bar.setDisabledByAdmin(mAdmin);
if (mChannel != null) {
bar.setChecked(!mAppRow.banned
&& mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE);
} else if (mChannelGroup != null && mChannelGroup.getGroup() != null) {
bar.setChecked(!mAppRow.banned && !mChannelGroup.isBlocked());
} else {
bar.setChecked(!mAppRow.banned);
}
}
}
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
boolean blocked = !isChecked;
if (mChannel != null) {
final int originalImportance = mChannel.getImportance();
// setting the initial state of the switch in updateState() triggers this callback.
// It's always safe to override the importance if it's meant to be blocked or if
// it was blocked and we are unblocking it.
if (blocked || originalImportance == IMPORTANCE_NONE) {
final int importance = blocked ? IMPORTANCE_NONE
: DEFAULT_CHANNEL_ID.equals(mChannel.getId())
? IMPORTANCE_UNSPECIFIED : IMPORTANCE_DEFAULT;
mChannel.setImportance(importance);
saveChannel();
}
} else if (mChannelGroup != null && mChannelGroup.getGroup() != null) {
mChannelGroup.setBlocked(blocked);
mBackend.updateChannelGroup(mAppRow.pkg, mAppRow.uid, mChannelGroup.getGroup());
} else if (mAppRow != null) {
mAppRow.banned = blocked;
mBackend.setNotificationsEnabledForPackage(mAppRow.pkg, mAppRow.uid, !blocked);
}
mImportanceListener.onImportanceChanged();
}
}

View File

@@ -16,20 +16,15 @@
package com.android.settings.notification;
import android.app.Activity;
import android.app.NotificationChannel;
import android.content.Context;
import android.support.v7.preference.Preference;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.Collections;
@@ -38,12 +33,6 @@ import java.util.List;
public class ChannelGroupNotificationSettings extends NotificationSettingsBase {
private static final String TAG = "ChannelGroupSettings";
private static String KEY_DELETED = "deleted";
private EntityHeaderController mHeaderPref;
private List<Preference> mChannels = new ArrayList();
private FooterPreference mDeletedChannels;
@Override
public int getMetricsCategory() {
return MetricsEvent.NOTIFICATION_CHANNEL_GROUP;
@@ -52,137 +41,68 @@ public class ChannelGroupNotificationSettings extends NotificationSettingsBase {
@Override
public void onResume() {
super.onResume();
if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannelGroup == null) {
if (mAppRow == null || mChannelGroup == null || mChannelGroup.getGroup() == null) {
Log.w(TAG, "Missing package or uid or packageinfo or group");
finish();
return;
}
if (getPreferenceScreen() != null) {
getPreferenceScreen().removeAll();
}
addPreferencesFromResource(R.xml.notification_settings);
setupBlock();
addHeaderPref();
addAppLinkPref();
addFooterPref();
populateChannelList();
updateDependents(mChannelGroup.isBlocked());
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
}
@Override
void setupBadge() {
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.notification_group_settings;
}
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
mControllers = new ArrayList<>();
mControllers.add(new HeaderPreferenceController(context, this));
mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend));
mControllers.add(new AppLinkPreferenceController(context));
mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new DescriptionPreferenceController(context));
return new ArrayList<>(mControllers);
}
private void populateChannelList() {
if (!mChannels.isEmpty()) {
// If there's anything in mChannels, we've called populateChannelList twice.
if (!mDynamicPreferences.isEmpty()) {
// If there's anything in mDynamicPreferences, we've called populateChannelList twice.
// Clear out existing channels and log.
Log.w(TAG, "Notification channel group posted twice to settings - old size " +
mChannels.size() + ", new size " + mChannels.size());
for (Preference p : mChannels) {
mDynamicPreferences.size() + ", new size " + mDynamicPreferences.size());
for (Preference p : mDynamicPreferences) {
getPreferenceScreen().removePreference(p);
}
}
if (mChannelGroup.getChannels().isEmpty()) {
if (mChannelGroup.getGroup().getChannels().isEmpty()) {
Preference empty = new Preference(getPrefContext());
empty.setTitle(R.string.no_channels);
empty.setEnabled(false);
getPreferenceScreen().addPreference(empty);
mChannels.add(empty);
mDynamicPreferences.add(empty);
} else {
final List<NotificationChannel> channels = mChannelGroup.getChannels();
final List<NotificationChannel> channels = mChannelGroup.getGroup().getChannels();
Collections.sort(channels, mChannelComparator);
for (NotificationChannel channel : channels) {
mChannels.add(populateSingleChannelPrefs(
getPreferenceScreen(), channel, getImportanceSummary(channel)));
mDynamicPreferences.add(populateSingleChannelPrefs(
getPreferenceScreen(), channel,
ImportancePreferenceController.getImportanceSummary(
getPrefContext(), channel)));
}
int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid);
if (deletedChannelCount > 0) {
mDeletedChannels = new FooterPreference(getPrefContext());
mDeletedChannels.setSelectable(false);
mDeletedChannels.setTitle(getResources().getQuantityString(
R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount));
mDeletedChannels.setEnabled(false);
mDeletedChannels.setKey(KEY_DELETED);
mDeletedChannels.setOrder(ORDER_LAST);
getPreferenceScreen().addPreference(mDeletedChannels);
mChannels.add(mDeletedChannels);
}
}
updateDependents(mAppRow.banned);
}
private void addHeaderPref() {
ArrayMap<String, NotificationBackend.AppRow> rows = new ArrayMap<>();
rows.put(mAppRow.pkg, mAppRow);
collectConfigActivities(rows);
final Activity activity = getActivity();
mHeaderPref = EntityHeaderController
.newInstance(activity, this /* fragment */, null /* header */)
.setRecyclerView(getListView(), getLifecycle());
final Preference pref = mHeaderPref
.setIcon(mAppRow.icon)
.setLabel(mChannelGroup.getName())
.setSummary(mAppRow.label)
.setPackageName(mAppRow.pkg)
.setUid(mAppRow.uid)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE,
EntityHeaderController.ActionType.ACTION_NONE)
.setHasAppInfoLink(true)
.done(activity, getPrefContext());
getPreferenceScreen().addPreference(pref);
}
private void addFooterPref() {
if (!TextUtils.isEmpty(mChannelGroup.getDescription())) {
FooterPreference descPref = new FooterPreference(getPrefContext());
descPref.setOrder(ORDER_LAST);
descPref.setSelectable(false);
descPref.setTitle(mChannelGroup.getDescription());
getPreferenceScreen().addPreference(descPref);
mChannels.add(descPref);
}
}
private void setupBlock() {
View switchBarContainer = LayoutInflater.from(
getPrefContext()).inflate(R.layout.styled_switch_bar, null);
mSwitchBar = switchBarContainer.findViewById(R.id.switch_bar);
mSwitchBar.show();
mSwitchBar.setDisabledByAdmin(mSuspendedAppsAdmin);
mSwitchBar.setChecked(!mChannelGroup.isBlocked());
mSwitchBar.addOnSwitchChangeListener((switchView, isChecked) -> {
mChannelGroup.setBlocked(!isChecked);
mBackend.updateChannelGroup(mPkg, mUid, mChannelGroup);
updateDependents(!isChecked);
});
mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer);
mBlockBar.setOrder(ORDER_FIRST);
mBlockBar.setKey(KEY_BLOCK);
getPreferenceScreen().addPreference(mBlockBar);
if (!isChannelGroupBlockable(mChannelGroup)) {
setVisible(mBlockBar, false);
}
setupBlockDesc(R.string.channel_group_notifications_off_desc);
}
protected void updateDependents(boolean banned) {
for (Preference channel : mChannels) {
setVisible(channel, !banned);
}
if (mAppLink != null) {
setVisible(mAppLink, !banned);
}
setVisible(mBlockBar, isChannelGroupBlockable(mChannelGroup));
setVisible(mBlockedDesc, mAppRow.banned || mChannelGroup.isBlocked());
}
}

View File

@@ -37,6 +37,7 @@ import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.widget.RadioButtonPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
@@ -60,8 +61,8 @@ public class ChannelImportanceSettings extends NotificationSettingsBase
@Override
public void onResume() {
super.onResume();
if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null) {
Log.w(TAG, "Missing package or uid or packageinfo or channel");
if (mAppRow == null || mChannel == null) {
Log.w(TAG, "Missing package or channel");
finish();
return;
}
@@ -69,10 +70,19 @@ public class ChannelImportanceSettings extends NotificationSettingsBase
}
@Override
void setupBadge() {}
protected String getLogTag() {
return TAG;
}
@Override
void updateDependents(boolean banned) {}
protected int getPreferenceScreenResId() {
return R.xml.notification_importance;
}
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
return null;
}
@Override
public void onPause() {
@@ -81,11 +91,6 @@ public class ChannelImportanceSettings extends NotificationSettingsBase
private PreferenceScreen createPreferenceHierarchy() {
PreferenceScreen root = getPreferenceScreen();
if (root != null) {
root.removeAll();
}
addPreferencesFromResource(R.xml.notification_importance);
root = getPreferenceScreen();
for (int i = 0; i < root.getPreferenceCount(); i++) {
Preference pref = root.getPreference(i);
@@ -148,8 +153,9 @@ public class ChannelImportanceSettings extends NotificationSettingsBase
// but the sound you had selected was "Silence",
// then set sound for this channel to your default sound,
// because you probably intended to cause this channel to actually start making sound.
if (oldImportance < IMPORTANCE_DEFAULT && !hasValidSound(mChannel) &&
mChannel.getImportance() >= IMPORTANCE_DEFAULT) {
if (oldImportance < IMPORTANCE_DEFAULT
&& !SoundPreferenceController.hasValidSound(mChannel)
&& mChannel.getImportance() >= IMPORTANCE_DEFAULT) {
mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
mChannel.getAudioAttributes());
mChannel.lockFields(USER_LOCKED_SOUND);

View File

@@ -16,59 +16,23 @@
package com.android.settings.notification;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.AsyncTask;
import android.provider.Settings;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Switch;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.RingtonePreference;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.widget.EntityHeaderController;
import com.android.settings.widget.SwitchBar;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import java.util.ArrayList;
import java.util.List;
public class ChannelNotificationSettings extends NotificationSettingsBase {
private static final String TAG = "ChannelSettings";
private static final String KEY_LIGHTS = "lights";
private static final String KEY_VIBRATE = "vibrate";
private static final String KEY_RINGTONE = "ringtone";
private static final String KEY_IMPORTANCE = "importance";
private static final String KEY_ADVANCED = "advanced";
private Preference mImportance;
private RestrictedSwitchPreference mLights;
private RestrictedSwitchPreference mVibrate;
private NotificationSoundPreference mRingtone;
private FooterPreference mFooter;
private NotificationChannelGroup mChannelGroup;
private EntityHeaderController mHeaderPref;
private PreferenceGroup mAdvanced;
@Override
public int getMetricsCategory() {
return MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION;
@@ -83,308 +47,52 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
return;
}
if (getPreferenceScreen() != null) {
getPreferenceScreen().removeAll();
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
addPreferencesFromResource(R.xml.notification_settings);
setupBlock();
addHeaderPref();
addAppLinkPref();
addFooterPref();
if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
populateDefaultChannelPrefs();
mShowLegacyChannelConfig = true;
} else {
populateUpgradedChannelPrefs();
if (mChannel.getGroup() != null) {
mChannelGroup = mBackend.getGroup(mPkg, mUid, mChannel.getGroup());
if (mChannelGroup != null) {
setChannelGroupLabel(mChannelGroup.getName());
}
}
}
updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
}
private void populateUpgradedChannelPrefs() {
addPreferencesFromResource(R.xml.upgraded_channel_notification_settings);
setupBadge();
setupPriorityPref(mChannel.canBypassDnd());
setupVisOverridePref(mChannel.getLockscreenVisibility());
setupLights();
setupVibrate();
setupRingtone();
setupImportance();
mAdvanced = (PreferenceGroup) findPreference(KEY_ADVANCED);
}
private void addHeaderPref() {
ArrayMap<String, NotificationBackend.AppRow> rows = new ArrayMap<>();
rows.put(mAppRow.pkg, mAppRow);
collectConfigActivities(rows);
final Activity activity = getActivity();
mHeaderPref = EntityHeaderController
.newInstance(activity, this /* fragment */, null /* header */)
.setRecyclerView(getListView(), getLifecycle());
final Preference pref = mHeaderPref
.setIcon(mAppRow.icon)
.setLabel(mChannel.getName())
.setSummary(mAppRow.label)
.setPackageName(mAppRow.pkg)
.setUid(mAppRow.uid)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE,
EntityHeaderController.ActionType.ACTION_NONE)
.setHasAppInfoLink(true)
.done(activity, getPrefContext());
getPreferenceScreen().addPreference(pref);
}
private void setChannelGroupLabel(CharSequence groupName) {
final SpannableStringBuilder summary = new SpannableStringBuilder();
BidiFormatter bidi = BidiFormatter.getInstance();
summary.append(bidi.unicodeWrap(mAppRow.label.toString()));
if (groupName != null) {
summary.append(bidi.unicodeWrap(mContext.getText(
R.string.notification_header_divider_symbol_with_spaces)));
summary.append(bidi.unicodeWrap(groupName.toString()));
}
final Activity activity = getActivity();
mHeaderPref.setSummary(summary.toString());
mHeaderPref.done(activity, getPrefContext());
}
private void addFooterPref() {
if (!TextUtils.isEmpty(mChannel.getDescription())) {
FooterPreference descPref = new FooterPreference(getPrefContext());
descPref.setOrder(ORDER_LAST);
descPref.setSelectable(false);
descPref.setTitle(mChannel.getDescription());
getPreferenceScreen().addPreference(descPref);
}
}
protected void setupBadge() {
mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
mBadge.setEnabled(mAppRow.showBadge);
mBadge.setChecked(mChannel.canShowBadge());
mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean value = (Boolean) newValue;
mChannel.setShowBadge(value);
mChannel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
mBackend.updateChannel(mPkg, mUid, mChannel);
return true;
}
});
}
private void setupLights() {
mLights = (RestrictedSwitchPreference) findPreference(KEY_LIGHTS);
mLights.setDisabledByAdmin(mSuspendedAppsAdmin);
mLights.setChecked(mChannel.shouldShowLights());
mLights.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean lights = (Boolean) newValue;
mChannel.enableLights(lights);
mChannel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
mBackend.updateChannel(mPkg, mUid, mChannel);
return true;
}
});
}
private void setupVibrate() {
mVibrate = (RestrictedSwitchPreference) findPreference(KEY_VIBRATE);
mVibrate.setDisabledByAdmin(mSuspendedAppsAdmin);
mVibrate.setEnabled(!mVibrate.isDisabledByAdmin() && isChannelConfigurable(mChannel));
mVibrate.setChecked(mChannel.shouldVibrate());
mVibrate.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean vibrate = (Boolean) newValue;
mChannel.enableVibration(vibrate);
mChannel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
mBackend.updateChannel(mPkg, mUid, mChannel);
return true;
}
});
}
private void setupRingtone() {
mRingtone = (NotificationSoundPreference) findPreference(KEY_RINGTONE);
mRingtone.setRingtone(mChannel.getSound());
mRingtone.setEnabled(isChannelConfigurable(mChannel));
mRingtone.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
mChannel.setSound((Uri) newValue, mChannel.getAudioAttributes());
mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
mBackend.updateChannel(mPkg, mUid, mChannel);
return false;
}
});
}
private void setupBlock() {
View switchBarContainer = LayoutInflater.from(
getPrefContext()).inflate(R.layout.styled_switch_bar, null);
mSwitchBar = switchBarContainer.findViewById(R.id.switch_bar);
mSwitchBar.show();
mSwitchBar.setDisabledByAdmin(mSuspendedAppsAdmin);
mSwitchBar.setChecked(mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE);
mSwitchBar.addOnSwitchChangeListener(new SwitchBar.OnSwitchChangeListener() {
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
int importance = 0;
if (mShowLegacyChannelConfig) {
importance = isChecked ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE;
mImportanceToggle.setChecked(importance == IMPORTANCE_UNSPECIFIED);
} else {
importance = isChecked ? IMPORTANCE_LOW : IMPORTANCE_NONE;
mImportance.setSummary(getImportanceSummary(importance));
}
mChannel.setImportance(importance);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
mBackend.updateChannel(mPkg, mUid, mChannel);
updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
}
});
mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer);
mBlockBar.setOrder(ORDER_FIRST);
mBlockBar.setKey(KEY_BLOCK);
getPreferenceScreen().addPreference(mBlockBar);
if (!isChannelBlockable(mChannel)) {
setVisible(mBlockBar, false);
}
setupBlockDesc(R.string.channel_notifications_off_desc);
}
private void setupImportance() {
mImportance = findPreference(KEY_IMPORTANCE);
Bundle channelArgs = new Bundle();
channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg);
channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId());
mImportance.setEnabled(mSuspendedAppsAdmin == null && isChannelConfigurable(mChannel));
// Set up intent to show importance selection only if this setting is enabled.
if (mImportance.isEnabled()) {
Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
ChannelImportanceSettings.class.getName(),
channelArgs, null, R.string.notification_importance_title, null,
false, getMetricsCategory());
mImportance.setIntent(channelIntent);
}
mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
}
private String getImportanceSummary(int importance) {
String title;
String summary = null;
switch (importance) {
case IMPORTANCE_UNSPECIFIED:
title = getContext().getString(R.string.notification_importance_unspecified);
break;
case NotificationManager.IMPORTANCE_MIN:
title = getContext().getString(R.string.notification_importance_min_title);
summary = getContext().getString(R.string.notification_importance_min);
break;
case NotificationManager.IMPORTANCE_LOW:
title = getContext().getString(R.string.notification_importance_low_title);
summary = getContext().getString(R.string.notification_importance_low);
break;
case NotificationManager.IMPORTANCE_DEFAULT:
title = getContext().getString(R.string.notification_importance_default_title);
if (hasValidSound(mChannel)) {
summary = getContext().getString(R.string.notification_importance_default);
} else {
summary = getContext().getString(R.string.notification_importance_low);
}
break;
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
title = getContext().getString(R.string.notification_importance_high_title);
if (hasValidSound(mChannel)) {
summary = getContext().getString(R.string.notification_importance_high);
} else {
summary = getContext().getString(R.string.notification_importance_high_silent);
}
break;
default:
return "";
}
if (summary != null) {
return getContext().getString(R.string.notification_importance_divider, title, summary);
} else {
return title;
}
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference instanceof RingtonePreference) {
mRingtone.onPrepareRingtonePickerIntent(mRingtone.getIntent());
startActivityForResult(preference.getIntent(), 200);
return true;
}
return super.onPreferenceTreeClick(preference);
updatePreferenceStates();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mRingtone != null) {
mRingtone.onActivityResult(requestCode, resultCode, data);
}
if (mChannel != null) {
mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
for (NotificationPreferenceController controller : mControllers) {
if (controller instanceof PreferenceManager.OnActivityResultListener) {
((PreferenceManager.OnActivityResultListener) controller)
.onActivityResult(requestCode, resultCode, data);
}
}
}
boolean canPulseLight() {
if (!getResources()
.getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) {
return false;
}
return Settings.System.getInt(getContentResolver(),
Settings.System.NOTIFICATION_LIGHT_PULSE, 0) == 1;
@Override
protected String getLogTag() {
return TAG;
}
void updateDependents(boolean banned) {
PreferenceGroup parent;
if (mShowLegacyChannelConfig) {
parent = getPreferenceScreen();
setVisible(mImportanceToggle, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
} else {
setVisible(mAdvanced, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
setVisible(mImportance, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
setVisible(mAdvanced, mLights, checkCanBeVisible(
NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight());
setVisible(mVibrate, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
setVisible(mRingtone, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
parent = mAdvanced;
}
setVisible(parent, mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
setVisible(parent, mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
|| (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
&& mDndVisualEffectsSuppressed));
setVisible(parent, mVisibilityOverride, isLockScreenSecure()
&&checkCanBeVisible(NotificationManager.IMPORTANCE_LOW));
setVisible(mBlockedDesc, mChannel.getImportance() == IMPORTANCE_NONE);
if (mAppLink != null) {
setVisible(mAppLink, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
}
if (mFooter != null) {
setVisible(mFooter, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.channel_notification_settings;
}
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
mControllers = new ArrayList<>();
mControllers.add(new HeaderPreferenceController(context, this));
mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend));
mControllers.add(new ImportancePreferenceController(context));
mControllers.add(new AllowSoundPreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new SoundPreferenceController(context, this,
mImportanceListener, mBackend));
mControllers.add(new VibrationPreferenceController(context, mBackend));
mControllers.add(new AppLinkPreferenceController(context));
mControllers.add(new DescriptionPreferenceController(context));
mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context),
mBackend));
mControllers.add(new LightsPreferenceController(context, mBackend));
mControllers.add(new BadgePreferenceController(context, mBackend));
mControllers.add(new DndPreferenceController(context, getLifecycle(), mBackend));
mControllers.add(new NotificationsOffPreferenceController(context));
return new ArrayList<>(mControllers);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.support.v7.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
public class DeletedChannelsPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String KEY_DELETED = "deleted";
public DeletedChannelsPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY_DELETED;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
// only visible on app screen
if (mChannel != null || hasValidGroup()) {
return false;
}
return mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid) > 0;
}
public void updateState(Preference preference) {
if (mAppRow != null) {
int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid);
preference.setTitle(mContext.getResources().getQuantityString(
R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount));
}
preference.setEnabled(false);
preference.setSelectable(false);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.support.v7.preference.Preference;
import android.text.TextUtils;
import com.android.settings.core.PreferenceControllerMixin;
public class DescriptionPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String KEY_DESC = "desc";
public DescriptionPreferenceController(Context context) {
super(context, null);
}
@Override
public String getPreferenceKey() {
return KEY_DESC;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mChannel == null && !hasValidGroup()) {
return false;
}
if (mChannel != null && !TextUtils.isEmpty(mChannel.getDescription())) {
return true;
}
if (hasValidGroup() && !TextUtils.isEmpty(mChannelGroup.getDescription())) {
return true;
}
return false;
}
public void updateState(Preference preference) {
if (mAppRow != null) {
if (mChannel != null) {
preference.setTitle(mChannel.getDescription());
} else if (hasValidGroup()) {
preference.setTitle(mChannelGroup.getDescription());
}
}
preference.setEnabled(false);
preference.setSelectable(false);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2017 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;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.support.v7.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnResume;
public class DndPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener,
LifecycleObserver, OnResume {
private static final String KEY_BYPASS_DND = "bypass_dnd";
private boolean mVisualEffectsSuppressed;
public DndPreferenceController(Context context, Lifecycle lifecycle,
NotificationBackend backend) {
super(context, backend);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@Override
public void onResume() {
NotificationManager.Policy policy = mNm.getNotificationPolicy();
mVisualEffectsSuppressed = policy != null && policy.suppressedVisualEffects != 0;
}
@Override
public String getPreferenceKey() {
return KEY_BYPASS_DND;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
|| (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
&& mVisualEffectsSuppressed);
}
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin());
pref.setChecked(mChannel.canBypassDnd());
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final boolean bypassZenMode = (Boolean) newValue;
mChannel.setBypassDnd(bypassZenMode);
mChannel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
saveChannel();
}
return true;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2017 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;
import static com.android.settings.widget.EntityHeaderController.PREF_KEY_APP_HEADER;
import android.content.Context;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.View;
import com.android.settings.R;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.EntityHeaderController;
public class HeaderPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private final PreferenceFragment mFragment;
public HeaderPreferenceController(Context context, PreferenceFragment fragment) {
super(context, null);
mFragment = fragment;
}
@Override
public String getPreferenceKey() {
return PREF_KEY_APP_HEADER;
}
@Override
public boolean isAvailable() {
return mAppRow != null;
}
public void updateState(Preference preference) {
if (mAppRow != null && mFragment != null) {
LayoutPreference pref = (LayoutPreference) preference;
EntityHeaderController controller = EntityHeaderController
.newInstance(mFragment.getActivity(), mFragment,
pref.findViewById(R.id.entity_header));
pref = controller.setIcon(mAppRow.icon)
.setLabel(getLabel())
.setSummary(getSummary())
.setPackageName(mAppRow.pkg)
.setUid(mAppRow.uid)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE,
EntityHeaderController.ActionType.ACTION_NONE)
.setHasAppInfoLink(true)
.done(mFragment.getActivity(), mContext);
pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE);
}
}
CharSequence getLabel() {
return mChannel != null ? mChannel.getName()
: mChannelGroup != null && mChannelGroup.getGroup() != null
? mChannelGroup.getGroup().getName()
: mAppRow.label;
}
CharSequence getSummary() {
if (mChannel != null) {
if (mChannelGroup != null && mChannelGroup.getGroup() != null
&& !TextUtils.isEmpty(mChannelGroup.getGroup().getName())) {
final SpannableStringBuilder summary = new SpannableStringBuilder();
BidiFormatter bidi = BidiFormatter.getInstance();
summary.append(bidi.unicodeWrap(mAppRow.label.toString()));
summary.append(bidi.unicodeWrap(mContext.getText(
R.string.notification_header_divider_symbol_with_spaces)));
summary.append(bidi.unicodeWrap(mChannelGroup.getGroup().getName().toString()));
return summary;
} else {
return mAppRow.label;
}
} else if (mChannelGroup != null && mChannelGroup.getGroup() != null) {
return mAppRow.label;
} else {
return "";
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2017 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;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.preference.Preference;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
public class ImportancePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String KEY_IMPORTANCE = "importance";
// Ironically doesn't take an importance listener because the importance is not changed
// by this controller's preference but by the screen it links to.
public ImportancePreferenceController(Context context) {
super(context, null);
}
@Override
public String getPreferenceKey() {
return KEY_IMPORTANCE;
}
private int getMetricsCategory() {
return MetricsProto.MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mChannel == null) {
return false;
}
return !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId());
}
public void updateState(Preference preference) {
if (mAppRow!= null && mChannel != null) {
preference.setEnabled(mAdmin == null && isChannelConfigurable());
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, mChannel.getId());
if (preference.isEnabled()) {
Intent channelIntent = Utils.onBuildStartFragmentIntent(mContext,
ChannelImportanceSettings.class.getName(),
channelArgs, null, R.string.notification_importance_title, null,
false, getMetricsCategory());
preference.setIntent(channelIntent);
preference.setSummary(getImportanceSummary(mContext, mChannel));
}
}
}
protected static String getImportanceSummary(Context context, NotificationChannel channel) {
String title;
String summary = null;
int importance = channel.getImportance();
switch (importance) {
case IMPORTANCE_UNSPECIFIED:
title = context.getString(R.string.notification_importance_unspecified);
break;
case NotificationManager.IMPORTANCE_MIN:
title = context.getString(R.string.notification_importance_min_title);
summary = context.getString(R.string.notification_importance_min);
break;
case NotificationManager.IMPORTANCE_LOW:
title = context.getString(R.string.notification_importance_low_title);
summary = context.getString(R.string.notification_importance_low);
break;
case NotificationManager.IMPORTANCE_DEFAULT:
title = context.getString(R.string.notification_importance_default_title);
if (SoundPreferenceController.hasValidSound(channel)) {
summary = context.getString(R.string.notification_importance_default);
} else {
summary = context.getString(R.string.notification_importance_low);
}
break;
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
title = context.getString(R.string.notification_importance_high_title);
if (SoundPreferenceController.hasValidSound(channel)) {
summary = context.getString(R.string.notification_importance_high);
} else {
summary = context.getString(R.string.notification_importance_high_silent);
}
break;
default:
return "";
}
if (summary != null) {
return context.getString(R.string.notification_importance_divider, title, summary);
} else {
return title;
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2017 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;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.provider.Settings;
import android.support.v7.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnResume;
public class LightsPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_LIGHTS = "lights";
public LightsPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY_LIGHTS;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mChannel == null) {
return false;
}
return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight()
&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId());
}
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin());
pref.setChecked(mChannel.shouldShowLights());
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final boolean lights = (Boolean) newValue;
mChannel.enableLights(lights);
saveChannel();
}
return true;
}
boolean canPulseLight() {
if (!mContext.getResources()
.getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) {
return false;
}
return Settings.System.getInt(mContext.getContentResolver(),
Settings.System.NOTIFICATION_LIGHT_PULSE, 0) == 1;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.support.v4.content.res.TypedArrayUtils;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.widget.TextView;
import com.android.settingslib.R;
/**
* FooterPreference that can have any key or ordering.
*/
public class NotificationFooterPreference extends Preference {
public NotificationFooterPreference(Context context, AttributeSet attrs) {
super(context, attrs, TypedArrayUtils.getAttr(
context, R.attr.footerPreferenceStyle, android.R.attr.preferenceStyle));
init();
}
public NotificationFooterPreference(Context context) {
this(context, null);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
TextView title = holder.itemView.findViewById(android.R.id.title);
title.setMovementMethod(new LinkMovementMethod());
title.setClickable(false);
title.setLongClickable(false);
}
private void init() {
setIcon(R.drawable.ic_info_outline_24dp);
setSelectable(false);
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2017 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;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import android.annotation.Nullable;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.util.Log;
import com.android.settings.wrapper.NotificationChannelGroupWrapper;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.Objects;
/**
* Parent class for preferences appearing on notification setting pages at the app,
* notification channel group, or notification channel level.
*/
public abstract class NotificationPreferenceController extends AbstractPreferenceController
{
private static final String TAG = "ChannelPrefContr";
@Nullable protected NotificationChannel mChannel;
@Nullable protected NotificationChannelGroupWrapper mChannelGroup;
protected RestrictedLockUtils.EnforcedAdmin mAdmin;
protected NotificationBackend.AppRow mAppRow;
protected final NotificationManager mNm;
protected final NotificationBackend mBackend;
protected final Context mContext;
protected final UserManager mUm;
protected final PackageManager mPm;
protected Preference mPreference;
public NotificationPreferenceController(Context context, NotificationBackend backend) {
super(context);
mContext = context;
mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mBackend = backend;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mPm = mContext.getPackageManager();
}
/**
* Returns true if field's parent object is not blocked.
*/
@Override
public boolean isAvailable() {
if (mAppRow == null) {
return false;
}
if (mAppRow.banned) {
return false;
}
if (mChannel != null) {
return mChannel.getImportance() != IMPORTANCE_NONE;
}
if (mChannelGroup != null && mChannelGroup.getGroup() == null) {
return !mChannelGroup.isBlocked();
}
return true;
}
/**
* Displays or removes preference in this controller.
*/
@Override
public void displayPreference(PreferenceScreen screen) {
if (isAvailable()) {
final Preference preference = screen.findPreference(getPreferenceKey());
if (mPreference != null && preference == null) {
screen.addPreference(mPreference);
}
if (preference != null) {
mPreference = preference;
}
if (this instanceof Preference.OnPreferenceChangeListener) {
mPreference.setOnPreferenceChangeListener(
(Preference.OnPreferenceChangeListener) this);
}
} else {
findAndRemovePreference(screen, getPreferenceKey());
}
}
// finds the preference recursively and removes it from its parent
private void findAndRemovePreference(PreferenceGroup prefGroup, String key) {
final int preferenceCount = prefGroup.getPreferenceCount();
for (int i = preferenceCount - 1; i >= 0; i--) {
final Preference preference = prefGroup.getPreference(i);
final String curKey = preference.getKey();
if (curKey != null && curKey.equals(key)) {
mPreference = preference;
prefGroup.removePreference(preference);
}
if (preference instanceof PreferenceGroup) {
findAndRemovePreference((PreferenceGroup) preference, key);
}
}
}
protected void onResume(NotificationBackend.AppRow appRow,
@Nullable NotificationChannel channel, @Nullable NotificationChannelGroupWrapper group,
RestrictedLockUtils.EnforcedAdmin admin) {
mAppRow = appRow;
mChannel = channel;
mChannelGroup = group;
mAdmin = admin;
}
protected boolean checkCanBeVisible(int minImportanceVisible) {
if (mChannel == null) {
Log.w(TAG, "No channel");
return false;
}
int importance = mChannel.getImportance();
if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
return true;
}
return importance >= minImportanceVisible;
}
protected void saveChannel() {
if (mChannel != null && mAppRow != null) {
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, mChannel);
}
}
protected boolean isChannelConfigurable() {
if (mChannel != null && mAppRow != null) {
return !Objects.equals(mChannel.getId(), mAppRow.lockedChannelId);
}
return false;
}
protected boolean isChannelBlockable() {
if (mChannel != null && mAppRow != null) {
if (!mAppRow.systemApp) {
return true;
}
return mChannel.isBlockableSystem()
|| mChannel.getImportance() == IMPORTANCE_NONE;
}
return false;
}
protected boolean isChannelGroupBlockable() {
if (mChannelGroup != null && mChannelGroup.getGroup() == null && mAppRow != null) {
if (!mAppRow.systemApp) {
return true;
}
return mChannelGroup.isBlocked();
}
return false;
}
protected boolean hasValidGroup() {
return mChannelGroup != null && mChannelGroup.getGroup() != null;
}
}

View File

@@ -27,10 +27,15 @@ import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.widget.MasterSwitchPreference;
import com.android.settings.widget.SwitchBar;
import com.android.settings.wrapper.NotificationChannelGroupWrapper;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.FooterPreference;
import android.app.Notification;
@@ -53,10 +58,12 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -68,66 +75,30 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
abstract public class NotificationSettingsBase extends SettingsPreferenceFragment {
abstract public class NotificationSettingsBase extends DashboardFragment {
private static final String TAG = "NotifiSettingsBase";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT
= new Intent(Intent.ACTION_MAIN)
.addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES);
protected static final int ORDER_FIRST = -500;
protected static final int ORDER_LAST = 1000;
protected static final String KEY_APP_LINK = "app_link";
protected static final String KEY_HEADER = "header";
protected static final String KEY_BLOCK = "block";
protected static final String KEY_BADGE = "badge";
protected static final String KEY_BYPASS_DND = "bypass_dnd";
protected static final String KEY_VISIBILITY_OVERRIDE = "visibility_override";
protected static final String KEY_BLOCKED_DESC = "block_desc";
protected static final String KEY_ALLOW_SOUND = "allow_sound";
protected PackageManager mPm;
protected UserManager mUm;
protected NotificationBackend mBackend = new NotificationBackend();
protected LockPatternUtils mLockPatternUtils;
protected NotificationManager mNm;
protected Context mContext;
protected boolean mCreated;
protected int mUid;
protected int mUserId;
protected String mPkg;
protected PackageInfo mPkgInfo;
protected RestrictedSwitchPreference mBadge;
protected RestrictedSwitchPreference mPriority;
protected RestrictedDropDownPreference mVisibilityOverride;
protected RestrictedSwitchPreference mImportanceToggle;
protected LayoutPreference mBlockBar;
protected SwitchBar mSwitchBar;
protected FooterPreference mBlockedDesc;
protected Preference mAppLink;
protected EnforcedAdmin mSuspendedAppsAdmin;
protected boolean mDndVisualEffectsSuppressed;
protected NotificationChannelGroup mChannelGroup;
protected NotificationChannelGroupWrapper mChannelGroup;
protected NotificationChannel mChannel;
protected NotificationBackend.AppRow mAppRow;
protected boolean mShowLegacyChannelConfig = false;
protected boolean mShowLegacyChannelConfig = false;
protected boolean mListeningToPackageRemove;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (DEBUG) Log.d(TAG, "onActivityCreated mCreated=" + mCreated);
if (mCreated) {
Log.w(TAG, "onActivityCreated: ignoring duplicate call");
return;
}
mCreated = true;
}
protected List<NotificationPreferenceController> mControllers = new ArrayList<>();
protected List<Preference> mDynamicPreferences = new ArrayList<>();
protected ImportanceListener mImportanceListener = new ImportanceListener();
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -143,7 +114,6 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
}
mPm = getPackageManager();
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mNm = NotificationManager.from(mContext);
mPkg = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_NAME)
@@ -187,35 +157,41 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
return;
}
mAppRow = mBackend.loadAppRow(mContext, mPm, mPkgInfo);
if (mAppRow == null) {
Log.w(TAG, "Can't load package");
finish();
return;
}
collectConfigActivities();
Bundle args = getArguments();
mChannel = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_ID)) ?
mBackend.getChannel(mPkg, mUid, args.getString(Settings.EXTRA_CHANNEL_ID)) : null;
mChannelGroup = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_GROUP_ID)) ?
mBackend.getGroupWithChannels(mPkg, mUid,
NotificationChannelGroup group =
(args != null && args.containsKey(Settings.EXTRA_CHANNEL_GROUP_ID))
? mBackend.getGroupWithChannels(mPkg, mUid,
args.getString(Settings.EXTRA_CHANNEL_GROUP_ID))
: null;
: null;
if (group != null) {
mChannelGroup = new NotificationChannelGroupWrapper(group);
}
mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
mContext, mPkg, mUserId);
NotificationManager.Policy policy = mNm.getNotificationPolicy();
mDndVisualEffectsSuppressed = policy == null ? false : policy.suppressedVisualEffects != 0;
mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
mContext, mPkg, mUserId);
}
mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid)
|| (mChannel != null
&& NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()));
protected void setVisible(Preference p, boolean visible) {
setVisible(getPreferenceScreen(), p, visible);
}
protected void setVisible(PreferenceGroup parent, Preference p, boolean visible) {
final boolean isVisible = parent.findPreference(p.getKey()) != null;
if (isVisible == visible) return;
if (visible) {
parent.addPreference(p);
} else {
parent.removePreference(p);
if (mShowLegacyChannelConfig) {
mChannel = mBackend.getChannel(
mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID);
}
if (mChannel != null && !TextUtils.isEmpty(mChannel.getGroup())) {
group = mBackend.getGroup(mPkg, mUid, mChannel.getGroup());
if (group != null) {
mChannelGroup = new NotificationChannelGroupWrapper(group);
}
}
}
@@ -224,49 +200,34 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
getActivity().finish();
}
private List<ResolveInfo> queryNotificationConfigActivities() {
if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is "
+ APP_NOTIFICATION_PREFS_CATEGORY_INTENT);
protected void collectConfigActivities() {
Intent intent = new Intent(Intent.ACTION_MAIN)
.addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
.setPackage(mAppRow.pkg);
final List<ResolveInfo> resolveInfos = mPm.queryIntentActivities(
APP_NOTIFICATION_PREFS_CATEGORY_INTENT,
intent,
0 //PackageManager.MATCH_DEFAULT_ONLY
);
return resolveInfos;
}
protected void collectConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows) {
final List<ResolveInfo> resolveInfos = queryNotificationConfigActivities();
applyConfigActivities(rows, resolveInfos);
}
private void applyConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows,
List<ResolveInfo> resolveInfos) {
if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities"
+ (resolveInfos.size() == 0 ? " ;_;" : ""));
for (ResolveInfo ri : resolveInfos) {
final ActivityInfo activityInfo = ri.activityInfo;
final ApplicationInfo appInfo = activityInfo.applicationInfo;
final NotificationBackend.AppRow row = rows.get(appInfo.packageName);
if (row == null) {
if (DEBUG) Log.v(TAG, "Ignoring notification preference activity ("
+ activityInfo.name + ") for unknown package "
+ activityInfo.packageName);
continue;
}
if (row.settingsIntent != null) {
if (mAppRow.settingsIntent != null) {
if (DEBUG) Log.v(TAG, "Ignoring duplicate notification preference activity ("
+ activityInfo.name + ") for package "
+ activityInfo.packageName);
continue;
}
row.settingsIntent = new Intent(APP_NOTIFICATION_PREFS_CATEGORY_INTENT)
mAppRow.settingsIntent = intent
.setPackage(null)
.setClassName(activityInfo.packageName, activityInfo.name);
if (mChannel != null) {
row.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId());
mAppRow.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId());
}
if (mChannelGroup != null) {
row.settingsIntent.putExtra(
Notification.EXTRA_CHANNEL_GROUP_ID, mChannelGroup.getId());
mAppRow.settingsIntent.putExtra(
Notification.EXTRA_CHANNEL_GROUP_ID, mChannelGroup.getGroup().getId());
}
}
}
@@ -292,134 +253,6 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
return null;
}
protected void addAppLinkPref() {
if (mAppRow.settingsIntent != null && mAppLink == null) {
addPreferencesFromResource(R.xml.inapp_notification_settings);
mAppLink = findPreference(KEY_APP_LINK);
mAppLink.setIntent(mAppRow.settingsIntent);
}
}
protected void populateDefaultChannelPrefs() {
if (mPkgInfo != null && mChannel != null) {
addPreferencesFromResource(R.xml.legacy_channel_notification_settings);
setupPriorityPref(mChannel.canBypassDnd());
setupVisOverridePref(mChannel.getLockscreenVisibility());
setupImportanceToggle();
setupBadge();
}
mSwitchBar.setChecked(!mAppRow.banned
&& mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE);
}
abstract void setupBadge();
abstract void updateDependents(boolean banned);
// 'allow sound'
private void setupImportanceToggle() {
mImportanceToggle = (RestrictedSwitchPreference) findPreference(KEY_ALLOW_SOUND);
mImportanceToggle.setDisabledByAdmin(mSuspendedAppsAdmin);
mImportanceToggle.setEnabled(isChannelConfigurable(mChannel)
&& !mImportanceToggle.isDisabledByAdmin());
mImportanceToggle.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT
|| mChannel.getImportance() == IMPORTANCE_UNSPECIFIED);
mImportanceToggle.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final int importance =
((Boolean) newValue ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_LOW);
mChannel.setImportance(importance);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
mBackend.updateChannel(mPkg, mUid, mChannel);
updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
return true;
}
});
}
protected void setupPriorityPref(boolean priority) {
mPriority = (RestrictedSwitchPreference) findPreference(KEY_BYPASS_DND);
mPriority.setDisabledByAdmin(mSuspendedAppsAdmin);
mPriority.setEnabled(isChannelConfigurable(mChannel) && !mPriority.isDisabledByAdmin());
mPriority.setChecked(priority);
mPriority.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean bypassZenMode = (Boolean) newValue;
mChannel.setBypassDnd(bypassZenMode);
mChannel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
mBackend.updateChannel(mPkg, mUid, mChannel);
return true;
}
});
}
protected void setupVisOverridePref(int sensitive) {
mVisibilityOverride =
(RestrictedDropDownPreference) findPreference(KEY_VISIBILITY_OVERRIDE);
ArrayList<CharSequence> entries = new ArrayList<>();
ArrayList<CharSequence> values = new ArrayList<>();
mVisibilityOverride.clearRestrictedItems();
if (getLockscreenNotificationsEnabled() && getLockscreenAllowPrivateNotifications()) {
final String summaryShowEntry =
getString(R.string.lock_screen_notifications_summary_show);
final String summaryShowEntryValue =
Integer.toString(NotificationManager.VISIBILITY_NO_OVERRIDE);
entries.add(summaryShowEntry);
values.add(summaryShowEntryValue);
setRestrictedIfNotificationFeaturesDisabled(summaryShowEntry, summaryShowEntryValue,
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
| DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
}
final String summaryHideEntry = getString(R.string.lock_screen_notifications_summary_hide);
final String summaryHideEntryValue = Integer.toString(Notification.VISIBILITY_PRIVATE);
entries.add(summaryHideEntry);
values.add(summaryHideEntryValue);
setRestrictedIfNotificationFeaturesDisabled(summaryHideEntry, summaryHideEntryValue,
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
entries.add(getString(R.string.lock_screen_notifications_summary_disable));
values.add(Integer.toString(Notification.VISIBILITY_SECRET));
mVisibilityOverride.setEntries(entries.toArray(new CharSequence[entries.size()]));
mVisibilityOverride.setEntryValues(values.toArray(new CharSequence[values.size()]));
if (sensitive == NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
mVisibilityOverride.setValue(Integer.toString(getGlobalVisibility()));
} else {
mVisibilityOverride.setValue(Integer.toString(sensitive));
}
mVisibilityOverride.setSummary("%s");
mVisibilityOverride.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
int sensitive = Integer.parseInt((String) newValue);
if (sensitive == getGlobalVisibility()) {
sensitive = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
}
mChannel.setLockscreenVisibility(sensitive);
mChannel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
mBackend.updateChannel(mPkg, mUid, mChannel);
return true;
}
});
mVisibilityOverride.setDisabledByAdmin(mSuspendedAppsAdmin);
}
protected void setupBlockDesc(int summaryResId) {
mBlockedDesc = new FooterPreference(getPrefContext());
mBlockedDesc.setSelectable(false);
mBlockedDesc.setTitle(summaryResId);
mBlockedDesc.setEnabled(false);
mBlockedDesc.setOrder(50);
mBlockedDesc.setKey(KEY_BLOCKED_DESC);
getPreferenceScreen().addPreference(mBlockedDesc);
}
protected Preference populateSingleChannelPrefs(PreferenceGroup parent,
final NotificationChannel channel, String summary) {
MasterSwitchPreference channelPref = new MasterSwitchPreference(
@@ -461,105 +294,48 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
return channelPref;
}
protected boolean checkCanBeVisible(int minImportanceVisible) {
int importance = mChannel.getImportance();
if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
return true;
}
return importance >= minImportanceVisible;
}
protected String getImportanceSummary(NotificationChannel channel) {
switch (channel.getImportance()) {
case NotificationManager.IMPORTANCE_UNSPECIFIED:
return getContext().getString(R.string.notification_importance_unspecified);
case NotificationManager.IMPORTANCE_NONE:
return getContext().getString(R.string.notification_toggle_off);
case NotificationManager.IMPORTANCE_MIN:
return getContext().getString(R.string.notification_importance_min);
case NotificationManager.IMPORTANCE_LOW:
return getContext().getString(R.string.notification_importance_low);
case NotificationManager.IMPORTANCE_DEFAULT:
if (hasValidSound(channel)) {
return getContext().getString(R.string.notification_importance_default);
} else { // Silent
return getContext().getString(R.string.notification_importance_low);
}
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
default:
if (hasValidSound(channel)) {
return getContext().getString(R.string.notification_importance_high);
} else { // Silent
return getContext().getString(R.string.notification_importance_high_silent);
}
}
}
private void setRestrictedIfNotificationFeaturesDisabled(CharSequence entry,
CharSequence entryValue, int keyguardNotificationFeatures) {
RestrictedLockUtils.EnforcedAdmin admin =
RestrictedLockUtils.checkIfKeyguardFeaturesDisabled(
mContext, keyguardNotificationFeatures, mUserId);
if (admin != null) {
RestrictedDropDownPreference.RestrictedItem item =
new RestrictedDropDownPreference.RestrictedItem(entry, entryValue, admin);
mVisibilityOverride.addRestrictedItem(item);
}
}
private int getGlobalVisibility() {
int globalVis = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
if (!getLockscreenNotificationsEnabled()) {
globalVis = Notification.VISIBILITY_SECRET;
} else if (!getLockscreenAllowPrivateNotifications()) {
globalVis = Notification.VISIBILITY_PRIVATE;
}
return globalVis;
}
private boolean getLockscreenNotificationsEnabled() {
return Settings.Secure.getInt(getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0;
}
private boolean getLockscreenAllowPrivateNotifications() {
return Settings.Secure.getInt(getContentResolver(),
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0;
}
protected boolean isLockScreenSecure() {
if (mLockPatternUtils == null) {
mLockPatternUtils = new LockPatternUtils(getActivity());
}
boolean lockscreenSecure = mLockPatternUtils.isSecure(UserHandle.myUserId());
UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId());
if (parentUser != null){
lockscreenSecure |= mLockPatternUtils.isSecure(parentUser.id);
}
return lockscreenSecure;
}
protected boolean isChannelConfigurable(NotificationChannel channel) {
return !channel.getId().equals(mAppRow.lockedChannelId);
if (channel != null && mAppRow != null) {
return !channel.getId().equals(mAppRow.lockedChannelId);
}
return false;
}
protected boolean isChannelBlockable(NotificationChannel channel) {
if (!mAppRow.systemApp) {
return true;
}
if (channel != null && mAppRow != null) {
if (!mAppRow.systemApp) {
return true;
}
return channel.isBlockableSystem()
|| channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
return channel.isBlockableSystem()
|| channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
}
return false;
}
protected boolean isChannelGroupBlockable(NotificationChannelGroup group) {
if (!mAppRow.systemApp) {
return true;
}
if (group != null && mAppRow != null) {
if (!mAppRow.systemApp) {
return true;
}
return group.isBlocked();
return group.isBlocked();
}
return false;
}
protected void setVisible(Preference p, boolean visible) {
setVisible(getPreferenceScreen(), p, visible);
}
protected void setVisible(PreferenceGroup parent, Preference p, boolean visible) {
final boolean isVisible = parent.findPreference(p.getKey()) != null;
if (isVisible == visible) return;
if (visible) {
parent.addPreference(p);
} else {
parent.removePreference(p);
}
}
protected void startListeningToPackageRemove() {
@@ -604,7 +380,45 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
return left.getId().compareTo(right.getId());
};
boolean hasValidSound(NotificationChannel channel) {
return channel.getSound() != null && !Uri.EMPTY.equals(channel.getSound());
/**
* These screens aren't searchable - they only make sense in the context of an app, so
* surfacing a generic version would be impossible.
*/
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
return new ArrayList<>();
}
@Override
public List<AbstractPreferenceController> getPreferenceControllers(Context context) {
return getPreferenceControllers(context);
}
};
protected class ImportanceListener {
protected void onImportanceChanged() {
final PreferenceScreen screen = getPreferenceScreen();
for (NotificationPreferenceController controller : mControllers) {
controller.displayPreference(screen);
}
updatePreferenceStates();
boolean hideDynamicFields = false;
if (mAppRow == null || mAppRow.banned) {
hideDynamicFields = true;
} else {
if (mChannel != null) {
hideDynamicFields = mChannel.getImportance() == IMPORTANCE_NONE;
} else if (mChannelGroup != null) {
hideDynamicFields = mChannelGroup.isBlocked();
}
}
for (Preference preference : mDynamicPreferences) {
setVisible(getPreferenceScreen(), preference, !hideDynamicFields);
}
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.support.v7.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.widget.FooterPreference;
public class NotificationsOffPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String KEY_BLOCKED_DESC = "block_desc";
public NotificationsOffPreferenceController(Context context) {
super(context, null);
}
@Override
public String getPreferenceKey() {
return KEY_BLOCKED_DESC;
}
@Override
public boolean isAvailable() {
if (mAppRow == null) {
return false;
}
// Available only when other controllers are unavailable - this UI replaces the UI that
// would give more detailed notification controls.
return !super.isAvailable();
}
public void updateState(Preference preference) {
if (mAppRow != null) {
if (mChannel != null) {
preference.setTitle(R.string.channel_notifications_off_desc);
} else if (mChannelGroup != null && mChannelGroup.getGroup() == null) {
preference.setTitle(R.string.channel_group_notifications_off_desc);
} else {
preference.setTitle(R.string.app_notifications_off_desc);
}
}
preference.setEnabled(false);
preference.setSelectable(false);
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2017 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;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.PreferenceControllerMixin;
public class SoundPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener,
PreferenceManager.OnActivityResultListener {
private static final String KEY_SOUND = "ringtone";
private final SettingsPreferenceFragment mFragment;
private final NotificationSettingsBase.ImportanceListener mListener;
private NotificationSoundPreference mPreference;
protected static final int CODE = 200;
public SoundPreferenceController(Context context, SettingsPreferenceFragment hostFragment,
NotificationSettingsBase.ImportanceListener importanceListener,
NotificationBackend backend) {
super(context, backend);
mFragment = hostFragment;
mListener = importanceListener;
}
@Override
public String getPreferenceKey() {
return KEY_SOUND;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mChannel == null) {
return false;
}
return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId());
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = (NotificationSoundPreference) screen.findPreference(getPreferenceKey());
}
public void updateState(Preference preference) {
if (mAppRow!= null && mChannel != null) {
NotificationSoundPreference pref = (NotificationSoundPreference) preference;
pref.setEnabled(mAdmin == null && isChannelConfigurable());
pref.setRingtone(mChannel.getSound());
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
mChannel.setSound((Uri) newValue, mChannel.getAudioAttributes());
saveChannel();
}
return true;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY_SOUND.equals(preference.getKey()) && mFragment != null) {
NotificationSoundPreference pref = (NotificationSoundPreference) preference;
pref.onPrepareRingtonePickerIntent(pref.getIntent());
mFragment.startActivityForResult(preference.getIntent(), CODE);
return true;
}
return false;
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (CODE == requestCode) {
if (mPreference != null) {
mPreference.onActivityResult(requestCode, resultCode, data);
}
// the importance hasn't changed, but the importance description might as a result of
// user's selection.
mListener.onImportanceChanged();
return true;
}
return false;
}
protected static boolean hasValidSound(NotificationChannel channel) {
return channel != null
&& channel.getSound() != null && !Uri.EMPTY.equals(channel.getSound());
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 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;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Vibrator;
import android.support.v7.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.RestrictedSwitchPreference;
public class VibrationPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_VIBRATE = "vibrate";
private final Vibrator mVibrator;
public VibrationPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
@Override
public String getPreferenceKey() {
return KEY_VIBRATE;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable() || mChannel == null) {
return false;
}
return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())
&& mVibrator != null
&& mVibrator.hasVibrator();
}
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(!pref.isDisabledByAdmin() && isChannelConfigurable());
pref.setChecked(mChannel.shouldVibrate());
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final boolean vibrate = (Boolean) newValue;
mChannel.enableVibration(vibrate);
saveChannel();
}
return true;
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) 2017 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;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.support.v7.preference.Preference;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.RestrictedLockUtils;
import java.util.ArrayList;
public class VisibilityPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String TAG = "ChannelVisPrefContr";
private static final String KEY_VISIBILITY_OVERRIDE = "visibility_override";
private LockPatternUtils mLockPatternUtils;
public VisibilityPreferenceController(Context context, LockPatternUtils utils,
NotificationBackend backend) {
super(context, backend);
mLockPatternUtils = utils;
}
@Override
public String getPreferenceKey() {
return KEY_VISIBILITY_OVERRIDE;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mChannel == null || mAppRow.banned) {
return false;
}
return checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) && isLockScreenSecure();
}
public void updateState(Preference preference) {
if (mChannel != null && mAppRow != null) {
RestrictedDropDownPreference pref = (RestrictedDropDownPreference) preference;
ArrayList<CharSequence> entries = new ArrayList<>();
ArrayList<CharSequence> values = new ArrayList<>();
pref.clearRestrictedItems();
if (getLockscreenNotificationsEnabled() && getLockscreenAllowPrivateNotifications()) {
final String summaryShowEntry =
mContext.getString(R.string.lock_screen_notifications_summary_show);
final String summaryShowEntryValue =
Integer.toString(NotificationManager.VISIBILITY_NO_OVERRIDE);
entries.add(summaryShowEntry);
values.add(summaryShowEntryValue);
setRestrictedIfNotificationFeaturesDisabled(pref, summaryShowEntry,
summaryShowEntryValue,
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
| DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
}
final String summaryHideEntry =
mContext.getString(R.string.lock_screen_notifications_summary_hide);
final String summaryHideEntryValue = Integer.toString(Notification.VISIBILITY_PRIVATE);
entries.add(summaryHideEntry);
values.add(summaryHideEntryValue);
setRestrictedIfNotificationFeaturesDisabled(pref,
summaryHideEntry, summaryHideEntryValue,
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
entries.add(mContext.getString(R.string.lock_screen_notifications_summary_disable));
values.add(Integer.toString(Notification.VISIBILITY_SECRET));
pref.setEntries(entries.toArray(new CharSequence[entries.size()]));
pref.setEntryValues(values.toArray(new CharSequence[values.size()]));
if (mChannel.getLockscreenVisibility()
== NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
pref.setValue(Integer.toString(getGlobalVisibility()));
} else {
pref.setValue(Integer.toString(mChannel.getLockscreenVisibility()));
}
pref.setSummary("%s");
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
int sensitive = Integer.parseInt((String) newValue);
if (sensitive == getGlobalVisibility()) {
sensitive = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
}
mChannel.setLockscreenVisibility(sensitive);
mChannel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
saveChannel();
}
return true;
}
private void setRestrictedIfNotificationFeaturesDisabled(RestrictedDropDownPreference pref,
CharSequence entry, CharSequence entryValue, int keyguardNotificationFeatures) {
RestrictedLockUtils.EnforcedAdmin admin =
RestrictedLockUtils.checkIfKeyguardFeaturesDisabled(
mContext, keyguardNotificationFeatures, mAppRow.userId);
if (admin != null) {
RestrictedDropDownPreference.RestrictedItem item =
new RestrictedDropDownPreference.RestrictedItem(entry, entryValue, admin);
pref.addRestrictedItem(item);
}
}
private int getGlobalVisibility() {
int globalVis = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
if (!getLockscreenNotificationsEnabled()) {
globalVis = Notification.VISIBILITY_SECRET;
} else if (!getLockscreenAllowPrivateNotifications()) {
globalVis = Notification.VISIBILITY_PRIVATE;
}
return globalVis;
}
private boolean getLockscreenNotificationsEnabled() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0;
}
private boolean getLockscreenAllowPrivateNotifications() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0;
}
protected boolean isLockScreenSecure() {
boolean lockscreenSecure = mLockPatternUtils.isSecure(UserHandle.myUserId());
UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId());
if (parentUser != null){
lockscreenSecure |= mLockPatternUtils.isSecure(parentUser.id);
}
return lockscreenSecure;
}
}

View File

@@ -64,7 +64,10 @@ import com.android.settings.location.LocationSettings;
import com.android.settings.location.ScanningSettings;
import com.android.settings.network.NetworkDashboardFragment;
import com.android.settings.nfc.PaymentSettings;
import com.android.settings.notification.AppNotificationSettings;
import com.android.settings.notification.ChannelGroupNotificationSettings;
import com.android.settings.notification.ChannelImportanceSettings;
import com.android.settings.notification.ChannelNotificationSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.SoundSettings;
import com.android.settings.notification.ZenModeAutomationSettings;
@@ -168,6 +171,10 @@ public final class SearchIndexableResources {
addIndex(LockscreenDashboardFragment.class);
addIndex(ZenModeBehaviorSettings.class);
addIndex(ZenModeAutomationSettings.class);
addIndex(AppNotificationSettings.class);
addIndex(ChannelNotificationSettings.class);
addIndex(ChannelImportanceSettings.class);
addIndex(ChannelGroupNotificationSettings.class);
}
private SearchIndexableResources() {

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2017 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.wrapper;
import android.app.NotificationChannelGroup;
/**
* Wrapper for {@link NotificationChannelGroup} until roboletric supports O MR1.
*/
public class NotificationChannelGroupWrapper {
private final NotificationChannelGroup mGroup;
public NotificationChannelGroupWrapper(NotificationChannelGroup group) {
mGroup = group;
}
/**
* Get the real group object so we can call APIs directly on it.
*/
public NotificationChannelGroup getGroup() {
return mGroup;
}
public String getDescription() {
if (mGroup != null) {
return mGroup.getDescription();
}
return null;
}
public void setDescription(String desc) {
if (mGroup != null) {
mGroup.setDescription(desc);
}
}
public boolean isBlocked() {
if (mGroup != null) {
return mGroup.isBlocked();
}
return true;
}
public void setBlocked(boolean blocked) {
if (mGroup != null) {
mGroup.setBlocked(blocked);
}
}
}