Add NLS specific screens for notification listener approval
Fixes: 141689199
Fixes: 143639217
Test: atest
Change-Id: I4ead087e0015ad33d6be4f9357de50a4298b3347
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 24963b3..c6b7e62 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -96,6 +96,7 @@
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 0000000..3577946
--- /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 f9e8fe3..6025da5 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 @@
? 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 0000000..dc0a1cb
--- /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<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;
+ }
+ }
+ }
+ }
+}
\ 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 0000000..6613f96e7
--- /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 a318037..0934ba9 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.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 @@
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 4c20f32..7f5f536 100644
--- a/src/com/android/settings/notification/NotificationAccessSettings.java
+++ b/src/com/android/settings/notification/NotificationAccessSettings.java
@@ -16,61 +16,174 @@
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;
}
@@ -82,109 +195,10 @@
}
@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);
}