Notification listeners have full DND access.

Bug: 27976092
Change-Id: I9603d900d7cee5666ec3567b4f42fee6d93ae5f8
This commit is contained in:
Julia Reynolds
2016-04-13 10:02:18 -04:00
parent d4f4e2d5b5
commit 83f35ba88d
9 changed files with 325 additions and 54 deletions

View File

@@ -6024,7 +6024,13 @@
<xliff:g id="notification_listener_name">%1$s</xliff:g> will be able to read all notifications,
including personal information such as contact names and the text of messages you receive.
It will also be able to dismiss notifications or trigger action buttons they contain.
\n\nThis will also give the app the ability to turn Do Not Disturb on or off and change related settings.
</string>
<string name="notification_listener_disable_warning_summary">
If you turn off notification access for <xliff:g id="notification_listener_name">%1$s</xliff:g>, Do Not Disturb access may also be turned off.
</string>
<string name="notification_listener_disable_warning_confirm">Turn off</string>
<string name="notification_listener_disable_warning_cancel">Cancel</string>
<!-- Title for managing VR (virtual reality) helper services. [CHAR LIMIT=50] -->
<string name="vr_listeners_title">VR helper services</string>
@@ -6870,6 +6876,8 @@
<!-- Zen mode access settings - summary for warning dialog when enabling access [CHAR LIMIT=NONE] -->
<string name="zen_access_warning_dialog_summary">The app will be able to turn on/off Do Not Disturb and make changes to related settings.</string>
<string name="zen_access_disabled_package_warning">Must stay turned on because notification access is on</string>
<!-- Zen mode access settings - title for warning dialog when revoking access [CHAR LIMIT=NONE] -->
<string name="zen_access_revoke_warning_dialog_title">Revoke access to Do Not Disturb for <xliff:g id="app" example="Tasker">%1$s</xliff:g>?</string>

View File

@@ -16,8 +16,16 @@
package com.android.settings.notification;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
@@ -30,6 +38,14 @@ public class NotificationAccessSettings extends ManagedServiceSettings {
private static final String TAG = NotificationAccessSettings.class.getSimpleName();
private static final Config CONFIG = getNotificationListenerConfig();
private NotificationManager mNm;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
private static Config getNotificationListenerConfig() {
final Config c = new Config();
c.tag = TAG;
@@ -60,4 +76,74 @@ public class NotificationAccessSettings extends ManagedServiceSettings {
public static int getEnabledListenersCount(Context context) {
return ServiceListing.getEnabledServicesCount(CONFIG, context);
}
protected boolean setEnabled(ComponentName service, String title, boolean enable) {
if (!enable) {
if (!mServiceListing.isEnabled(service)) {
return true; // already disabled
}
// show a friendly dialog
new FriendlyWarningDialogFragment()
.setServiceInfo(service, title)
.show(getFragmentManager(), "friendlydialog");
return false;
} else {
return super.setEnabled(service, title, enable);
}
}
private static void deleteRules(final Context context, final String pkg) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
final NotificationManager mgr = context.getSystemService(NotificationManager.class);
mgr.removeAutomaticZenRules(pkg);
}
});
}
public class FriendlyWarningDialogFragment extends DialogFragment {
static final String KEY_COMPONENT = "c";
static final String KEY_LABEL = "l";
public FriendlyWarningDialogFragment setServiceInfo(ComponentName cn, String label) {
Bundle args = new Bundle();
args.putString(KEY_COMPONENT, cn.flattenToString());
args.putString(KEY_LABEL, label);
setArguments(args);
return this;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
final String label = args.getString(KEY_LABEL);
final ComponentName cn = ComponentName.unflattenFromString(args
.getString(KEY_COMPONENT));
final String summary = getResources().getString(
R.string.notification_listener_disable_warning_summary, label);
return new AlertDialog.Builder(mContext)
.setMessage(summary)
.setCancelable(true)
.setPositiveButton(R.string.notification_listener_disable_warning_confirm,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mServiceListing.setEnabled(cn, false);
if (!mNm.isNotificationPolicyAccessGrantedForPackage(
cn.getPackageName())) {
deleteRules(mContext, cn.getPackageName());
}
}
})
.setNegativeButton(R.string.notification_listener_disable_warning_cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// pass
}
})
.create();
}
}
}

