From: Fabian Kozynski Date: Wed, 20 Feb 2019 17:55:10 +0000 (-0500) Subject: Create settings screen for Notification Assistant X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=01b2a635e99b3793b8ae71b72ccc4755bd508490;p=android-x86%2Fpackages-apps-Settings.git Create settings screen for Notification Assistant Test: this atest Test: manual: change assistant and "adb shell dumpsys notification" Test: manual: verify persistance through reboot (including none) Fixes:120852765 Change-Id: Ie4516c3339246d66d7b6719ac5dd1d65c4d03b57 --- diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a94801f1df..fb6dad6940 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2335,6 +2335,18 @@ + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 10fc7d3cf7..236c0cc7a7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7796,10 +7796,29 @@ %d apps can read notifications + + Notification Assistant + + + No assistant + No installed apps have requested notification access. + Allow notification access for + %1$s? + + + %1$s will be able to read all notifications, + including personal information such as contact names and the text of messages you receive. + It will also be able to modify or dismiss notifications or trigger action buttons they contain. + \n\nThis will also give the app the ability to turn Do Not Disturb on or off and change related settings. + + + Allow notification access for %1$s? diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index 803d12bab8..38fa06043f 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -19,6 +19,13 @@ android:title="@string/configure_notification_settings" android:key="configure_notification_settings"> + + + + + \ No newline at end of file diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index ae4ae2ab33..7565dd8ef3 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -110,6 +110,7 @@ public class Settings extends SettingsActivity { public static class ZenModeEventRuleSettingsActivity extends SettingsActivity { /* empty */ } public static class SoundSettingsActivity extends SettingsActivity { /* empty */ } public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ } + public static class NotificationAssistantSettingsActivity extends SettingsActivity{ /* empty */ } public static class NotificationAppListActivity extends SettingsActivity { /* empty */ } public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ } public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index fb3d0c55ae..60655fe997 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -101,6 +101,7 @@ import com.android.settings.notification.ChannelGroupNotificationSettings; import com.android.settings.notification.ChannelNotificationSettings; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.NotificationAccessSettings; +import com.android.settings.notification.NotificationAssistantPicker; import com.android.settings.notification.NotificationStation; import com.android.settings.notification.SoundSettings; import com.android.settings.notification.ZenAccessSettings; @@ -218,6 +219,7 @@ public class SettingsGateway { AppInfoDashboardFragment.class.getName(), BatterySaverSettings.class.getName(), AppNotificationSettings.class.getName(), + NotificationAssistantPicker.class.getName(), ChannelNotificationSettings.class.getName(), ChannelGroupNotificationSettings.class.getName(), ApnSettings.class.getName(), diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java index 1b860e3b63..73f6e0618e 100644 --- a/src/com/android/settings/notification/ConfigureNotificationSettings.java +++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java @@ -58,6 +58,8 @@ public class ConfigureNotificationSettings extends DashboardFragment implements static final String KEY_LOCKSCREEN_WORK_PROFILE = "lock_screen_notifications_profile"; @VisibleForTesting static final String KEY_SWIPE_DOWN = "gesture_swipe_down_fingerprint_notifications"; + @VisibleForTesting + static final String KEY_NOTIFICATION_ASSISTANT = "notification_assistant"; private static final String KEY_NOTI_DEFAULT_RINGTONE = "notification_default_ringtone"; diff --git a/src/com/android/settings/notification/NotificationAssistantPicker.java b/src/com/android/settings/notification/NotificationAssistantPicker.java new file mode 100644 index 0000000000..7720e6fe00 --- /dev/null +++ b/src/com/android/settings/notification/NotificationAssistantPicker.java @@ -0,0 +1,162 @@ +/* + * 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.notification; + +import android.app.settings.SettingsEnums; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageItemInfo; +import android.content.pm.ServiceInfo; +import android.graphics.drawable.Drawable; +import android.provider.SearchIndexableResource; +import android.provider.Settings; +import android.service.notification.NotificationAssistantService; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.applications.defaultapps.DefaultAppPickerFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settingslib.applications.DefaultAppInfo; +import com.android.settingslib.applications.ServiceListing; +import com.android.settingslib.widget.CandidateInfo; + +import java.util.ArrayList; +import java.util.List; + +public class NotificationAssistantPicker extends DefaultAppPickerFragment implements + ServiceListing.Callback { + + private static final String TAG = "NotiAssistantPicker"; + + @VisibleForTesting + protected NotificationBackend mNotificationBackend; + private List mCandidateInfos = new ArrayList<>(); + @VisibleForTesting + protected Context mContext; + private ServiceListing mServiceListing; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mContext = context; + mNotificationBackend = new NotificationBackend(); + mServiceListing = new ServiceListing.Builder(context) + .setTag(TAG) + .setSetting(Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT) + .setIntentAction(NotificationAssistantService.SERVICE_INTERFACE) + .setPermission(android.Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE) + .setNoun("notification assistant") + .build(); + mServiceListing.addCallback(this); + mServiceListing.reload(); + } + + @Override + public void onDetach() { + super.onDetach(); + mServiceListing.removeCallback(this); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.notification_assistant_settings; + } + + @Override + protected List getCandidates() { + return mCandidateInfos; + } + + @Override + protected String getDefaultKey() { + ComponentName cn = mNotificationBackend.getAllowedNotificationAssistant(); + return (cn != null) ? cn.flattenToString() : ""; + } + + @Override + protected boolean setDefaultKey(String key) { + return mNotificationBackend.setNotificationAssistantGranted( + ComponentName.unflattenFromString(key)); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.DEFAULT_NOTIFICATION_ASSISTANT; + } + + @Override + protected CharSequence getConfirmationMessage(CandidateInfo info) { + if (TextUtils.isEmpty(info.getKey())) { + return null; + } + return mContext.getString(R.string.notification_assistant_security_warning_summary, + info.loadLabel()); + } + + @Override + public void onServicesReloaded(List services) { + List list = new ArrayList<>(); + services.sort(new PackageItemInfo.DisplayNameComparator(mPm)); + for (ServiceInfo service : services) { + final ComponentName cn = new ComponentName(service.packageName, service.name); + list.add(new DefaultAppInfo(mContext, mPm, mUserId, cn)); + } + list.add(new CandidateNone(mContext)); + mCandidateInfos = list; + } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex(Context context, + boolean enabled) { + final List result = new ArrayList<>(); + + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.notification_assistant_settings; + result.add(sir); + return result; + } + }; + + public static class CandidateNone extends CandidateInfo { + + public Context mContext; + + public CandidateNone(Context context) { + super(true); + mContext = context; + } + + @Override + public CharSequence loadLabel() { + return mContext.getString(R.string.no_notification_assistant); + } + + @Override + public Drawable loadIcon() { + return null; + } + + @Override + public String getKey() { + return ""; + } + } +} diff --git a/src/com/android/settings/notification/NotificationAssistantPreferenceController.java b/src/com/android/settings/notification/NotificationAssistantPreferenceController.java new file mode 100644 index 0000000000..5c591b894a --- /dev/null +++ b/src/com/android/settings/notification/NotificationAssistantPreferenceController.java @@ -0,0 +1,33 @@ +/* + * 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.notification; + +import android.content.Context; + +import com.android.settings.core.BasePreferenceController; + +public class NotificationAssistantPreferenceController extends BasePreferenceController { + + public NotificationAssistantPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return BasePreferenceController.AVAILABLE; + } +} diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index dbba6161d5..ba07438cab 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -23,6 +23,7 @@ import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -410,6 +411,29 @@ public class NotificationBackend { } } + public ComponentName getAllowedNotificationAssistant() { + try { + return sINM.getAllowedNotificationAssistant(); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return null; + } + } + + public boolean setNotificationAssistantGranted(ComponentName cn) { + try { + sINM.setNotificationAssistantAccessGranted(cn, true); + if (cn == null) { + return sINM.getAllowedNotificationAssistant() == null; + } else { + return cn.equals(sINM.getAllowedNotificationAssistant()); + } + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + /** * NotificationsSentState contains how often an app sends notifications and how recently it sent * one. diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAssistantPickerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationAssistantPickerTest.java new file mode 100644 index 0000000000..6b6ed023c6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/NotificationAssistantPickerTest.java @@ -0,0 +1,165 @@ +/* + * 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.notification; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_SMART_NULLS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; + +import com.android.settingslib.widget.CandidateInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +import org.mockito.invocation.InvocationOnMock; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class NotificationAssistantPickerTest { + + private NotificationAssistantPicker mFragment; + @Mock + private Context mContext; + @Mock + private PackageManager mPackageManager; + @Mock + private NotificationBackend mNotificationBackend; + private static final String TEST_PKG = "test.package"; + private static final String TEST_SRV = "test.component"; + private static final String TEST_CMP = TEST_PKG + "/" + TEST_SRV; + private static final String TEST_NAME = "Test name"; + private static final ComponentName TEST_COMPONENT = ComponentName.unflattenFromString(TEST_CMP); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mFragment = new TestNotificationAssistantPicker(mContext, mPackageManager, + mNotificationBackend); + } + + @Test + public void getCurrentAssistant() { + when(mNotificationBackend.getAllowedNotificationAssistant()).thenReturn(TEST_COMPONENT); + String key = mFragment.getDefaultKey(); + assertEquals(key, TEST_CMP); + } + + @Test + public void getCurrentAssistant_None() { + when(mNotificationBackend.getAllowedNotificationAssistant()).thenReturn(null); + String key = mFragment.getDefaultKey(); + assertEquals(key, ""); + } + + @Test + public void setAssistant() { + mFragment.setDefaultKey(TEST_CMP); + verify(mNotificationBackend).setNotificationAssistantGranted(TEST_COMPONENT); + } + + @Test + public void setAssistant_None() { + mFragment.setDefaultKey(""); + verify(mNotificationBackend).setNotificationAssistantGranted(null); + } + + @Test + public void candidateListHasNoneAtEnd() { + List list = new ArrayList<>(); + ServiceInfo serviceInfo = mock(ServiceInfo.class, RETURNS_SMART_NULLS); + serviceInfo.packageName = TEST_PKG; + serviceInfo.name = TEST_SRV; + list.add(serviceInfo); + mFragment.onServicesReloaded(list); + List candidates = mFragment.getCandidates(); + assertTrue(candidates.size() > 0); + assertEquals(candidates.get(candidates.size() - 1).getKey(), ""); + } + + @Test + public void candidateListHasCorrectCandidate() { + List list = new ArrayList<>(); + ServiceInfo serviceInfo = mock(ServiceInfo.class, RETURNS_SMART_NULLS); + serviceInfo.packageName = TEST_PKG; + serviceInfo.name = TEST_SRV; + list.add(serviceInfo); + mFragment.onServicesReloaded(list); + List candidates = mFragment.getCandidates(); + boolean found = false; + for (CandidateInfo c : candidates) { + if (TEST_CMP.equals(c.getKey())) { + found = true; + break; + } + } + if (!found) fail(); + } + + @Test + public void noDialogOnNoAssistantSelected() { + when(mContext.getString(anyInt(), anyString())).thenAnswer( + (InvocationOnMock invocation) -> { + return invocation.getArgument(1); + }); + assertNull(mFragment.getConfirmationMessage( + new NotificationAssistantPicker.CandidateNone(mContext))); + } + + @Test + public void dialogTextHasAssistantName() { + CandidateInfo c = mock(CandidateInfo.class); + when(mContext.getString(anyInt(), anyString())).thenAnswer( + (InvocationOnMock invocation) -> { + return invocation.getArgument(1); + }); + when(c.loadLabel()).thenReturn(TEST_NAME); + when(c.getKey()).thenReturn(TEST_CMP); + CharSequence text = mFragment.getConfirmationMessage(c); + assertNotNull(text); + assertTrue(text.toString().contains(TEST_NAME)); + } + + + private static class TestNotificationAssistantPicker extends NotificationAssistantPicker { + TestNotificationAssistantPicker(Context context, PackageManager packageManager, + NotificationBackend notificationBackend) { + mContext = context; + mPm = packageManager; + mNotificationBackend = notificationBackend; + } + } + +}