diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f2ed29cd3b7..c2dcd158d12 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2353,6 +2353,17 @@ android:value="com.android.settings.notification.NotificationAccessSettings" /> + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 758c05482dd..503da8732d2 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8066,6 +8066,9 @@ No installed apps have requested notification access. + + Allow notification access + Allow notification access for diff --git a/res/xml/notification_access_permission_details.xml b/res/xml/notification_access_permission_details.xml new file mode 100644 index 00000000000..8cc5993904f --- /dev/null +++ b/res/xml/notification_access_permission_details.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index c189b7b2ffe..f910fae11fd 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -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 */ } diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java new file mode 100644 index 00000000000..3577946e28f --- /dev/null +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java @@ -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(); + } +} diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessController.java index f9e8fe31571..6025da569d3 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessController.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessController.java @@ -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); + } } diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java new file mode 100644 index 00000000000..dc0a1cbf877 --- /dev/null +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -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 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; + } + } + } + } +} \ No newline at end of file diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java new file mode 100644 index 00000000000..6613f96e7fb --- /dev/null +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java @@ -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(); + } +} \ No newline at end of file diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index a3180379b50..0934ba93e09 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -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(), diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index 4c20f328444..7f5f5361714 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -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 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); } diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetailsTest.java similarity index 51% rename from tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java rename to tests/robotests/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetailsTest.java index cf476c17659..3e9889caa64 100644 --- a/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java +++ b/tests/robotests/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetailsTest.java @@ -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); } }