Reorganize notification Settings classes

The notification package was getting too big.

Test: make -j64 RunSettingsRoboTests
Fixes: 145224451
Change-Id: I25ba82f42f7a137d8adcce72dcf8089d0e018bdc
This commit is contained in:
Julia Reynolds
2019-11-26 16:14:03 -05:00
parent c050807cdc
commit aceccce75c
201 changed files with 402 additions and 388 deletions

View File

@@ -0,0 +1,84 @@
/*
* 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.app;
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.util.Log;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
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 NotificationSettings.ImportanceListener mImportanceListener;
public AllowSoundPreferenceController(Context context,
NotificationSettings.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());
}
@Override
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(!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,117 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.R;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
@SearchIndexable
public class AppBubbleNotificationSettings extends NotificationSettings implements
GlobalBubblePermissionObserverMixin.Listener {
private static final String TAG = "AppBubNotiSettings";
private GlobalBubblePermissionObserverMixin mObserverMixin;
@Override
public int getMetricsCategory() {
return SettingsEnums.APP_BUBBLE_SETTINGS;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.app_bubble_notification_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
mControllers = getPreferenceControllers(context, this);
return new ArrayList<>(mControllers);
}
protected static List<NotificationPreferenceController> getPreferenceControllers(
Context context, AppBubbleNotificationSettings fragment) {
List<NotificationPreferenceController> controllers = new ArrayList<>();
controllers.add(new HeaderPreferenceController(context, fragment));
controllers.add(new BubblePreferenceController(context, fragment != null
? fragment.getChildFragmentManager()
: null,
new NotificationBackend(), true /* isAppPage */));
return controllers;
}
@Override
public void onGlobalBubblePermissionChanged() {
updatePreferenceStates();
}
@Override
public void onResume() {
super.onResume();
if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
Log.w(TAG, "Missing package or uid or packageinfo");
finish();
return;
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
mObserverMixin = new GlobalBubblePermissionObserverMixin(getContext(), this);
mObserverMixin.onStart();
}
@Override
public void onPause() {
mObserverMixin.onStop();
super.onPause();
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
protected boolean isPageSearchEnabled(Context context) {
return false;
}
@Override
public List<AbstractPreferenceController> createPreferenceControllers(Context
context) {
return new ArrayList<>(AppBubbleNotificationSettings.getPreferenceControllers(
context, null));
}
};
}

View File

