Add NLS specific screens for notification listener approval

Fixes: 141689199
Fixes: 143639217
Test: atest

Change-Id: I4ead087e0015ad33d6be4f9357de50a4298b3347
This commit is contained in:
Julia Reynolds
2019-10-29 15:15:08 -04:00
parent 2c3ba517fe
commit 31dc1fd806
11 changed files with 591 additions and 137 deletions

View File

@@ -2412,6 +2412,17 @@
android:value="com.android.settings.notification.NotificationAccessSettings" />
</activity>
<activity
android:name="Settings$NotificationAccessDetailsActivity"
android:label="@string/manage_notification_access_title" >
<intent-filter android:priority="1">
<action android:name="android.settings.NOTIFICATION_LISTENER_DETAIL_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessDetails" />
</activity>
<activity
android:name="Settings$NotificationAssistantSettingsActivity"
android:label="@string/notification_assistant_title"

View File

@@ -8069,6 +8069,9 @@
<!-- String to show in the list of notification listeners, when none is installed -->
<string name="no_notification_listeners">No installed apps have requested notification access.</string>
<!-- Button title that grants 'notification access' permission to an app [CHAR_LIMIT=60]-->
<string name="notification_access_detail_switch">Allow notification access</string>
<!-- Title for a warning message about security implications of enabling a notification
assistant, displayed as a dialog message. [CHAR LIMIT=NONE] -->
<string name="notification_assistant_security_warning_title">Allow notification access for

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="notification_access_permission_detail_settings"
android:title="@string/manage_notification_access_title">
<SwitchPreference
android:key="notification_access_switch"
android:title="@string/notification_access_detail_switch"/>
</PreferenceScreen>

View File

@@ -96,6 +96,7 @@ public class Settings extends SettingsActivity {
public static class NotificationStationActivity extends SettingsActivity { /* empty */ }
public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessDetailsActivity extends SettingsActivity { /* empty */ }
public static class VrListenersSettingsActivity extends SettingsActivity { /* empty */ }
public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppPictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }

View File

