Merge "Add new settings page for apps overriding dnd"

This commit is contained in:
Beverly Tai
2018-11-15 18:06:06 +00:00
committed by Android (Google) Code Review
11 changed files with 630 additions and 46 deletions

View File

@@ -21,6 +21,7 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.content.Context;
@@ -38,8 +39,6 @@ import android.text.format.DateUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.Utils;
import com.android.settingslib.utils.StringUtil;
@@ -49,6 +48,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import androidx.annotation.VisibleForTesting;
public class NotificationBackend {
private static final String TAG = "NotificationBackend";
@@ -208,6 +209,19 @@ public class NotificationBackend {
}
}
/**
* Returns all notification channels associated with the package and uid that will bypass DND
*/
public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
int uid) {
try {
return sINM.getNotificationChannelsBypassingDnd(pkg, uid);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return ParceledListSlice.emptyList();
}
}
public void updateChannel(String pkg, int uid, NotificationChannel channel) {
try {
sINM.updateNotificationChannelForPackage(pkg, uid, channel);
@@ -260,6 +274,15 @@ public class NotificationBackend {
}
}
public int getNumAppsBypassingDnd(int uid) {
try {
return sINM.getAppsBypassingDndCount(uid);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return 0;
}
}
public List<NotifyingApp> getRecentApps() {
try {
return sINM.getRecentNotifyingAppsForUser(UserHandle.myUserId()).getList();

View File

@@ -0,0 +1,200 @@
/*
* 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;
import android.app.Application;
import android.app.NotificationChannel;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.apppreference.AppPreference;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
/**
* Adds a preference to the PreferenceScreen for each notification channel that can bypass DND.
*/
public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
private final String KEY = "zen_mode_bypassing_apps_category";
@VisibleForTesting ApplicationsState mApplicationsState;
@VisibleForTesting PreferenceScreen mPreferenceScreen;
@VisibleForTesting Context mPrefContext;
private ApplicationsState.Session mAppSession;
private NotificationBackend mNotificationBackend = new NotificationBackend();
private Fragment mHostFragment;
public ZenModeAllBypassingAppsPreferenceController(Context context, Application app,
Fragment host) {
this(context, app == null ? null : ApplicationsState.getInstance(app), host);
}
private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState,
Fragment host) {
super(context);
mApplicationsState = appState;
mHostFragment = host;
if (mApplicationsState != null && host != null) {
mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceScreen = screen;
mPrefContext = mPreferenceScreen.getContext();
updateNotificationChannelList();
super.displayPreference(screen);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY;
}
/**
* Call this method to trigger the notification channels list to refresh.
*/
public void updateNotificationChannelList() {
if (mAppSession == null) {
return;
}
ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
ApplicationsState.ALPHA_COMPARATOR);
if (apps != null) {
updateNotificationChannelList(apps);
}
}
@VisibleForTesting
void updateNotificationChannelList(List<ApplicationsState.AppEntry> apps) {
if (mPreferenceScreen == null || apps == null) {
return;
}
List<Preference> channelsBypassingDnd = new ArrayList<>();
for (ApplicationsState.AppEntry entry : apps) {
String pkg = entry.info.packageName;
mApplicationsState.ensureIcon(entry);
for (NotificationChannel channel : mNotificationBackend
.getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList()) {
Preference pref = new AppPreference(mPrefContext);
pref.setKey(pkg + "|" + channel.getId());
pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label));
pref.setIcon(entry.icon);
pref.setSummary(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, entry.info.packageName);
args.putInt(AppInfoBase.ARG_PACKAGE_UID, entry.info.uid);
args.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
new SubSettingLauncher(mContext)
.setDestination(ChannelNotificationSettings.class.getName())
.setArguments(args)
.setTitleRes(R.string.notification_channel_title)
.setResultListener(mHostFragment, 0)
.setSourceMetricsCategory(
MetricsEvent.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
.launch();
return true;
}
});
channelsBypassingDnd.add(pref);
}
mPreferenceScreen.removeAll();
if (channelsBypassingDnd.size() > 0) {
for (Preference prefToAdd : channelsBypassingDnd) {
mPreferenceScreen.addPreference(prefToAdd);
}
}
}
}
private final ApplicationsState.Callbacks mAppSessionCallbacks =
new ApplicationsState.Callbacks() {
@Override
public void onRunningStateChanged(boolean running) {
updateNotificationChannelList();
}
@Override
public void onPackageListChanged() {
updateNotificationChannelList();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
updateNotificationChannelList(apps);
}
@Override
public void onPackageIconChanged() {
updateNotificationChannelList();
}
@Override
public void onPackageSizeChanged(String packageName) {
updateNotificationChannelList();
}
@Override
public void onAllSizesComputed() { }
@Override
public void onLauncherInfoChanged() {
updateNotificationChannelList();
}
@Override
public void onLoadEntriesCompleted() {
updateNotificationChannelList();
}
};
}