@@ -0,0 +1,56 @@
/*
* 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.app;
import android.content.Context;
import androidx.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

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
/** These settings are per app, so should not be returned in global search results. */
public class AppNotificationSettings extends NotificationSettings {
private static final String TAG = "AppNotificationSettings";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static String KEY_ADVANCED_CATEGORY = "app_advanced";
private static String KEY_BADGE = "badge";
private static String KEY_APP_LINK = "app_link";
private static String KEY_BUBBLE = "bubble_link_pref";
private static String[] LEGACY_NON_ADVANCED_KEYS = {KEY_BADGE, KEY_APP_LINK, KEY_BUBBLE};
@Override
public int getMetricsCategory() {
return SettingsEnums.NOTIFICATION_APP_NOTIFICATION;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final PreferenceScreen screen = getPreferenceScreen();
if (mShowLegacyChannelConfig && screen != null) {
// if showing legacy settings, pull advanced settings out of the advanced category
PreferenceGroup advanced = (PreferenceGroup) findPreference(KEY_ADVANCED_CATEGORY);
removePreference(KEY_ADVANCED_CATEGORY);
if (advanced != null) {
for (String key : LEGACY_NON_ADVANCED_KEYS) {
Preference pref = advanced.findPreference(key);
advanced.removePreference(pref);
if (pref != null) {
screen.addPreference(pref);
}
}
}
}
}
@Override
public void onResume() {
super.onResume();
if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
Log.w(TAG, "Missing package or uid or packageinfo");
finish();
return;
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.app_notification_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(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, mImportanceListener, mBackend));
mControllers.add(new MinImportancePreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new HighImportancePreferenceController(
context, mImportanceListener, mBackend));
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, mBackend));
mControllers.add(new AppLinkPreferenceController(context));
mControllers.add(new DescriptionPreferenceController(context));
mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
mControllers.add(new ChannelListPreferenceController(context, mBackend));
return new ArrayList<>(mControllers);
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.app;
import static android.provider.Settings.Secure.NOTIFICATION_BADGING;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
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) {
if (isDefaultChannel()) {
return true;
} else {
return mAppRow == null ? false : mAppRow.showBadge;
}
}
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(!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,143 @@
/*
* 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.app;
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.widget.Switch;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.widget.SwitchBar;
import com.android.settingslib.widget.LayoutPreference;
public class BlockPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, SwitchBar.OnSwitchChangeListener {
private static final String KEY_BLOCK = "block";
private NotificationSettings.ImportanceListener mImportanceListener;
public BlockPreferenceController(Context context,
NotificationSettings.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;
}
return true;
}
public void updateState(Preference preference) {
LayoutPreference pref = (LayoutPreference) preference;
pref.setSelectable(false);
SwitchBar bar = pref.findViewById(R.id.switch_bar);
if (bar != null) {
String switchBarText = getSwitchBarText();
bar.setSwitchBarText(switchBarText, switchBarText);
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 && !isChannelBlockable()) {
bar.setEnabled(false);
}
if (mChannelGroup != null && !isChannelGroupBlockable()) {
bar.setEnabled(false);
}
if (mChannel == null && mAppRow.systemApp
&& (!mAppRow.banned || mAppRow.lockedImportance)) {
bar.setEnabled(false);
}
if (mChannel != null) {
bar.setChecked(!mAppRow.banned
&& mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE);
} else if (mChannelGroup != 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
: isDefaultChannel() ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_DEFAULT;
mChannel.setImportance(importance);
saveChannel();
}
if (mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid)) {
if (mAppRow.banned != blocked) {
mAppRow.banned = blocked;
mBackend.setNotificationsEnabledForPackage(mAppRow.pkg, mAppRow.uid, !blocked);
}
}
} else if (mChannelGroup != null) {
mChannelGroup.setBlocked(blocked);
mBackend.updateChannelGroup(mAppRow.pkg, mAppRow.uid, mChannelGroup);
} else if (mAppRow != null) {
mAppRow.banned = blocked;
mBackend.setNotificationsEnabledForPackage(mAppRow.pkg, mAppRow.uid, !blocked);
}
mImportanceListener.onImportanceChanged();
}
String getSwitchBarText() {
if (mChannel != null) {
return mContext.getString(R.string.notification_content_block_title);
} else {
CharSequence fieldContextName;
if (mChannelGroup != null) {
fieldContextName = mChannelGroup.getName();
} else {
fieldContextName = mAppRow.label;
}
return mContext.getString(R.string.notification_switch_label, fieldContextName);
}
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.annotation.Nullable;
import android.content.Context;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
public class BubblePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String TAG = "BubblePrefContr";
private static final String KEY = "bubble_pref";
@VisibleForTesting
static final int SYSTEM_WIDE_ON = 1;
@VisibleForTesting
static final int SYSTEM_WIDE_OFF = 0;
private FragmentManager mFragmentManager;
private boolean mIsAppPage;
public BubblePreferenceController(Context context, @Nullable FragmentManager fragmentManager,
NotificationBackend backend, boolean isAppPage) {
super(context, backend);
mFragmentManager = fragmentManager;
mIsAppPage = isAppPage;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (!mIsAppPage && !isGloballyEnabled()) {
return false;
}
if (mChannel != null) {
if (isDefaultChannel()) {
return true;
} else {
return mAppRow != null && mAppRow.allowBubbles;
}
}
return true;
}
public void updateState(Preference preference) {
if (mAppRow != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
if (mChannel != null) {
pref.setChecked(mChannel.canBubble() && isGloballyEnabled());
pref.setEnabled(!pref.isDisabledByAdmin());
} else {
pref.setChecked(mAppRow.allowBubbles && isGloballyEnabled());
pref.setSummary(mContext.getString(
R.string.bubbles_app_toggle_summary, mAppRow.label));
}
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean value = (Boolean) newValue && isGloballyEnabled();
if (mChannel != null) {
mChannel.setAllowBubbles(value);
saveChannel();
return true;
} else if (mAppRow != null && mFragmentManager != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
// if the global setting is off, toggling app level permission requires extra
// confirmation
if (!isGloballyEnabled() && !pref.isChecked()) {
new BubbleWarningDialogFragment()
.setPkgInfo(mAppRow.pkg, mAppRow.uid)
.show(mFragmentManager, "dialog");
return false;
} else {
mAppRow.allowBubbles = value;
mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
}
}
return true;
}
private boolean isGloballyEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON;
}
// Used in app level prompt that confirms the user is ok with turning on bubbles
// globally. If they aren't, undo what
public static void revertBubblesApproval(Context mContext, String pkg, int uid) {
NotificationBackend backend = new NotificationBackend();
backend.setAllowBubbles(pkg, uid, false);
// changing the global settings will cause the observer on the host page to reload
// correct preference state
Settings.Global.putInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
}
// Apply global bubbles approval
public static void applyBubblesApproval(Context mContext, String pkg, int uid) {
NotificationBackend backend = new NotificationBackend();
backend.setAllowBubbles(pkg, uid, true);
// changing the global settings will cause the observer on the host page to reload
// correct preference state
Settings.Global.putInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
public class BubbleSummaryPreferenceController extends NotificationPreferenceController {
private static final String KEY = "bubble_link_pref";
@VisibleForTesting
static final int SYSTEM_WIDE_ON = 1;
@VisibleForTesting
static final int SYSTEM_WIDE_OFF = 0;
public BubbleSummaryPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mAppRow == null && mChannel == null) {
return false;
}
if (mChannel != null) {
if (!isGloballyEnabled()) {
return false;
}
if (isDefaultChannel()) {
return true;
} else {
return mAppRow != null && mAppRow.allowBubbles;
}
}
return isGloballyEnabled();
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (mAppRow != null) {
Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
args.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(AppBubbleNotificationSettings.class.getName())
.setArguments(args)
.setSourceMetricsCategory(
SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
.toIntent());
}
}
@Override
public CharSequence getSummary() {
boolean canBubble = false;
if (mAppRow != null) {
if (mChannel != null) {
canBubble |= mChannel.canBubble() && isGloballyEnabled();
} else {
canBubble |= mAppRow.allowBubbles && isGloballyEnabled();
}
}
return mContext.getString(canBubble ? R.string.switch_on_text : R.string.switch_off_text);
}
private boolean isGloballyEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
static final String KEY_PKG = "p";
static final String KEY_UID = "u";
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_APP_BUBBLE_SETTINGS;
}
public BubbleWarningDialogFragment setPkgInfo(String pkg, int uid) {
Bundle args = new Bundle();
args.putString(KEY_PKG, pkg);
args.putInt(KEY_UID, uid);
setArguments(args);
return this;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
final String pkg = args.getString(KEY_PKG);
final int uid = args.getInt(KEY_UID);
final String title =
getResources().getString(R.string.bubbles_feature_disabled_dialog_title);
final String summary = getResources()
.getString(R.string.bubbles_feature_disabled_dialog_text);
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setTitle(title)
.setCancelable(true)
.setPositiveButton(R.string.bubbles_feature_disabled_button_approve,
(dialog, id) ->
BubblePreferenceController.applyBubblesApproval(
getContext(), pkg, uid))
.setNegativeButton(R.string.bubbles_feature_disabled_button_cancel,
(dialog, id) ->
BubblePreferenceController.revertBubblesApproval(
getContext(), pkg, uid))
.create();
}
}

View File

@@ -0,0 +1,313 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.widget.MasterSwitchPreference;
import com.android.settingslib.RestrictedSwitchPreference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
public class ChannelListPreferenceController extends NotificationPreferenceController {
private static final String KEY = "channels";
private static String KEY_GENERAL_CATEGORY = "categories";
public static final String ARG_FROM_SETTINGS = "fromSettings";
private List<NotificationChannelGroup> mChannelGroupList;
private PreferenceCategory mPreference;
public ChannelListPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
if (mAppRow == null) {
return false;
}
if (mAppRow.banned) {
return false;
}
if (mChannel != null) {
if (mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid)
|| NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
return false;
}
}
return true;
}
@Override
public void updateState(Preference preference) {
mPreference = (PreferenceCategory) preference;
// Load channel settings
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... unused) {
mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList();
Collections.sort(mChannelGroupList, mChannelGroupComparator);
return null;
}
@Override
protected void onPostExecute(Void unused) {
if (mContext == null) {
return;
}
populateList();
}
}.execute();
}
private void populateList() {
// TODO: if preference has children, compare with newly loaded list
mPreference.removeAll();
if (mChannelGroupList.isEmpty()) {
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
groupCategory.setTitle(R.string.notification_channels);
groupCategory.setKey(KEY_GENERAL_CATEGORY);
mPreference.addPreference(groupCategory);
Preference empty = new Preference(mContext);
empty.setTitle(R.string.no_channels);
empty.setEnabled(false);
groupCategory.addPreference(empty);
} else {
populateGroupList();
}
}
private void populateGroupList() {
for (NotificationChannelGroup group : mChannelGroupList) {
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
groupCategory.setOrderingAsAdded(true);
mPreference.addPreference(groupCategory);
if (group.getId() == null) {
if (mChannelGroupList.size() > 1) {
groupCategory.setTitle(R.string.notification_channels_other);
}
groupCategory.setKey(KEY_GENERAL_CATEGORY);
} else {
groupCategory.setTitle(group.getName());
groupCategory.setKey(group.getId());
populateGroupToggle(groupCategory, group);
}
if (!group.isBlocked()) {
final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, mChannelComparator);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
}
}
}
}
protected void populateGroupToggle(final PreferenceGroup parent,
NotificationChannelGroup group) {
RestrictedSwitchPreference preference =
new RestrictedSwitchPreference(mContext);
preference.setTitle(mContext.getString(
R.string.notification_switch_label, group.getName()));
preference.setEnabled(mAdmin == null
&& isChannelGroupBlockable(group));
preference.setChecked(!group.isBlocked());
preference.setOnPreferenceClickListener(preference1 -> {
final boolean allowGroup = ((SwitchPreference) preference1).isChecked();
group.setBlocked(!allowGroup);
mBackend.updateChannelGroup(mAppRow.pkg, mAppRow.uid, group);
onGroupBlockStateChanged(group);
return true;
});
parent.addPreference(preference);
}
protected Preference populateSingleChannelPrefs(PreferenceGroup parent,
final NotificationChannel channel, final boolean groupBlocked) {
MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
channelPref.setSwitchEnabled(mAdmin == null
&& isChannelBlockable(channel)
&& isChannelConfigurable(channel)
&& !groupBlocked);
channelPref.setIcon(null);
if (channel.getImportance() > IMPORTANCE_LOW) {
channelPref.setIcon(getAlertingIcon());
}
channelPref.setIconSize(MasterSwitchPreference.ICON_SIZE_SMALL);
channelPref.setKey(channel.getId());
channelPref.setTitle(channel.getName());
channelPref.setSummary(NotificationBackend.getSentSummary(
mContext, mAppRow.sentByChannel.get(channel.getId()), false));
channelPref.setChecked(channel.getImportance() != IMPORTANCE_NONE);
Bundle channelArgs = new Bundle();
channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
channelArgs.putBoolean(ARG_FROM_SETTINGS, true);
channelPref.setIntent(new SubSettingLauncher(mContext)
.setDestination(ChannelNotificationSettings.class.getName())
.setArguments(channelArgs)
.setTitleRes(R.string.notification_channel_title)
.setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
.toIntent());
channelPref.setOnPreferenceChangeListener(
(preference, o) -> {
boolean value = (Boolean) o;
int importance = value ? IMPORTANCE_LOW : IMPORTANCE_NONE;
channel.setImportance(importance);
channel.lockFields(
NotificationChannel.USER_LOCKED_IMPORTANCE);
MasterSwitchPreference channelPref1 = (MasterSwitchPreference) preference;
channelPref1.setIcon(null);
if (channel.getImportance() > IMPORTANCE_LOW) {
channelPref1.setIcon(getAlertingIcon());
}
toggleBehaviorIconState(channelPref1.getIcon(),
importance != IMPORTANCE_NONE);
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
return true;
});
if (parent.findPreference(channelPref.getKey()) == null) {
parent.addPreference(channelPref);
}
return channelPref;
}
private Drawable getAlertingIcon() {
Drawable icon = mContext.getDrawable(R.drawable.ic_notifications_alert);
icon.setTintList(Utils.getColorAccent(mContext));
return icon;
}
private void toggleBehaviorIconState(Drawable icon, boolean enabled) {
if (icon == null) return;
LayerDrawable layerDrawable = (LayerDrawable) icon;
GradientDrawable background =
(GradientDrawable) layerDrawable.findDrawableByLayerId(R.id.back);
if (background == null) return;
if (enabled) {
background.clearColorFilter();
} else {
background.setColorFilter(new BlendModeColorFilter(
mContext.getColor(R.color.material_grey_300),
BlendMode.SRC_IN));
}
}
protected void onGroupBlockStateChanged(NotificationChannelGroup group) {
if (group == null) {
return;
}
PreferenceGroup groupGroup = mPreference.findPreference(group.getId());
if (groupGroup != null) {
if (group.isBlocked()) {
List<Preference> toRemove = new ArrayList<>();
int childCount = groupGroup.getPreferenceCount();
for (int i = 0; i < childCount; i++) {
Preference pref = groupGroup.getPreference(i);
if (pref instanceof MasterSwitchPreference) {
toRemove.add(pref);
}
}
for (Preference pref : toRemove) {
groupGroup.removePreference(pref);
}
} else {
final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, mChannelComparator);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
populateSingleChannelPrefs(groupGroup, channel, group.isBlocked());
}
}
}
}
private Comparator<NotificationChannelGroup> mChannelGroupComparator =
new Comparator<NotificationChannelGroup>() {
@Override
public int compare(NotificationChannelGroup left, NotificationChannelGroup right) {
// Non-grouped channels (in placeholder group with a null id) come last
if (left.getId() == null && right.getId() != null) {
return 1;
} else if (right.getId() == null && left.getId() != null) {
return -1;
}
return left.getId().compareTo(right.getId());
}
};
protected Comparator<NotificationChannel> mChannelComparator =
(left, right) -> {
if (left.isDeleted() != right.isDeleted()) {
return Boolean.compare(left.isDeleted(), right.isDeleted());
} else if (left.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
// Uncategorized/miscellaneous legacy channel goes last
return 1;
} else if (right.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
return -1;
}
return left.getId().compareTo(right.getId());
};
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static com.android.settings.notification.app.ChannelListPreferenceController.ARG_FROM_SETTINGS;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.preference.PreferenceScreen;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
public class ChannelNotificationSettings extends NotificationSettings {
private static final String TAG = "ChannelSettings";
@Override
public int getMetricsCategory() {
return SettingsEnums.NOTIFICATION_TOPIC_NOTIFICATION;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final PreferenceScreen screen = getPreferenceScreen();
Bundle args = getArguments();
// If linking to this screen from an external app, expand settings
if (screen != null && args != null) {
if (!args.getBoolean(ARG_FROM_SETTINGS, false)) {
screen.setInitialExpandedChildrenCount(Integer.MAX_VALUE);
}
}
}
@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");
finish();
return;
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
for (NotificationPreferenceController controller : mControllers) {
if (controller instanceof PreferenceManager.OnActivityResultListener) {
((PreferenceManager.OnActivityResultListener) controller)
.onActivityResult(requestCode, resultCode, data);
}
}
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.channel_notification_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
mControllers = new ArrayList<>();
mControllers.add(new HeaderPreferenceController(context, this));
mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend));
mControllers.add(new ImportancePreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new MinImportancePreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new HighImportancePreferenceController(
context, mImportanceListener, mBackend));
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, mBackend));
mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new BubblePreferenceController(context, getChildFragmentManager(),
mBackend, false /* isAppPage */));
return new ArrayList<>(mControllers);
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.app;
import android.content.Context;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
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.setSelectable(false);
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.app;
import android.content.Context;
import android.text.TextUtils;
import androidx.preference.Preference;
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,69 @@
/*
* 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.app;
import android.app.NotificationChannel;
import android.content.Context;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
public class DndPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_BYPASS_DND = "bypass_dnd";
public DndPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY_BYPASS_DND;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable() || mChannel == null) {
return false;
}
return true;
}
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(!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,59 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
public class GlobalBubblePermissionObserverMixin extends ContentObserver {
public interface Listener {
void onGlobalBubblePermissionChanged();
}
private final Context mContext;
private final Listener mListener;
public GlobalBubblePermissionObserverMixin(Context context, Listener listener) {
super(new Handler(Looper.getMainLooper()));
mContext = context;
mListener = listener;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (mListener != null) {
mListener.onGlobalBubblePermissionChanged();
}
}
public void onStart() {
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(
Settings.Global.NOTIFICATION_BUBBLES),
false /* notifyForDescendants */,
this /* observer */);
}
public void onStop() {
mContext.getContentResolver().unregisterContentObserver(this /* observer */);
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.app;
import static com.android.settings.widget.EntityHeaderController.PREF_KEY_APP_HEADER;
import android.app.Activity;
import android.content.Context;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.LayoutPreference;
public class HeaderPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, LifecycleObserver {
private final DashboardFragment mFragment;
private EntityHeaderController mHeaderController;
private boolean mStarted = false;
public HeaderPreferenceController(Context context, DashboardFragment fragment) {
super(context, null);
mFragment = fragment;
}
@Override
public String getPreferenceKey() {
return PREF_KEY_APP_HEADER;
}
@Override
public boolean isAvailable() {
return mAppRow != null;
}
@Override
public void updateState(Preference preference) {
if (mAppRow != null && mFragment != null) {
Activity activity = null;
if (mStarted) {
// don't call done on an activity if it hasn't started yet
activity = mFragment.getActivity();
}
if (activity == null) {
return;
}
LayoutPreference pref = (LayoutPreference) preference;
mHeaderController = EntityHeaderController.newInstance(
activity, mFragment, pref.findViewById(R.id.entity_header));
pref = mHeaderController.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)
.setRecyclerView(mFragment.getListView(), mFragment.getSettingsLifecycle())
.done(activity, mContext);
pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE);
}
}
@Override
public CharSequence getSummary() {
if (mChannel != null && !isDefaultChannel()) {
if (mChannelGroup != null
&& !TextUtils.isEmpty(mChannelGroup.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.getName().toString()));
return summary.toString();
} else {
return mAppRow.label.toString();
}
} else if (mChannelGroup != null) {
return mAppRow.label.toString();
} else {
return "";
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
mStarted = true;
if (mHeaderController != null) {
mHeaderController.styleActionBar(mFragment.getActivity());
}
}
@VisibleForTesting
CharSequence getLabel() {
return (mChannel != null && !isDefaultChannel()) ? mChannel.getName()
: mChannelGroup != null
? mChannelGroup.getName()
: mAppRow.label;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import android.app.NotificationChannel;
import android.content.Context;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
import androidx.preference.Preference;
public class HighImportancePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_IMPORTANCE = "high_importance";
private NotificationSettings.ImportanceListener mImportanceListener;
public HighImportancePreferenceController(Context context,
NotificationSettings.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;
}
if (mChannel == null) {
return false;
}
if (isDefaultChannel()) {
return false;
}
return mChannel.getImportance() >= IMPORTANCE_DEFAULT;
}
@Override
public void updateState(Preference preference) {
if (mAppRow != null && mChannel != null) {
preference.setEnabled(mAdmin == null && !mChannel.isImportanceLockedByOEM());
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setChecked(mChannel.getImportance() >= IMPORTANCE_HIGH);
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final boolean checked = (boolean) newValue;
mChannel.setImportance(checked ? IMPORTANCE_HIGH : IMPORTANCE_DEFAULT);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
saveChannel();
mImportanceListener.onImportanceChanged();
}
return true;
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.transition.AutoTransition;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settings.Utils;
import com.android.settingslib.R;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
public class ImportancePreference extends Preference {
private boolean mIsConfigurable = true;
private int mImportance;
private boolean mDisplayInStatusBar;
private boolean mDisplayOnLockscreen;
private View mSilenceButton;
private View mAlertButton;
private Context mContext;
Drawable selectedBackground;
Drawable unselectedBackground;
private static final int BUTTON_ANIM_TIME_MS = 100;
private static final boolean SHOW_BUTTON_SUMMARY = false;
public ImportancePreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
public ImportancePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public ImportancePreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ImportancePreference(Context context) {
super(context);
init(context);
}
private void init(Context context) {
mContext = context;
selectedBackground = mContext.getDrawable(R.drawable.button_border_selected);
unselectedBackground = mContext.getDrawable(R.drawable.button_border_unselected);
setLayoutResource(R.layout.notif_importance_preference);
}
public void setImportance(int importance) {
mImportance = importance;
}
public void setConfigurable(boolean configurable) {
mIsConfigurable = configurable;
}
public void setDisplayInStatusBar(boolean display) {
mDisplayInStatusBar = display;
}
public void setDisplayOnLockscreen(boolean display) {
mDisplayOnLockscreen = display;
}
@Override
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.itemView.setClickable(false);
mSilenceButton = holder.findViewById(R.id.silence);
mAlertButton = holder.findViewById(R.id.alert);
if (!mIsConfigurable) {
mSilenceButton.setEnabled(false);
mAlertButton.setEnabled(false);
}
setImportanceSummary((ViewGroup) holder.itemView, mImportance, false);
switch (mImportance) {
case IMPORTANCE_MIN:
case IMPORTANCE_LOW:
mAlertButton.setBackground(unselectedBackground);
mSilenceButton.setBackground(selectedBackground);
mSilenceButton.setSelected(true);
break;
case IMPORTANCE_HIGH:
default:
mSilenceButton.setBackground(unselectedBackground);
mAlertButton.setBackground(selectedBackground);
mAlertButton.setSelected(true);
break;
}
mSilenceButton.setOnClickListener(v -> {
callChangeListener(IMPORTANCE_LOW);
mAlertButton.setBackground(unselectedBackground);
mSilenceButton.setBackground(selectedBackground);
setImportanceSummary((ViewGroup) holder.itemView, IMPORTANCE_LOW, true);
// a11y service won't always read the newly appearing text in the right order if the
// selection happens too soon (readback happens on a different thread as layout). post
// the selection to make that conflict less likely
holder.itemView.post(() -> {
mAlertButton.setSelected(false);
mSilenceButton.setSelected(true);
});
});
mAlertButton.setOnClickListener(v -> {
callChangeListener(IMPORTANCE_DEFAULT);
mSilenceButton.setBackground(unselectedBackground);
mAlertButton.setBackground(selectedBackground);
setImportanceSummary((ViewGroup) holder.itemView, IMPORTANCE_DEFAULT, true);
holder.itemView.post(() -> {
mSilenceButton.setSelected(false);
mAlertButton.setSelected(true);
});
});
}
private ColorStateList getAccentTint() {
return Utils.getColorAccent(getContext());
}
private ColorStateList getRegularTint() {
return Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary);
}
void setImportanceSummary(ViewGroup parent, int importance, boolean fromUser) {
if (fromUser) {
AutoTransition transition = new AutoTransition();
transition.setDuration(BUTTON_ANIM_TIME_MS);
TransitionManager.beginDelayedTransition(parent, transition);
}
ColorStateList colorAccent = getAccentTint();
ColorStateList colorNormal = getRegularTint();
if (importance >= IMPORTANCE_DEFAULT) {
parent.findViewById(R.id.silence_summary).setVisibility(GONE);
((ImageView) parent.findViewById(R.id.silence_icon)).setImageTintList(colorNormal);
((TextView) parent.findViewById(R.id.silence_label)).setTextColor(colorNormal);
((ImageView) parent.findViewById(R.id.alert_icon)).setImageTintList(colorAccent);
((TextView) parent.findViewById(R.id.alert_label)).setTextColor(colorAccent);
parent.findViewById(R.id.alert_summary).setVisibility(VISIBLE);
} else {
parent.findViewById(R.id.alert_summary).setVisibility(GONE);
((ImageView) parent.findViewById(R.id.alert_icon)).setImageTintList(colorNormal);
((TextView) parent.findViewById(R.id.alert_label)).setTextColor(colorNormal);
((ImageView) parent.findViewById(R.id.silence_icon)).setImageTintList(colorAccent);
((TextView) parent.findViewById(R.id.silence_label)).setTextColor(colorAccent);
parent.findViewById(R.id.silence_summary).setVisibility(VISIBLE);
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.app;
import static android.app.NotificationChannel.USER_LOCKED_SOUND;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import android.app.NotificationChannel;
import android.content.Context;
import android.media.RingtoneManager;
import android.provider.Settings;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import androidx.preference.Preference;
public class ImportancePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_IMPORTANCE = "importance";
private NotificationSettings.ImportanceListener mImportanceListener;
public ImportancePreferenceController(Context context,
NotificationSettings.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;
}
if (mChannel == null) {
return false;
}
return !isDefaultChannel();
}
@Override
public void updateState(Preference preference) {
if (mAppRow!= null && mChannel != null) {
preference.setEnabled(mAdmin == null && !mChannel.isImportanceLockedByOEM());
ImportancePreference pref = (ImportancePreference) preference;
pref.setConfigurable(!mChannel.isImportanceLockedByOEM());
pref.setImportance(mChannel.getImportance());
pref.setDisplayInStatusBar(mBackend.showSilentInStatusBar(mContext.getPackageName()));
pref.setDisplayOnLockscreen(Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1) == 1);
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final int importance = (Integer) newValue;
// If you are moving from an importance level without sound to one with sound,
// 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 (mChannel.getImportance() < IMPORTANCE_DEFAULT
&& !SoundPreferenceController.hasValidSound(mChannel)
&& importance >= IMPORTANCE_DEFAULT) {
mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
mChannel.getAudioAttributes());
mChannel.lockFields(USER_LOCKED_SOUND);
}
mChannel.setImportance(importance);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
saveChannel();
mImportanceListener.onImportanceChanged();
}
return true;
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.app;
import android.app.NotificationManager;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
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()
&& !isDefaultChannel();
}
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(!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,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.app;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import android.app.NotificationChannel;
import android.content.Context;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
import androidx.preference.Preference;
public class MinImportancePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_IMPORTANCE = "min_importance";
private NotificationSettings.ImportanceListener mImportanceListener;
public MinImportancePreferenceController(Context context,
NotificationSettings.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;
}
if (mChannel == null) {
return false;
}
if (isDefaultChannel()) {
return false;
}
return mChannel.getImportance() <= IMPORTANCE_LOW;
}
@Override
public void updateState(Preference preference) {
if (mAppRow != null && mChannel != null) {
preference.setEnabled(mAdmin == null && !mChannel.isImportanceLockedByOEM());
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setChecked(mChannel.getImportance() == IMPORTANCE_MIN);
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final boolean checked = (boolean) newValue;
mChannel.setImportance(checked ? IMPORTANCE_MIN : IMPORTANCE_LOW);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
saveChannel();
mImportanceListener.onImportanceChanged();
}
return true;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Switch;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.widget.MasterSwitchPreference;
import com.android.settingslib.RestrictedLockUtils;
/**
* Shows an app icon, title and summary. Has a second switch touch target.
*/
public class NotificationAppPreference extends MasterSwitchPreference {
private Switch mSwitch;
private boolean mChecked;
private boolean mEnableSwitch = true;
public NotificationAppPreference(Context context) {
super(context);
}
public NotificationAppPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NotificationAppPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public NotificationAppPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected int getSecondTargetResId() {
return R.layout.preference_widget_master_switch;
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
final View widgetView = view.findViewById(android.R.id.widget_frame);
if (widgetView != null) {
widgetView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSwitch != null && !mSwitch.isEnabled()) {
return;
}
setChecked(!mChecked);
if (!callChangeListener(mChecked)) {
setChecked(!mChecked);
} else {
persistBoolean(mChecked);
}
}
});
}
mSwitch = (Switch) view.findViewById(R.id.switchWidget);
if (mSwitch != null) {
mSwitch.setContentDescription(getTitle());
mSwitch.setChecked(mChecked);
mSwitch.setEnabled(mEnableSwitch);
}
}
public boolean isChecked() {
return mSwitch != null && mChecked;
}
public void setChecked(boolean checked) {
mChecked = checked;
if (mSwitch != null) {
mSwitch.setChecked(checked);
}
}
public void setSwitchEnabled(boolean enabled) {
mEnableSwitch = enabled;
if (mSwitch != null) {
mSwitch.setEnabled(enabled);
}
}
/**
* If admin is not null, disables the switch.
* Otherwise, keep it enabled.
*/
public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
setSwitchEnabled(admin == null);
}
public Switch getSwitch() {
return mSwitch;
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.app;
import android.content.Context;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
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,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.app;
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.util.Log;
import androidx.preference.Preference;
import com.android.settings.notification.NotificationBackend;
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 NotificationChannelGroup 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 (mChannelGroup != null) {
if (mChannelGroup.isBlocked()) {
return false;
}
}
if (mChannel != null) {
return mChannel.getImportance() != IMPORTANCE_NONE;
}
return true;
}
protected void onResume(NotificationBackend.AppRow appRow,
@Nullable NotificationChannel channel, @Nullable NotificationChannelGroup 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 isChannelBlockable() {
return isChannelBlockable(mChannel);
}
protected boolean isChannelBlockable(NotificationChannel channel) {
if (channel != null && mAppRow != null) {
if (channel.isImportanceLockedByCriticalDeviceFunction()
|| channel.isImportanceLockedByOEM()) {
return channel.getImportance() == IMPORTANCE_NONE;
}
return channel.isBlockableSystem() || !mAppRow.systemApp
|| channel.getImportance() == IMPORTANCE_NONE;
}
return false;
}
protected boolean isChannelConfigurable(NotificationChannel channel) {
if (channel != null && mAppRow != null) {
return !channel.isImportanceLockedByOEM();
}
return false;
}
protected boolean isChannelGroupBlockable() {
return isChannelGroupBlockable(mChannelGroup);
}
protected boolean isChannelGroupBlockable(NotificationChannelGroup group) {
if (group != null && mAppRow != null) {
if (!mAppRow.systemApp) {
return true;
}
return group.isBlocked();
}
return false;
}
protected boolean hasValidGroup() {
return mChannelGroup != null;
}
protected final boolean isDefaultChannel() {
if (mChannel == null) {
return false;
}
return Objects.equals(NotificationChannel.DEFAULT_CHANNEL_ID, mChannel.getId());
}
}