@@ -0,0 +1,75 @@
/*
* 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.applications.specialaccess.notificationaccess;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
public class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
static final String KEY_COMPONENT = "c";
static final String KEY_LABEL = "l";
public FriendlyWarningDialogFragment setServiceInfo(ComponentName cn, CharSequence label,
Fragment target) {
Bundle args = new Bundle();
args.putString(KEY_COMPONENT, cn.flattenToString());
args.putCharSequence(KEY_LABEL, label);
setArguments(args);
setTargetFragment(target, 0);
return this;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_DISABLE_NOTIFICATION_ACCESS;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
final CharSequence label = args.getCharSequence(KEY_LABEL);
final ComponentName cn = ComponentName.unflattenFromString(args
.getString(KEY_COMPONENT));
NotificationAccessDetails parent = (NotificationAccessDetails) getTargetFragment();
final String summary = getResources().getString(
R.string.notification_listener_disable_warning_summary, label);
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setCancelable(true)
.setPositiveButton(R.string.notification_listener_disable_warning_confirm,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
parent.disable(cn);
}
})
.setNegativeButton(R.string.notification_listener_disable_warning_cancel,
(dialog, id) -> {
// pass
})
.create();
}
}

View File

@@ -17,6 +17,8 @@
package com.android.settings.applications.specialaccess.notificationaccess;
import android.app.ActivityManager;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import com.android.settings.core.BasePreferenceController;
@@ -33,4 +35,9 @@ public class NotificationAccessController extends BasePreferenceController {
? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE;
}
public static boolean hasAccess(Context context, ComponentName cn) {
return context.getSystemService(NotificationManager.class)
.isNotificationListenerAccessGranted(cn);
}
}

View File

@@ -0,0 +1,226 @@
/*
* 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.applications.specialaccess.notificationaccess;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
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.os.AsyncTask;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.Slog;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;
import java.util.List;
import java.util.Objects;
public class NotificationAccessDetails extends AppInfoBase {
private static final String TAG = "NotifAccessDetails";
private static final String SWITCH_PREF_KEY = "notification_access_switch";
private boolean mCreated;
private ComponentName mComponentName;
private CharSequence mServiceName;
private boolean mIsNls;
private NotificationManager mNm;
private PackageManager mPm;
@Override
public void onCreate(Bundle savedInstanceState) {
final Intent intent = getIntent();
if (mComponentName == null && intent != null) {
String cn = intent.getStringExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME);
if (cn != null) {
mComponentName = ComponentName.unflattenFromString(cn);
if (mComponentName != null) {
final Bundle args = getArguments();
args.putString(ARG_PACKAGE_NAME, mComponentName.getPackageName());
}
}
}
super.onCreate(savedInstanceState);
mNm = getContext().getSystemService(NotificationManager.class);
mPm = getPackageManager();
addPreferencesFromResource(R.xml.notification_access_permission_details);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mCreated) {
Log.w(TAG, "onActivityCreated: ignoring duplicate call");
return;
}
mCreated = true;
if (mPackageInfo == null) return;
loadNotificationListenerService();
final Activity activity = getActivity();
final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */)
.setRecyclerView(getListView(), getSettingsLifecycle())
.setIcon(IconDrawableFactory.newInstance(getContext())
.getBadgedIcon(mPackageInfo.applicationInfo))
.setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
.setSummary(mServiceName)
.setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo))
.setPackageName(mPackageName)
.setUid(mPackageInfo.applicationInfo.uid)
.setHasAppInfoLink(true)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
.done(activity, getPrefContext());
getPreferenceScreen().addPreference(pref);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.NOTIFICATION_ACCESS_DETAIL;
}
@Override
protected boolean refreshUi() {
final Context context = getContext();
if (context.getSystemService(ActivityManager.class).isLowRamDeviceStatic()) {
Slog.d(TAG, "not available on low ram devices");
return false;
}
if (mComponentName == null) {
// No service given
Slog.d(TAG, "No component name provided");
return false;
}
if (!mIsNls) {
// This component doesn't have the right androidmanifest definition to be an NLS
Slog.d(TAG, "Provided component name is not an NLS");
return false;
}
if (UserManager.get(getContext()).isManagedProfile()) {
// Apps in the work profile do not support notification listeners.
Slog.d(TAG, "NLSes aren't allowed in work profiles");
return false;
}
updatePreference(findPreference(SWITCH_PREF_KEY));
return true;
}
@Override
protected AlertDialog createDialog(int id, int errorCode) {
return null;
}
public void updatePreference(SwitchPreference preference) {
final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm);
preference.setChecked(isServiceEnabled(mComponentName));
preference.setOnPreferenceChangeListener((p, newValue) -> {
final boolean access = (Boolean) newValue;
if (!access) {
if (!isServiceEnabled(mComponentName)) {
return true; // already disabled
}
// show a friendly dialog
new FriendlyWarningDialogFragment()
.setServiceInfo(mComponentName, label, this)
.show(getFragmentManager(), "friendlydialog");
return false;
} else {
if (isServiceEnabled(mComponentName)) {
return true; // already enabled
}
// show a scary dialog
new ScaryWarningDialogFragment()
.setServiceInfo(mComponentName, label, this)
.show(getFragmentManager(), "dialog");
return false;
}
});
}
@VisibleForTesting
void logSpecialPermissionChange(boolean enable, String packageName) {
int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY;
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
logCategory, packageName);
}
public void disable(final ComponentName cn) {
logSpecialPermissionChange(true, cn.getPackageName());
mNm.setNotificationListenerAccessGranted(cn, false);
AsyncTask.execute(() -> {
if (!mNm.isNotificationPolicyAccessGrantedForPackage(
cn.getPackageName())) {
mNm.removeAutomaticZenRules(cn.getPackageName());
}
});
refreshUi();
}
protected void enable(ComponentName cn) {
logSpecialPermissionChange(true, cn.getPackageName());
mNm.setNotificationListenerAccessGranted(cn, true);
refreshUi();
}
protected boolean isServiceEnabled(ComponentName cn) {
return mNm.isNotificationListenerAccessGranted(cn);
}
protected void loadNotificationListenerService() {
mIsNls = false;
if (mComponentName == null) {
return;
}
Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE)
.setComponent(mComponentName);
List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser(
intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, mUserId);
for (ResolveInfo resolveInfo : installedServices) {
ServiceInfo info = resolveInfo.serviceInfo;
if (android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
info.permission)) {
if (Objects.equals(mComponentName, info.getComponentName())) {
mIsNls = true;
mServiceName = info.loadLabel(mPm);
break;
}
}
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.applications.specialaccess.notificationaccess;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
public class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
private static final String KEY_COMPONENT = "c";
private static final String KEY_LABEL = "l";
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_SERVICE_ACCESS_WARNING;
}
public ScaryWarningDialogFragment setServiceInfo(ComponentName cn, CharSequence label,
Fragment target) {
Bundle args = new Bundle();
args.putString(KEY_COMPONENT, cn.flattenToString());
args.putCharSequence(KEY_LABEL, label);
setArguments(args);
setTargetFragment(target, 0);
return this;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
final CharSequence label = args.getCharSequence(KEY_LABEL);
final ComponentName cn = ComponentName.unflattenFromString(args
.getString(KEY_COMPONENT));
NotificationAccessDetails parent = (NotificationAccessDetails) getTargetFragment();
final String title = getResources().getString(
R.string.notification_listener_security_warning_title, label);
final String summary = getResources().getString(
R.string.notification_listener_security_warning_summary, label);
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setTitle(title)
.setCancelable(true)
.setPositiveButton(R.string.allow,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
parent.enable(cn);
}
})
.setNegativeButton(R.string.deny,
(dialog, id) -> {
// pass
})
.create();
}
}

