Merge "Separate conversations from channels"

This commit is contained in:
Julia Reynolds
2020-02-08 11:48:18 +00:00
committed by Android (Google) Code Review
52 changed files with 2338 additions and 318 deletions

View File

@@ -17,6 +17,8 @@ package com.android.settings.notification;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
import android.app.INotificationManager;
import android.app.NotificationChannel;
@@ -29,13 +31,17 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotifyingApp;
import android.text.format.DateUtils;
import android.util.IconDrawableFactory;
@@ -48,6 +54,7 @@ import com.android.settingslib.Utils;
import com.android.settingslib.utils.StringUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -154,7 +161,7 @@ public class NotificationBackend {
try {
if (onlyHasDefaultChannel(pkg, uid)) {
NotificationChannel defaultChannel =
getChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
getChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, null);
defaultChannel.setImportance(enabled ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE);
updateChannel(pkg, uid, defaultChannel);
}
@@ -204,13 +211,17 @@ public class NotificationBackend {
}
}
public NotificationChannel getChannel(String pkg, int uid, String channelId) {
return getChannel(pkg, uid, channelId, null);
}
public NotificationChannel getChannel(String pkg, int uid, String channelId,
String conversationId) {
if (channelId == null) {
return null;
}
try {
return sINM.getNotificationChannelForPackage(pkg, uid, channelId, true);
return sINM.getNotificationChannelForPackage(pkg, uid, channelId, conversationId, true);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return null;
@@ -238,6 +249,15 @@ public class NotificationBackend {
}
}
public ParceledListSlice<ConversationChannelWrapper> getConversations(String pkg, int uid) {
try {
return sINM.getConversationsForPackage(pkg, uid);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return ParceledListSlice.emptyList();
}
}
/**
* Returns all notification channels associated with the package and uid that will bypass DND
*/
@@ -474,6 +494,32 @@ public class NotificationBackend {
}
}
public ShortcutInfo getConversationInfo(Context context, String pkg, int uid, String id) {
LauncherApps la = context.getSystemService(LauncherApps.class);
LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
.setPackage(pkg)
.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
.setShortcutIds(Arrays.asList(id));
List<ShortcutInfo> shortcuts = la.getShortcuts(
query, UserHandle.of(UserHandle.getUserId(uid)));
if (shortcuts != null && !shortcuts.isEmpty()) {
return shortcuts.get(0);
}
return null;
}
public Drawable getConversationDrawable(Context context, ShortcutInfo info) {
LauncherApps la = context.getSystemService(LauncherApps.class);
return la.getShortcutBadgedIconDrawable(info,
context.getResources().getDisplayMetrics().densityDpi);
}
public void requestPinShortcut(Context context, ShortcutInfo shortcutInfo) {
ShortcutManager sm = context.getSystemService(ShortcutManager.class);
sm.requestPinShortcut(shortcutInfo, null);
}
/**
* NotificationsSentState contains how often an app sends notifications and how recently it sent
* one.

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.content.Context;
import android.util.Slog;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
public class AddToHomeScreenPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String TAG = "HomeScreenPref";
private static final String KEY = "add_to_home";
public AddToHomeScreenPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
return mConversationInfo != null;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY.equals(preference.getKey())) {
try {
mBackend.requestPinShortcut(mContext, mConversationInfo);
return true;
} catch (SecurityException e) {
Slog.e(TAG, "Cannot add to home screen", e);
}
}
return false;
}
}

View File

@@ -84,7 +84,7 @@ public class AppBubbleNotificationSettings extends NotificationSettings implemen
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.onResume(mAppRow, mChannel, mChannelGroup, null, null, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();

View File

@@ -80,7 +80,7 @@ public class AppNotificationSettings extends NotificationSettings {
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.onResume(mAppRow, mChannel, mChannelGroup, null, null, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
@@ -123,6 +123,7 @@ public class AppNotificationSettings extends NotificationSettings {
mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
mControllers.add(new ChannelListPreferenceController(context, mBackend));
mControllers.add(new ConversationListPreferenceController(context, mBackend));
return new ArrayList<>(mControllers);
}
}

View File

@@ -22,15 +22,15 @@ import android.annotation.Nullable;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
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 {

View File

@@ -31,6 +31,7 @@ import android.graphics.drawable.LayerDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.Utils;
@@ -132,9 +133,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
groupCategory.setOrderingAsAdded(true);
mPreference.addPreference(groupCategory);
if (group.getId() == null) {
if (mChannelGroupList.size() > 1) {
groupCategory.setTitle(R.string.notification_channels_other);
}
groupCategory.setTitle(R.string.notification_channels_other);
groupCategory.setKey(KEY_GENERAL_CATEGORY);
} else {
groupCategory.setTitle(group.getName());
@@ -147,7 +146,10 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
// conversations get their own section
if (TextUtils.isEmpty(channel.getConversationId()) || channel.isDemoted()) {
populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
}
}
}
}

View File

@@ -30,6 +30,7 @@ import androidx.preference.PreferenceScreen;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
@@ -65,8 +66,20 @@ public class ChannelNotificationSettings extends NotificationSettings {
return;
}
if (mChannel != null && !TextUtils.isEmpty(mChannel.getConversationId())
&& !mChannel.isDemoted()) {
startActivity(new SubSettingLauncher(mContext)
.setDestination(ConversationNotificationSettings.class.getName())
.setArguments(getArguments())
.setExtras(getIntent() != null ? getIntent().getExtras(): null)
.setSourceMetricsCategory(SettingsEnums.NOTIFICATION_TOPIC_NOTIFICATION)
.toIntent());
finish();
return;
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.onResume(mAppRow, mChannel, mChannelGroup, null, null, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
@@ -118,6 +131,7 @@ public class ChannelNotificationSettings extends NotificationSettings {
mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new BubblePreferenceController(context, getChildFragmentManager(),
mBackend, false /* isAppPage */));
mControllers.add(new ConversationPromotePreferenceController(context, this, mBackend));
return new ArrayList<>(mControllers);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.content.Context;
import android.text.TextUtils;
import androidx.preference.Preference;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
public class ConversationDemotePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String KEY = "demote";
SettingsPreferenceFragment mHostFragment;
public ConversationDemotePreferenceController(Context context,
SettingsPreferenceFragment hostFragment,
NotificationBackend backend) {
super(context, backend);
mHostFragment = hostFragment;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mAppRow == null || mChannel == null) {
return false;
}
return !TextUtils.isEmpty(mChannel.getConversationId()) && !mChannel.isDemoted();
}
public void updateState(Preference preference) {
preference.setEnabled(mAdmin == null);
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY.equals(preference.getKey())) {
mChannel.setDemoted(true);
saveChannel();
mHostFragment.getActivity().finish();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import static 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 ConversationHeaderPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, LifecycleObserver {
private final DashboardFragment mFragment;
private EntityHeaderController mHeaderController;
private boolean mStarted = false;
public ConversationHeaderPreferenceController(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(mConversationDrawable)
.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 mConversationInfo != null
? mConversationInfo.getShortLabel()
: mChannel.getName();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import 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 ConversationImportantPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String TAG = "ConvoImpPC";
private static final String KEY = "important";
public ConversationImportantPreferenceController(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;
}
return true;
}
public void updateState(Preference preference) {
if (mAppRow != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setDisabledByAdmin(mAdmin);
pref.setChecked(mChannel.isImportantConversation());
pref.setEnabled(!pref.isDisabledByAdmin());
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel == null) {
return false;
}
final boolean value = (Boolean) newValue;
mChannel.setImportantConversation(value);
saveChannel();
return true;
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.app.NotificationChannel;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ConversationListPreferenceController extends NotificationPreferenceController {
private static final String KEY = "conversations";
public static final String ARG_FROM_SETTINGS = "fromSettings";
private List<ConversationChannelWrapper> mConversations;
private PreferenceCategory mPreference;
public ConversationListPreferenceController(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) {
mConversations = mBackend.getConversations(mAppRow.pkg, mAppRow.uid).getList();
Collections.sort(mConversations, mConversationComparator);
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();
mPreference.setTitle(R.string.conversations_category_title);
if (mConversations.isEmpty()) {
mPreference.setVisible(false);
} else {
mPreference.setVisible(true);
populateConversations();
}
}
private void populateConversations() {
for (ConversationChannelWrapper conversation : mConversations) {
if (conversation.getNotificationChannel().isDemoted()) {
continue;
}
mPreference.addPreference(createConversationPref(conversation));
}
}
protected Preference createConversationPref(final ConversationChannelWrapper conversation) {
Preference pref = new Preference(mContext);
ShortcutInfo si = conversation.getShortcutInfo();
pref.setTitle(si != null
? si.getShortLabel()
: conversation.getNotificationChannel().getName());
pref.setSummary(conversation.getNotificationChannel().getGroup() != null
? mContext.getString(R.string.notification_conversation_summary,
conversation.getParentChannelLabel(), conversation.getGroupLabel())
: conversation.getParentChannelLabel());
if (si != null) {
pref.setIcon(mBackend.getConversationDrawable(mContext, si));
}
pref.setKey(conversation.getNotificationChannel().getId());
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,
conversation.getNotificationChannel().getParentChannelId());
channelArgs.putString(Settings.EXTRA_CONVERSATION_ID,
conversation.getNotificationChannel().getConversationId());
channelArgs.putBoolean(ARG_FROM_SETTINGS, true);
pref.setIntent(new SubSettingLauncher(mContext)
.setDestination(ChannelNotificationSettings.class.getName())
.setArguments(channelArgs)
.setExtras(channelArgs)
.setTitleText(pref.getTitle())
.setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
.toIntent());
return pref;
}
protected Comparator<ConversationChannelWrapper> mConversationComparator =
(left, right) -> {
if (left.getNotificationChannel().isImportantConversation()
!= right.getNotificationChannel().isImportantConversation()) {
// important first
return Boolean.compare(right.getNotificationChannel().isImportantConversation(),
left.getNotificationChannel().isImportantConversation());
}
return left.getNotificationChannel().getId().compareTo(
right.getNotificationChannel().getId());
};
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.preference.PreferenceManager;
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;
public class ConversationNotificationSettings extends NotificationSettings {
private static final String TAG = "ConvoSettings";
@Override
public int getMetricsCategory() {
return SettingsEnums.NOTIFICATION_CONVERSATION_SETTINGS;
}
@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, mConversationDrawable,
mConversationInfo, 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.conversation_notification_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
mControllers = new ArrayList<>();
mControllers.add(new ConversationHeaderPreferenceController(context, this));
mControllers.add(new ConversationImportantPreferenceController(context, mBackend));
mControllers.add(new DefaultImportancePreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new AddToHomeScreenPreferenceController(context, mBackend));
mControllers.add(new HighImportancePreferenceController(
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 NotificationsOffPreferenceController(context));
mControllers.add(new BubblePreferenceController(context, getChildFragmentManager(),
mBackend, false /* isAppPage */));
mControllers.add(new ConversationDemotePreferenceController(context, this, mBackend));
return new ArrayList<>(mControllers);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.app;
import android.content.Context;
import android.text.TextUtils;
import androidx.preference.Preference;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
public class ConversationPromotePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
private static final String KEY = "convo_promote";
SettingsPreferenceFragment mHostFragment;
public ConversationPromotePreferenceController(Context context,
SettingsPreferenceFragment hostFragment,
NotificationBackend backend) {
super(context, backend);
mHostFragment = hostFragment;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
return false;
}
if (mAppRow == null || mChannel == null) {
return false;
}
return !TextUtils.isEmpty(mChannel.getConversationId()) && mChannel.isDemoted();
}
public void updateState(Preference preference) {
preference.setEnabled(mAdmin == null);
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (mChannel == null) {
return false;
}
mChannel.setDemoted(false);
saveChannel();
if (mHostFragment != null) {
mHostFragment.getActivity().finish();
}
return true;
}
}

View File

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

View File

@@ -24,6 +24,8 @@ import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
import android.util.Log;
@@ -53,6 +55,10 @@ public abstract class NotificationPreferenceController extends AbstractPreferenc
protected final UserManager mUm;
protected final PackageManager mPm;
protected Preference mPreference;
@Nullable
protected Drawable mConversationDrawable;
@Nullable
protected ShortcutInfo mConversationInfo;
public NotificationPreferenceController(Context context, NotificationBackend backend) {
super(context);
@@ -87,11 +93,15 @@ public abstract class NotificationPreferenceController extends AbstractPreferenc
protected void onResume(NotificationBackend.AppRow appRow,
@Nullable NotificationChannel channel, @Nullable NotificationChannelGroup group,
Drawable conversationDrawable,
ShortcutInfo conversationInfo,
RestrictedLockUtils.EnforcedAdmin admin) {
mAppRow = appRow;
mChannel = channel;
mChannelGroup = group;
mAdmin = admin;
mConversationDrawable = conversationDrawable;
mConversationInfo = conversationInfo;
}
protected boolean checkCanBeVisible(int minImportanceVisible) {

View File

@@ -32,6 +32,8 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
@@ -46,8 +48,6 @@ 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;
@@ -71,6 +71,8 @@ abstract public class NotificationSettings extends DashboardFragment {
protected NotificationChannelGroup mChannelGroup;
protected NotificationChannel mChannel;
protected NotificationBackend.AppRow mAppRow;
protected Drawable mConversationDrawable;
protected ShortcutInfo mConversationInfo;
protected boolean mShowLegacyChannelConfig = false;
protected boolean mListeningToPackageRemove;
@@ -118,10 +120,17 @@ abstract public class NotificationSettings extends DashboardFragment {
loadChannelGroup();
collectConfigActivities();
getSettingsLifecycle().addObserver(use(HeaderPreferenceController.class));
if (use(HeaderPreferenceController.class) != null) {
getSettingsLifecycle().addObserver(use(HeaderPreferenceController.class));
}
if (use(ConversationHeaderPreferenceController.class) != null) {
getSettingsLifecycle().addObserver(
use(ConversationHeaderPreferenceController.class));
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin);
controller.onResume(mAppRow, mChannel, mChannelGroup, null, null,
mSuspendedAppsAdmin);
}
}
}
@@ -169,6 +178,7 @@ abstract public class NotificationSettings extends DashboardFragment {
return;
}
loadChannel();
loadConversation();
loadChannelGroup();
collectConfigActivities();
}
@@ -180,7 +190,21 @@ abstract public class NotificationSettings extends DashboardFragment {
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);
String conversationId = intent != null
? intent.getStringExtra(Settings.EXTRA_CONVERSATION_ID) : null;
mChannel = mBackend.getChannel(mPkg, mUid, channelId, conversationId);
}
private void loadConversation() {
if (mChannel == null || TextUtils.isEmpty(mChannel.getConversationId())
|| mChannel.isDemoted()) {
return;
}
mConversationInfo = mBackend.getConversationInfo(
mContext, mPkg, mUid, mChannel.getConversationId());
if (mConversationInfo != null) {
mConversationDrawable = mBackend.getConversationDrawable(mContext, mConversationInfo);
}
}
private void loadAppRow() {
@@ -194,7 +218,7 @@ abstract public class NotificationSettings extends DashboardFragment {
if (mShowLegacyChannelConfig) {
mChannel = mBackend.getChannel(
mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID);
mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID, null);
}
if (mChannel != null && !TextUtils.isEmpty(mChannel.getGroup())) {
NotificationChannelGroup group = mBackend.getGroup(mPkg, mUid, mChannel.getGroup());