View File

@@ -21,6 +21,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
@@ -32,6 +33,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
@@ -40,6 +42,8 @@ import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;
import android.widget.Toast;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settings.R;
@@ -50,6 +54,7 @@ import java.util.List;
public class ZenAccessSettings extends EmptyTextSettings {
private final SettingObserver mObserver = new SettingObserver();
private static final String ENABLED_SERVICES_SEPARATOR = ":";
private Context mContext;
private PackageManager mPkgMan;
@@ -83,6 +88,9 @@ public class ZenAccessSettings extends EmptyTextSettings {
getContentResolver().registerContentObserver(
Secure.getUriFor(Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), false,
mObserver);
getContentResolver().registerContentObserver(
Secure.getUriFor(Secure.ENABLED_NOTIFICATION_LISTENERS), false,
mObserver);
}
@Override
@@ -96,7 +104,7 @@ public class ZenAccessSettings extends EmptyTextSettings {
screen.removeAll();
final ArrayList<ApplicationInfo> apps = new ArrayList<>();
final ArraySet<String> requesting = mNoMan.getPackagesRequestingNotificationPolicyAccess();
if (requesting != null && !requesting.isEmpty()) {
if (!requesting.isEmpty()) {
final List<ApplicationInfo> installed = mPkgMan.getInstalledApplications(0);
if (installed != null) {
for (ApplicationInfo app : installed) {
@@ -106,6 +114,8 @@ public class ZenAccessSettings extends EmptyTextSettings {
}
}
}
ArraySet<String> autoApproved = getEnabledNotificationListeners();
requesting.addAll(autoApproved);
Collections.sort(apps, new PackageItemInfo.DisplayNameComparator(mPkgMan));
for (ApplicationInfo app : apps) {
final String pkg = app.packageName;
@@ -115,6 +125,10 @@ public class ZenAccessSettings extends EmptyTextSettings {
pref.setIcon(app.loadIcon(mPkgMan));
pref.setTitle(label);
pref.setChecked(hasAccess(pkg));
if (autoApproved.contains(pkg)) {
pref.setEnabled(false);
pref.setSummary(getString(R.string.zen_access_disabled_package_warning));
}
pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
@@ -135,6 +149,22 @@ public class ZenAccessSettings extends EmptyTextSettings {
}
}
private ArraySet<String> getEnabledNotificationListeners() {
ArraySet<String> packages = new ArraySet<>();
String settingValue = Settings.Secure.getString(getContext().getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
if (!TextUtils.isEmpty(settingValue)) {
String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
for (int i = 0; i < restored.length; i++) {
ComponentName value = ComponentName.unflattenFromString(restored[i]);
if (null != value) {
packages.add(value.getPackageName());
}
}
}
return packages;
}
private boolean hasAccess(String pkg) {
return mNoMan.isNotificationPolicyAccessGrantedForPackage(pkg);
}

View File

@@ -43,11 +43,10 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.utils.ManagedServiceSettings.Config;
import com.android.settings.utils.ServiceListing;
import com.android.settings.utils.ZenServiceListing;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
@@ -56,14 +55,14 @@ public class ZenModeAutomationSettings extends ZenModeSettingsBase {
static final Config CONFIG = getConditionProviderConfig();
private PackageManager mPm;
private ServiceListing mServiceListing;
private ZenServiceListing mServiceListing;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.zen_mode_automation_settings);
mPm = mContext.getPackageManager();
mServiceListing = new ServiceListing(mContext, CONFIG);
mServiceListing = new ZenServiceListing(mContext, CONFIG);
mServiceListing.reloadApprovedServices();
}
@@ -203,6 +202,7 @@ public class ZenModeAutomationSettings extends ZenModeSettingsBase {
final Config c = new Config();
c.tag = TAG;
c.setting = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
c.secondarySetting = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
c.intentAction = ConditionProviderService.SERVICE_INTERFACE;
c.permission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
c.noun = "condition provider";
@@ -308,7 +308,7 @@ public class ZenModeAutomationSettings extends ZenModeSettingsBase {
final String action = isSchedule ? ZenModeScheduleRuleSettings.ACTION
: isEvent ? ZenModeEventRuleSettings.ACTION : "";
ServiceInfo si = mServiceListing.findService(mContext, CONFIG, rule.getOwner());
ServiceInfo si = mServiceListing.findService(rule.getOwner());
ComponentName settingsActivity = getSettingsActivity(si);
setIntent(getRuleIntent(action, settingsActivity, mId));
setSelectable(settingsActivity != null || isSystemRule);

View File

@@ -4,6 +4,31 @@ import android.content.ComponentName;
import android.net.Uri;
public class ZenRuleInfo {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ZenRuleInfo that = (ZenRuleInfo) o;
if (isSystem != that.isSystem) return false;
if (ruleInstanceLimit != that.ruleInstanceLimit) return false;
if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null)
return false;
if (title != null ? !title.equals(that.title) : that.title != null) return false;
if (settingsAction != null ? !settingsAction.equals(
that.settingsAction) : that.settingsAction != null) return false;
if (configurationActivity != null ? !configurationActivity.equals(
that.configurationActivity) : that.configurationActivity != null) return false;
if (defaultConditionId != null ? !defaultConditionId.equals(
that.defaultConditionId) : that.defaultConditionId != null) return false;
if (serviceComponent != null ? !serviceComponent.equals(
that.serviceComponent) : that.serviceComponent != null) return false;
return packageLabel != null ? packageLabel.equals(
that.packageLabel) : that.packageLabel == null;
}
public String packageName;
public String title;
public String settingsAction;

View File

@@ -27,6 +27,7 @@ import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,6 +37,7 @@ import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.utils.ServiceListing;
import com.android.settings.utils.ZenServiceListing;
import java.lang.ref.WeakReference;
import java.text.Collator;
@@ -43,6 +45,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public abstract class ZenRuleSelectionDialog {
private static final String TAG = "ZenRuleSelectionDialog";
@@ -53,9 +57,9 @@ public abstract class ZenRuleSelectionDialog {
private NotificationManager mNm;
private final AlertDialog mDialog;
private final LinearLayout mRuleContainer;
private final ServiceListing mServiceListing;
private final ZenServiceListing mServiceListing;
public ZenRuleSelectionDialog(Context context, ServiceListing serviceListing) {
public ZenRuleSelectionDialog(Context context, ZenServiceListing serviceListing) {
mContext = context;
mPm = context.getPackageManager();
mNm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -67,7 +71,7 @@ public abstract class ZenRuleSelectionDialog {
if (mServiceListing != null) {
bindType(defaultNewEvent());
bindType(defaultNewSchedule());
mServiceListing.addCallback(mServiceListingCallback);
mServiceListing.addZenCallback(mServiceListingCallback);
mServiceListing.reloadApprovedServices();
}
mDialog = new AlertDialog.Builder(context)
@@ -77,7 +81,7 @@ public abstract class ZenRuleSelectionDialog {
@Override
public void onDismiss(DialogInterface dialog) {
if (mServiceListing != null) {
mServiceListing.removeCallback(mServiceListingCallback);
mServiceListing.removeZenCallback(mServiceListingCallback);
}
}
})
@@ -152,24 +156,24 @@ public abstract class ZenRuleSelectionDialog {
return rt;
}
private void bindExternalRules(List<ZenRuleInfo> externalRuleTypes) {
Collections.sort(externalRuleTypes, RULE_TYPE_COMPARATOR);
private void bindExternalRules(Set<ZenRuleInfo> externalRuleTypes) {
for (ZenRuleInfo ri : externalRuleTypes) {
bindType(ri);
}
}
private final ServiceListing.Callback mServiceListingCallback = new ServiceListing.Callback() {
private final ZenServiceListing.Callback mServiceListingCallback = new
ZenServiceListing.Callback() {
@Override
public void onServicesReloaded(List<ServiceInfo> services) {
public void onServicesReloaded(Set<ServiceInfo> services) {
if (DEBUG) Log.d(TAG, "Services reloaded: count=" + services.size());
List<ZenRuleInfo> externalRuleTypes = new ArrayList<>();
for (int i = 0; i < services.size(); i++) {
final ZenRuleInfo ri = ZenModeAutomationSettings.getRuleInfo(mPm, services.get(i));
Set<ZenRuleInfo> externalRuleTypes = new TreeSet<>(RULE_TYPE_COMPARATOR);
for (ServiceInfo serviceInfo : services) {
final ZenRuleInfo ri = ZenModeAutomationSettings.getRuleInfo(mPm, serviceInfo);
if (ri != null && ri.configurationActivity != null
&& mNm.isNotificationPolicyAccessGrantedForPackage(ri.packageName)
&& (ri.ruleInstanceLimit <= 0 || ri.ruleInstanceLimit
>= (mNm.getRuleInstanceCount(services.get(i).getComponentName()) + 1))) {
>= (mNm.getRuleInstanceCount(serviceInfo.getComponentName()) + 1))) {
externalRuleTypes.add(ri);
}
}

View File

@@ -42,9 +42,9 @@ import java.util.List;
public abstract class ManagedServiceSettings extends EmptyTextSettings {
private final Config mConfig;
private Context mContext;
protected Context mContext;
private PackageManager mPM;
private ServiceListing mServiceListing;
protected ServiceListing mServiceListing;
private TextView mEmpty;
abstract protected Config getConfig();
@@ -111,7 +111,7 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings {
}
}
private boolean setEnabled(ComponentName service, String title, boolean enable) {
protected boolean setEnabled(ComponentName service, String title, boolean enable) {
if (!enable) {
// the simple version: disabling
mServiceListing.setEnabled(service, false);
@@ -173,6 +173,7 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings {
public static class Config {
public String tag;
public String setting;
public String secondarySetting;
public String intentAction;
public String permission;
public String noun;

View File

@@ -30,6 +30,7 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import com.android.settings.utils.ManagedServiceSettings.Config;
@@ -46,7 +47,6 @@ public class ServiceListing {
private final HashSet<ComponentName> mEnabledServices = new HashSet<ComponentName>();
private final List<ServiceInfo> mServices = new ArrayList<ServiceInfo>();
private final List<Callback> mCallbacks = new ArrayList<Callback>();
private final List<ServiceInfo> mApprovedServices = new ArrayList<ServiceInfo>();
private boolean mListening;
@@ -95,18 +95,7 @@ public class ServiceListing {
return getServices(c, null, pm);
}
public ServiceInfo findService(Context context, Config config, final ComponentName cn) {
final ServiceListing listing = new ServiceListing(context, config);
for (ServiceInfo service : mApprovedServices) {
final ComponentName serviceCN = new ComponentName(service.packageName, service.name);
if (serviceCN.equals(cn)) {
return service;
}
}
return null;
}
private static int getServices(Config c, List<ServiceInfo> list, PackageManager pm) {
protected static int getServices(Config c, List<ServiceInfo> list, PackageManager pm) {
int services = 0;
if (list != null) {
list.clear();
@@ -174,26 +163,6 @@ public class ServiceListing {
return mServices;
}
public void reloadApprovedServices() {
mApprovedServices.clear();
final String flat = Settings.Secure.getString(mContentResolver, mConfig.setting);
if (flat != null && !"".equals(flat)) {
final List<String> names = Arrays.asList(flat.split(":"));
List<ServiceInfo> services = new ArrayList<>();
getServices(mConfig, services, mContext.getPackageManager());
for (ServiceInfo service : services) {
final ComponentName componentName = service.getComponentName();
String flatCn = service.getComponentName().flattenToString();
if (names.contains(flatCn) || names.contains(componentName.getPackageName())) {
mApprovedServices.add(service);
}
}
for (Callback callback : mCallbacks) {
callback.onServicesReloaded(mApprovedServices);
}
}
}
public boolean isEnabled(ComponentName cn) {
return mEnabledServices.contains(cn);
}

View File

@@ -0,0 +1,148 @@
/*
* 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.utils;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class ZenServiceListing {
private final ContentResolver mContentResolver;
private final Context mContext;
private final ManagedServiceSettings.Config mConfig;
private final Set<ServiceInfo> mApprovedServices = new ArraySet<ServiceInfo>();
private final List<Callback> mZenCallbacks = new ArrayList<>();
public ZenServiceListing(Context context, ManagedServiceSettings.Config config) {
mContext = context;
mConfig = config;
mContentResolver = context.getContentResolver();
}
public ServiceInfo findService(final ComponentName cn) {
for (ServiceInfo service : mApprovedServices) {
final ComponentName serviceCN = new ComponentName(service.packageName, service.name);
if (serviceCN.equals(cn)) {
return service;
}
}
return null;
}
public void addZenCallback(Callback callback) {
mZenCallbacks.add(callback);
}
public void removeZenCallback(Callback callback) {
mZenCallbacks.remove(callback);
}
public void reloadApprovedServices() {
mApprovedServices.clear();
String[] settings = {mConfig.setting, mConfig.secondarySetting};
for (String setting : settings) {
if (!TextUtils.isEmpty(setting)) {
final String flat = Settings.Secure.getString(mContentResolver, setting);
if (!TextUtils.isEmpty(flat)) {
final List<String> names = Arrays.asList(flat.split(":"));
List<ServiceInfo> services = new ArrayList<>();
getServices(mConfig, services, mContext.getPackageManager());
for (ServiceInfo service : services) {
if (matchesApprovedPackage(names, service.getComponentName())) {
mApprovedServices.add(service);
}
}
}
}
}
if (!mApprovedServices.isEmpty()) {
for (Callback callback : mZenCallbacks) {
callback.onServicesReloaded(mApprovedServices);
}
}
}
// Setting could contain: the component name of the condition provider, the package name of
// the condition provider, the component name of the notification listener.
private boolean matchesApprovedPackage(List<String> approved, ComponentName serviceOwner) {
String flatCn = serviceOwner.flattenToString();
if (approved.contains(flatCn) || approved.contains(serviceOwner.getPackageName())) {
return true;
}
for (String entry : approved) {
if (!TextUtils.isEmpty(entry)) {
ComponentName approvedComponent = ComponentName.unflattenFromString(entry);
if (approvedComponent != null && approvedComponent.getPackageName().equals(
serviceOwner.getPackageName())) {
return true;
}
}
}
return false;
}
private static int getServices(ManagedServiceSettings.Config c, List<ServiceInfo> list,
PackageManager pm) {
int services = 0;
if (list != null) {
list.clear();
}
final int user = ActivityManager.getCurrentUser();
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
new Intent(c.intentAction),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
user);
for (int i = 0, count = installedServices.size(); i < count; i++) {
ResolveInfo resolveInfo = installedServices.get(i);
ServiceInfo info = resolveInfo.serviceInfo;
if (!c.permission.equals(info.permission)) {
Slog.w(c.tag, "Skipping " + c.noun + " service "
+ info.packageName + "/" + info.name
+ ": it does not require the permission "
+ c.permission);
continue;
}
if (list != null) {
list.add(info);
}
services++;
}
return services;
}
public interface Callback {
void onServicesReloaded(Set<ServiceInfo> services);
}
}