View File

@@ -0,0 +1,30 @@
package com.android.settings.notification;
import android.content.Context;
import android.os.UserHandle;
import com.android.settings.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePreferenceController {
protected static final String KEY = "zen_mode_bypassing_apps";
private NotificationBackend mNotificationBackend = new NotificationBackend();
public ZenModeBypassingAppsPreferenceController(Context context, Lifecycle lifecycle) {
super(context, KEY, lifecycle);
}
@Override
public boolean isAvailable() {
return mNotificationBackend.getNumAppsBypassingDnd(UserHandle.getCallingUserId()) != 0;
}
@Override
public String getSummary() {
final int channelsBypassing =
mNotificationBackend.getNumAppsBypassingDnd(UserHandle.getCallingUserId());
return mContext.getResources().getQuantityString(R.plurals.zen_mode_bypassing_apps_subtext,
channelsBypassing, channelsBypassing);
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.provider.SearchIndexableResource;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
import androidx.fragment.app.Fragment;
@SearchIndexable
public class ZenModeBypassingAppsSettings extends ZenModeSettingsBase implements
Indexable {
private final String TAG = "ZenBypassingApps";
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final Activity activity = getActivity();
final Application app;
if (activity != null) {
app = activity.getApplication();
} else {
app = null;
}
return buildPreferenceControllers(context, app, this);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Application app, Fragment host) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host));
return controllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.zen_mode_bypassing_apps;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getMetricsCategory() {
return MetricsEvent.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS;
}
/**
* For Search.
*/
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
final ArrayList<SearchIndexableResource> result = new ArrayList<>();
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.zen_mode_bypassing_apps;
result.add(sir);
return result;
}
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null, null);
}
};
}

View File

@@ -16,8 +16,6 @@
package com.android.settings.notification;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
import android.content.Context;
import android.provider.SearchIndexableResource;
@@ -55,6 +53,7 @@ public class ZenModeSoundVibrationSettings extends ZenModeSettingsBase implement
controllers.add(new ZenModeEventsPreferenceController(context, lifecycle));
controllers.add(new ZenModeBehaviorFooterPreferenceController(context, lifecycle,
R.string.zen_sound_footer));
controllers.add(new ZenModeBypassingAppsPreferenceController(context, lifecycle));
return controllers;
}

View File

@@ -28,16 +28,16 @@ import android.icu.text.ListFormatter;
import android.provider.Contacts;
import android.provider.ContactsContract;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
public class ZenModeStarredContactsPreferenceController extends
AbstractZenModePreferenceController implements Preference.OnPreferenceClickListener {
@@ -134,7 +134,6 @@ public class ZenModeStarredContactsPreferenceController extends
@VisibleForTesting
List<String> getStarredContacts(Cursor cursor) {
List<String> starredContacts = new ArrayList<>();
if (cursor.moveToFirst()) {
do {
String contact = cursor.getString(0);
@@ -147,7 +146,15 @@ public class ZenModeStarredContactsPreferenceController extends
}
private List<String> getStarredContacts() {
return getStarredContacts(queryData());
Cursor cursor = null;
try {
cursor = queryData();
return getStarredContacts(cursor);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
private Cursor queryData() {