View File

@@ -0,0 +1,314 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.HeaderPreferenceController;
import com.android.settings.notification.app.NotificationPreferenceController;
import com.android.settingslib.RestrictedLockUtilsInternal;
import java.util.ArrayList;
import java.util.List;
abstract public class NotificationSettings extends DashboardFragment {
private static final String TAG = "NotifiSettingsBase";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
protected PackageManager mPm;
protected NotificationBackend mBackend = new NotificationBackend();
protected NotificationManager mNm;
protected RoleManager mRm;
protected Context mContext;
protected int mUid;
protected int mUserId;
protected String mPkg;
protected PackageInfo mPkgInfo;
protected EnforcedAdmin mSuspendedAppsAdmin;
protected NotificationChannelGroup mChannelGroup;
protected NotificationChannel mChannel;
protected NotificationBackend.AppRow mAppRow;
protected boolean mShowLegacyChannelConfig = false;
protected boolean mListeningToPackageRemove;
protected List<NotificationPreferenceController> mControllers = new ArrayList<>();
protected ImportanceListener mImportanceListener = new ImportanceListener();
protected Intent mIntent;
protected Bundle mArgs;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = getActivity();
mIntent = getActivity().getIntent();
mArgs = getArguments();
mPm = getPackageManager();
mNm = NotificationManager.from(mContext);
mRm = mContext.getSystemService(RoleManager.class);
mPkg = mArgs != null && mArgs.containsKey(AppInfoBase.ARG_PACKAGE_NAME)
? mArgs.getString(AppInfoBase.ARG_PACKAGE_NAME)
: mIntent.getStringExtra(Settings.EXTRA_APP_PACKAGE);
mUid = mArgs != null && mArgs.containsKey(AppInfoBase.ARG_PACKAGE_UID)
? mArgs.getInt(AppInfoBase.ARG_PACKAGE_UID)
: mIntent.getIntExtra(Settings.EXTRA_APP_UID, -1);
if (mUid < 0) {
try {
mUid = mPm.getPackageUid(mPkg, 0);
} catch (NameNotFoundException e) {
}
}
mPkgInfo = findPackageInfo(mPkg, mUid);
if (mPkgInfo != null) {
mUserId = UserHandle.getUserId(mUid);
mSuspendedAppsAdmin = RestrictedLockUtilsInternal.checkIfApplicationIsSuspended(
mContext, mPkg, mUserId);
loadChannel();
loadAppRow();
loadChannelGroup();
collectConfigActivities();
getSettingsLifecycle().addObserver(use(HeaderPreferenceController.class));
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
}
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mIntent == null && mArgs == null) {
Log.w(TAG, "No intent");
toastAndFinish();
return;
}
if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
Log.w(TAG, "Missing package or uid or packageinfo");
toastAndFinish();
return;
}
startListeningToPackageRemove();
}
@Override
public void onDestroy() {
stopListeningToPackageRemove();
super.onDestroy();
}
@Override
public void onResume() {
super.onResume();
if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mAppRow == null) {
Log.w(TAG, "Missing package or uid or packageinfo");
finish();
return;
}
// Reload app, channel, etc onResume in case they've changed. A little wasteful if we've
// just done onAttach but better than making every preference controller reload all
// the data
loadAppRow();
if (mAppRow == null) {
Log.w(TAG, "Can't load package");
finish();
return;
}
loadChannel();
loadChannelGroup();
collectConfigActivities();
}
private void loadChannel() {
Intent intent = getActivity().getIntent();
String channelId = intent != null ? intent.getStringExtra(Settings.EXTRA_CHANNEL_ID) : null;
if (channelId == null && intent != null) {
Bundle args = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
channelId = args != null ? args.getString(Settings.EXTRA_CHANNEL_ID) : null;
}
mChannel = mBackend.getChannel(mPkg, mUid, channelId);
}
private void loadAppRow() {
mAppRow = mBackend.loadAppRow(mContext, mPm, mRm, mPkgInfo);
}
private void loadChannelGroup() {
mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid)
|| (mChannel != null
&& NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()));
if (mShowLegacyChannelConfig) {
mChannel = mBackend.getChannel(
mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID);
}
if (mChannel != null && !TextUtils.isEmpty(mChannel.getGroup())) {
NotificationChannelGroup group = mBackend.getGroup(mPkg, mUid, mChannel.getGroup());
if (group != null) {
mChannelGroup = group;
}
}
}
protected void toastAndFinish() {
Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show();
getActivity().finish();
}
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(
intent,
0 //PackageManager.MATCH_DEFAULT_ONLY
);
if (DEBUG) {
Log.d(TAG, "Found " + resolveInfos.size() + " preference activities"
+ (resolveInfos.size() == 0 ? " ;_;" : ""));
}
for (ResolveInfo ri : resolveInfos) {
final ActivityInfo activityInfo = ri.activityInfo;
if (mAppRow.settingsIntent != null) {
if (DEBUG) {
Log.d(TAG, "Ignoring duplicate notification preference activity ("
+ activityInfo.name + ") for package "
+ activityInfo.packageName);
}
continue;
}
// TODO(78660939): This should actually start a new task
mAppRow.settingsIntent = intent
.setPackage(null)
.setClassName(activityInfo.packageName, activityInfo.name);
if (mChannel != null) {
mAppRow.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId());
}
if (mChannelGroup != null) {
mAppRow.settingsIntent.putExtra(
Notification.EXTRA_CHANNEL_GROUP_ID, mChannelGroup.getId());
}
}
}
private PackageInfo findPackageInfo(String pkg, int uid) {
if (pkg == null || uid < 0) {
return null;
}
final String[] packages = mPm.getPackagesForUid(uid);
if (packages != null && pkg != null) {
final int N = packages.length;
for (int i = 0; i < N; i++) {
final String p = packages[i];
if (pkg.equals(p)) {
try {
return mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException e) {
Log.w(TAG, "Failed to load package " + pkg, e);
}
}
}
}
return null;
}
protected void startListeningToPackageRemove() {
if (mListeningToPackageRemove) {
return;
}
mListeningToPackageRemove = true;
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mPackageRemovedReceiver, filter);
}
protected void stopListeningToPackageRemove() {
if (!mListeningToPackageRemove) {
return;
}
mListeningToPackageRemove = false;
getContext().unregisterReceiver(mPackageRemovedReceiver);
}
protected void onPackageRemoved() {
getActivity().finishAndRemoveTask();
}
protected final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String packageName = intent.getData().getSchemeSpecificPart();
if (mPkgInfo == null || TextUtils.equals(mPkgInfo.packageName, packageName)) {
if (DEBUG) {
Log.d(TAG, "Package (" + packageName + ") removed. Removing"
+ "NotificationSettingsBase.");
}
onPackageRemoved();
}
}
};
protected class ImportanceListener {
protected void onImportanceChanged() {
final PreferenceScreen screen = getPreferenceScreen();
for (NotificationPreferenceController controller : mControllers) {
controller.displayPreference(screen);
}
updatePreferenceStates();
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.app;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.AttributeSet;
import com.android.settings.R;
import com.android.settings.RingtonePreference;
public class NotificationSoundPreference extends RingtonePreference {
private Uri mRingtone;
public NotificationSoundPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected Uri onRestoreRingtone() {
return mRingtone;
}
public void setRingtone(Uri ringtone) {
mRingtone = ringtone;
setSummary("\u00A0");
updateRingtoneName(mRingtone);
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
setRingtone(uri);
callChangeListener(uri);
}
return true;
}
private void updateRingtoneName(final Uri uri) {
AsyncTask ringtoneNameTask = new AsyncTask<Object, Void, CharSequence>() {
@Override
protected CharSequence doInBackground(Object... params) {
if (uri == null) {
return getContext().getString(com.android.internal.R.string.ringtone_silent);
} else if (RingtoneManager.isDefault(uri)) {
return getContext().getString(R.string.notification_sound_default);
} else if(ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
return getContext().getString(R.string.notification_unknown_sound_title);
} else {
return Ringtone.getTitle(getContext(), uri, false /* followSettingsUri */,
true /* allowRemote */);
}
}
@Override
protected void onPostExecute(CharSequence name) {
setSummary(name);
}
};
ringtoneNameTask.execute();
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.app;
import android.content.Context;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
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) {
preference.setTitle(R.string.channel_group_notifications_off_desc);
} else {
preference.setTitle(R.string.app_notifications_off_desc);
}
}
preference.setSelectable(false);
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.app;
import static android.media.AudioAttributes.USAGE_ALARM;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.preference.PreferenceManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
public class SoundPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener,
PreferenceManager.OnActivityResultListener {
private static final String KEY_SOUND = "ringtone";
private final SettingsPreferenceFragment mFragment;
private final NotificationSettings.ImportanceListener mListener;
private NotificationSoundPreference mPreference;
protected static final int CODE = 200;
public SoundPreferenceController(Context context, SettingsPreferenceFragment hostFragment,
NotificationSettings.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) && !isDefaultChannel();
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
public void updateState(Preference preference) {
if (mAppRow!= null && mChannel != null) {
NotificationSoundPreference pref = (NotificationSoundPreference) preference;
pref.setEnabled(mAdmin == null);
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;
if (mChannel != null && mChannel.getAudioAttributes() != null) {
if (USAGE_ALARM == mChannel.getAudioAttributes().getUsage()) {
pref.setRingtoneType(RingtoneManager.TYPE_ALARM);
} else if (USAGE_NOTIFICATION_RINGTONE
== mChannel.getAudioAttributes().getUsage()) {
pref.setRingtoneType(RingtoneManager.TYPE_RINGTONE);
} else {
pref.setRingtoneType(RingtoneManager.TYPE_NOTIFICATION);
}
}
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,75 @@
/*
* 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.app;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Vibrator;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.NotificationPreferenceController;
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)
&& !isDefaultChannel()
&& mVibrator != null
&& mVibrator.hasVibrator();
}
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setEnabled(!pref.isDisabledByAdmin());
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,175 @@
/*
* 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.app;
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 androidx.preference.Preference;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.RestrictedListPreference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.NotificationPreferenceController;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
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) {
RestrictedListPreference pref = (RestrictedListPreference) 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);
}
if (getLockscreenNotificationsEnabled()) {
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(RestrictedListPreference pref,
CharSequence entry, CharSequence entryValue, int keyguardNotificationFeatures) {
RestrictedLockUtils.EnforcedAdmin admin =
RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
mContext, keyguardNotificationFeatures, mAppRow.userId);
if (admin != null) {
RestrictedListPreference.RestrictedItem item =
new RestrictedListPreference.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() {
final UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId());
final int primaryUserId = parentUser != null ? parentUser.id : UserHandle.myUserId();
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, primaryUserId) != 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;
}
}