View File

@@ -45,6 +45,7 @@ import com.android.settings.applications.assist.ManageAssist;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.applications.managedomainurls.ManageDomainUrls;
import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminSettings;
import com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessDetails;
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails;
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings;
import com.android.settings.applications.specialaccess.vrlistener.VrListenerSettings;
@@ -217,6 +218,7 @@ public class SettingsGateway {
DreamSettings.class.getName(),
UserSettings.class.getName(),
NotificationAccessSettings.class.getName(),
NotificationAccessDetails.class.getName(),
AppBubbleNotificationSettings.class.getName(),
ZenAccessSettings.class.getName(),
ZenAccessDetails.class.getName(),

View File

@@ -16,60 +16,173 @@
package com.android.settings.notification;
import android.app.Dialog;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessDetails;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.utils.ManagedServiceSettings;
import com.android.settings.widget.EmptyTextSettings;
import com.android.settingslib.applications.ServiceListing;
import com.android.settingslib.search.SearchIndexable;
import java.util.List;
/**
* Settings screen for managing notification listener permissions
*/
@SearchIndexable
public class NotificationAccessSettings extends ManagedServiceSettings {
private static final String TAG = "NotificationAccessSettings";
private static final Config CONFIG = new Config.Builder()
.setTag(TAG)
.setSetting(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS)
.setIntentAction(NotificationListenerService.SERVICE_INTERFACE)
.setPermission(android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)
.setNoun("notification listener")
.setWarningDialogTitle(R.string.notification_listener_security_warning_title)
.setWarningDialogSummary(R.string.notification_listener_security_warning_summary)
.setEmptyText(R.string.no_notification_listeners)
.build();
public class NotificationAccessSettings extends EmptyTextSettings {
private static final String TAG = "NotifAccessSettings";
private static final ManagedServiceSettings.Config CONFIG =
new ManagedServiceSettings.Config.Builder()
.setTag(TAG)
.setSetting(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS)
.setIntentAction(NotificationListenerService.SERVICE_INTERFACE)
.setPermission(android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)
.setNoun("notification listener")
.setWarningDialogTitle(R.string.notification_listener_security_warning_title)
.setWarningDialogSummary(
R.string.notification_listener_security_warning_summary)
.setEmptyText(R.string.no_notification_listeners)
.build();
private NotificationManager mNm;
protected Context mContext;
private PackageManager mPm;
private DevicePolicyManager mDpm;
private ServiceListing mServiceListing;
private IconDrawableFactory mIconDrawableFactory;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Context ctx = getContext();
if (UserManager.get(ctx).isManagedProfile()) {
mContext = getActivity();
mPm = mContext.getPackageManager();
mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
mServiceListing = new ServiceListing.Builder(mContext)
.setPermission(CONFIG.permission)
.setIntentAction(CONFIG.intentAction)
.setNoun(CONFIG.noun)
.setSetting(CONFIG.setting)
.setTag(CONFIG.tag)
.build();
mServiceListing.addCallback(this::updateList);
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext));
if (UserManager.get(mContext).isManagedProfile()) {
// Apps in the work profile do not support notification listeners.
Toast.makeText(ctx, R.string.notification_settings_work_profile, Toast.LENGTH_SHORT)
.show();
Toast.makeText(mContext, R.string.notification_settings_work_profile,
Toast.LENGTH_SHORT).show();
finish();
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setEmptyText(CONFIG.emptyText);
}
@Override
public void onResume() {
super.onResume();
if (!ActivityManager.isLowRamDeviceStatic()) {
mServiceListing.reload();
mServiceListing.setListening(true);
} else {
setEmptyText(R.string.disabled_low_ram_device);
}
}
@Override
public void onPause() {
super.onPause();
mServiceListing.setListening(false);
}
private void updateList(List<ServiceInfo> services) {
final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId());
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
services.sort(new PackageItemInfo.DisplayNameComparator(mPm));
for (ServiceInfo service : services) {
final ComponentName cn = new ComponentName(service.packageName, service.name);
CharSequence title = null;
try {
title = mPm.getApplicationInfoAsUser(
service.packageName, 0, UserHandle.myUserId()).loadLabel(mPm);
} catch (PackageManager.NameNotFoundException e) {
// unlikely, as we are iterating over live services.
Log.e(TAG, "can't find package name", e);
}
final Preference pref = new Preference(getPrefContext());
pref.setTitle(title);
pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
UserHandle.getUserId(service.applicationInfo.uid)));
pref.setKey(cn.flattenToString());
pref.setSummary(mNm.isNotificationListenerAccessGranted(cn)
? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed);
if (managedProfileId != UserHandle.USER_NULL
&& !mDpm.isNotificationListenerServicePermitted(
service.packageName, managedProfileId)) {
pref.setSummary(R.string.work_profile_notification_access_blocked_summary);
}
pref.setOnPreferenceClickListener(preference -> {
final Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, cn.getPackageName());
args.putInt(AppInfoBase.ARG_PACKAGE_UID, service.applicationInfo.uid);
Bundle extras = new Bundle();
extras.putString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
cn.flattenToString());
new SubSettingLauncher(getContext())
.setDestination(NotificationAccessDetails.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.setTitleRes(R.string.manage_zen_access_title)
.setArguments(args)
.setExtras(extras)
.setUserHandle(UserHandle.getUserHandleForUid(service.applicationInfo.uid))
.launch();
return true;
});
pref.setKey(cn.flattenToString());
screen.addPreference(pref);
}
highlightPreferenceIfNeeded();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.NOTIFICATION_ACCESS;
@@ -81,110 +194,11 @@ public class NotificationAccessSettings extends ManagedServiceSettings {
mNm = context.getSystemService(NotificationManager.class);
}
@Override
protected Config getConfig() {
return CONFIG;
}
@Override
protected boolean setEnabled(ComponentName service, String title, boolean enable) {
logSpecialPermissionChange(enable, service.getPackageName());
if (!enable) {
if (!isServiceEnabled(service)) {
return true; // already disabled
}
// show a friendly dialog
new FriendlyWarningDialogFragment()
.setServiceInfo(service, title, this)
.show(getFragmentManager(), "friendlydialog");
return false;
} else {
if (isServiceEnabled(service)) {
return true; // already enabled
}
// show a scary dialog
new ScaryWarningDialogFragment()
.setServiceInfo(service, title, this)
.show(getFragmentManager(), "dialog");
return false;
}
}
@Override
protected boolean isServiceEnabled(ComponentName cn) {
return mNm.isNotificationListenerAccessGranted(cn);
}
@Override
protected void enable(ComponentName service) {
mNm.setNotificationListenerAccessGranted(service, true);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.notification_access_settings;
}
@VisibleForTesting
void logSpecialPermissionChange(boolean enable, String packageName) {
int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY;
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
logCategory, packageName);
}
private static void disable(final NotificationAccessSettings parent, final ComponentName cn) {
parent.mNm.setNotificationListenerAccessGranted(cn, false);
AsyncTask.execute(() -> {
if (!parent.mNm.isNotificationPolicyAccessGrantedForPackage(
cn.getPackageName())) {
parent.mNm.removeAutomaticZenRules(cn.getPackageName());
}
});
}
public static class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
static final String KEY_COMPONENT = "c";
static final String KEY_LABEL = "l";
public FriendlyWarningDialogFragment setServiceInfo(ComponentName cn, String label,
Fragment target) {
Bundle args = new Bundle();
args.putString(KEY_COMPONENT, cn.flattenToString());
args.putString(KEY_LABEL, label);
setArguments(args);
setTargetFragment(target, 0);
return this;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_DISABLE_NOTIFICATION_ACCESS;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
final String label = args.getString(KEY_LABEL);
final ComponentName cn = ComponentName.unflattenFromString(args
.getString(KEY_COMPONENT));
NotificationAccessSettings parent = (NotificationAccessSettings) getTargetFragment();
final String summary = getResources().getString(
R.string.notification_listener_disable_warning_summary, label);
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setCancelable(true)
.setPositiveButton(R.string.notification_listener_disable_warning_confirm,
(dialog, id) -> disable(parent, cn))
.setNegativeButton(R.string.notification_listener_disable_warning_cancel,
(dialog, id) -> {
// pass
})
.create();
}
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.notification_access_settings);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 The Android Open Source Project
* 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.
@@ -11,18 +11,21 @@
* 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
* limitations under the License.
*/
package com.android.settings.notification;
package com.android.settings.applications.specialaccess.notificationaccess;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.app.settings.SettingsEnums;
import android.content.pm.ActivityInfo;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails;
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -32,28 +35,34 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class NotificationAccessSettingsTest {
public class NotificationAccessDetailsTest {
private FakeFeatureFactory mFeatureFactory;
private NotificationAccessSettings mFragment;
private NotificationAccessDetails mFragment;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mFragment = new NotificationAccessSettings();
mFragment = new NotificationAccessDetails();
}
@Test
public void logSpecialPermissionChange() {
mFragment.logSpecialPermissionChange(true, "app");
verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class),
eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW),
eq("app"));
verify(mFeatureFactory.metricsFeatureProvider).action(
SettingsEnums.PAGE_UNKNOWN,
MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW,
mFragment.getMetricsCategory(),
"app",
0);
mFragment.logSpecialPermissionChange(false, "app");
verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class),
eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY),
eq("app"));
verify(mFeatureFactory.metricsFeatureProvider).action(
SettingsEnums.PAGE_UNKNOWN,
MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY,
mFragment.getMetricsCategory(),
"app",
0);
}
}