From 4e48da99a9ed4b2a722c33e71f6c69830018c475 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 26 Mar 2019 09:04:22 -0400 Subject: [PATCH] Move bubble settings to their own page To make room for graphics/longer descriptive text Test: robotests Bug: 129068779 Change-Id: Iac6ea43bd3a0cddc487ff3d1bbd8f35142294d01 --- AndroidManifest.xml | 12 ++ res/values/strings.xml | 4 +- res/xml/app_bubble_notification_settings.xml | 34 ++++ res/xml/app_notification_settings.xml | 7 +- res/xml/bubble_notification_settings.xml | 33 ++++ res/xml/configure_notification_settings.xml | 6 +- src/com/android/settings/Settings.java | 1 + .../core/gateway/SettingsGateway.java | 4 + .../AppBubbleNotificationSettings.java | 104 ++++++++++++ .../notification/AppNotificationSettings.java | 2 +- ...ubbleNotificationPreferenceController.java | 2 +- .../BubbleNotificationSettings.java | 65 ++++++++ .../BubblePreferenceController.java | 3 + ...mmaryNotificationPreferenceController.java | 53 +++++++ .../BubbleSummaryPreferenceController.java | 99 ++++++++++++ .../BubblePreferenceControllerTest.java | 5 + ...yNotificationPreferenceControllerTest.java | 70 ++++++++ ...BubbleSummaryPreferenceControllerTest.java | 149 ++++++++++++++++++ 18 files changed, 642 insertions(+), 11 deletions(-) create mode 100644 res/xml/app_bubble_notification_settings.xml create mode 100644 res/xml/bubble_notification_settings.xml create mode 100644 src/com/android/settings/notification/AppBubbleNotificationSettings.java create mode 100644 src/com/android/settings/notification/BubbleNotificationSettings.java create mode 100644 src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java create mode 100644 src/com/android/settings/notification/BubbleSummaryPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dbefdfa5e22..7b2935bfd0c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2454,6 +2454,18 @@ android:value="true" /> + + + + + + + + Allow notification dots - Allow bubbles + Bubbles - Allow apps to show some notifications as bubbles + Quickly access app content from anywhere using floating shortcuts Some notifications and other content can appear as bubbles on the screen. To open a bubble, tap it. To dismiss it, drag it down the screen. diff --git a/res/xml/app_bubble_notification_settings.xml b/res/xml/app_bubble_notification_settings.xml new file mode 100644 index 00000000000..8d97f8fda7a --- /dev/null +++ b/res/xml/app_bubble_notification_settings.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml index faad649a7ab..fedd3cc114f 100644 --- a/res/xml/app_notification_settings.xml +++ b/res/xml/app_notification_settings.xml @@ -60,11 +60,10 @@ settings:useAdditionalSummary="true" android:order="1001" settings:restrictedSwitchSummary="@string/enabled_by_admin" /> - + android:order="1002" /> + + + + + + + + + diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index e48ddc1f2bd..97a41822995 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -70,11 +70,11 @@ settings:controller="com.android.settings.notification.BadgingNotificationPreferenceController"/> - + settings:controller="com.android.settings.notification.BubbleSummaryNotificationPreferenceController" + android:fragment="com.android.settings.notification.BubbleNotificationSettings"/> createPreferenceControllers(Context context) { + mControllers = getPreferenceControllers(context, this); + return new ArrayList<>(mControllers); + } + + protected static List getPreferenceControllers( + Context context, AppBubbleNotificationSettings fragment) { + List controllers = new ArrayList<>(); + controllers.add(new HeaderPreferenceController(context, fragment)); + controllers.add(new BubblePreferenceController(context, new NotificationBackend())); + return controllers; + } + + @Override + public void onResume() { + super.onResume(); + + if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) { + Log.w(TAG, "Missing package or uid or packageinfo"); + finish(); + return; + } + + for (NotificationPreferenceController controller : mControllers) { + controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin); + controller.displayPreference(getPreferenceScreen()); + } + updatePreferenceStates(); + } + + /** + * For Search. + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.app_bubble_notification_settings; + return Arrays.asList(sir); + } + + @Override + public List createPreferenceControllers(Context + context) { + return new ArrayList<>(AppBubbleNotificationSettings.getPreferenceControllers( + context, null)); + } + }; +} diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 3ccca000406..24d85e21f59 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -152,7 +152,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { mControllers.add(new DescriptionPreferenceController(context)); mControllers.add(new NotificationsOffPreferenceController(context)); mControllers.add(new DeletedChannelsPreferenceController(context, mBackend)); - mControllers.add(new BubblePreferenceController(context, mBackend)); + mControllers.add(new BubbleSummaryPreferenceController(context, mBackend)); return new ArrayList<>(mControllers); } diff --git a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java index caba7d92cd9..83e73e9b841 100644 --- a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java +++ b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java @@ -56,7 +56,7 @@ public class BubbleNotificationPreferenceController extends TogglePreferenceCont @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - Preference preference = screen.findPreference(NOTIFICATION_BUBBLES); + Preference preference = screen.findPreference(getPreferenceKey()); if (preference != null) { mSettingObserver = new SettingObserver(preference); } diff --git a/src/com/android/settings/notification/BubbleNotificationSettings.java b/src/com/android/settings/notification/BubbleNotificationSettings.java new file mode 100644 index 00000000000..70442935cbd --- /dev/null +++ b/src/com/android/settings/notification/BubbleNotificationSettings.java @@ -0,0 +1,65 @@ +/* + * 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.Context; +import android.provider.SearchIndexableResource; + +import com.android.settings.R; +import com.android.settings.core.OnActivityResultListener; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import java.util.Arrays; +import java.util.List; + +@SearchIndexable +public class BubbleNotificationSettings extends DashboardFragment implements + OnActivityResultListener { + private static final String TAG = "BubbleNotiSettings"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.BUBBLE_SETTINGS; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bubble_notification_settings; + } + + /** + * For Search. + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.bubble_notification_settings; + return Arrays.asList(sir); + } + }; +} diff --git a/src/com/android/settings/notification/BubblePreferenceController.java b/src/com/android/settings/notification/BubblePreferenceController.java index 5b3be440622..5dab37445b8 100644 --- a/src/com/android/settings/notification/BubblePreferenceController.java +++ b/src/com/android/settings/notification/BubblePreferenceController.java @@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import android.content.Context; import android.provider.Settings; +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.RestrictedSwitchPreference; @@ -74,6 +75,8 @@ public class BubblePreferenceController extends NotificationPreferenceController pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin()); } else { pref.setChecked(mAppRow.allowBubbles); + pref.setSummary(mContext.getString( + R.string.bubbles_app_toggle_summary, mAppRow.label)); } } } diff --git a/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java new file mode 100644 index 00000000000..e26d9a80331 --- /dev/null +++ b/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java @@ -0,0 +1,53 @@ +/* + * 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 android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import androidx.annotation.VisibleForTesting; + +public class BubbleSummaryNotificationPreferenceController extends BasePreferenceController { + + @VisibleForTesting + static final int ON = 1; + + public BubbleSummaryNotificationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public CharSequence getSummary() { + return mContext.getString( + areBubblesEnabled() ? R.string.switch_on_text : R.string.switch_off_text); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + private boolean areBubblesEnabled() { + return Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, ON) == ON; + } +} diff --git a/src/com/android/settings/notification/BubbleSummaryPreferenceController.java b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java new file mode 100644 index 00000000000..708bbcded21 --- /dev/null +++ b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java @@ -0,0 +1,99 @@ +/* + * 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 android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.applications.AppInfoBase; +import com.android.settings.core.SubSettingLauncher; + +import androidx.preference.Preference; + +public class BubbleSummaryPreferenceController extends NotificationPreferenceController { + + private static final String KEY = "bubble_link_pref"; + private static final int SYSTEM_WIDE_ON = 1; + private static final int SYSTEM_WIDE_OFF = 0; + + public BubbleSummaryPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mAppRow == null && mChannel == null) { + return false; + } + if (Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) { + return false; + } + if (mChannel != null) { + if (isDefaultChannel()) { + return true; + } else { + return mAppRow == null ? false : mAppRow.allowBubbles; + } + } + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + if (mAppRow != null) { + Bundle args = new Bundle(); + args.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg); + args.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid); + + preference.setIntent(new SubSettingLauncher(mContext) + .setDestination(AppBubbleNotificationSettings.class.getName()) + .setArguments(args) + .setSourceMetricsCategory( + SettingsEnums.NOTIFICATION_APP_NOTIFICATION) + .toIntent()); + } + } + + @Override + public CharSequence getSummary() { + boolean canBubble = false; + if (mAppRow != null) { + if (mChannel != null) { + canBubble |= mChannel.canBubble(); + } else { + canBubble |= mAppRow.allowBubbles; + } + } + return mContext.getString(canBubble ? R.string.switch_on_text : R.string.switch_off_text); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java index 99787d824bc..6d13798bd27 100644 --- a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java @@ -23,6 +23,7 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -223,6 +224,7 @@ public class BubblePreferenceControllerTest { @Test public void testUpdateState_app() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.label = "App!"; appRow.allowBubbles = true; mController.onResume(appRow, null, null, null); @@ -235,6 +237,9 @@ public class BubblePreferenceControllerTest { mController.updateState(pref); assertFalse(pref.isChecked()); + + assertNotNull(pref.getSummary()); + assertTrue(pref.getSummary().toString().contains(appRow.label)); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java new file mode 100644 index 00000000000..4bdb2cca72a --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java @@ -0,0 +1,70 @@ +/* + * 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 android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import static com.android.settings.notification.BadgingNotificationPreferenceController.OFF; +import static com.android.settings.notification.BadgingNotificationPreferenceController.ON; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.provider.Settings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import androidx.preference.Preference; + +@RunWith(RobolectricTestRunner.class) +public class BubbleSummaryNotificationPreferenceControllerTest { + + private Context mContext; + + private BubbleSummaryNotificationPreferenceController mController; + private Preference mPreference; + + private static final String KEY_NOTIFICATION_BUBBLES = "notification_bubbles"; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mController = new BubbleSummaryNotificationPreferenceController(mContext, + KEY_NOTIFICATION_BUBBLES); + mPreference = new Preference(RuntimeEnvironment.application); + } + + @Test + public void display_shouldDisplay() { + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test + public void getSummary() { + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF); + + assertThat(mController.getSummary()).isEqualTo("Off"); + + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON); + + assertThat(mController.getSummary()).isEqualTo("On"); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java new file mode 100644 index 00000000000..5158e82e3b4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java @@ -0,0 +1,149 @@ +/* + * 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 android.app.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import static junit.framework.TestCase.assertEquals; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; + +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +@RunWith(RobolectricTestRunner.class) +public class BubbleSummaryPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + + private BubbleSummaryPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = RuntimeEnvironment.application; + mController = spy(new BubbleSummaryPreferenceController(mContext, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_notIfAppBlocked() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, mock(NotificationChannel.class), null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfOffGlobally() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0); + + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_app() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + mController.onResume(appRow, null, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1); + + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_defaultChannel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1); + + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + mController.onResume(appRow, null, null, null); + + Preference pref = new Preference(mContext); + mController.updateState(pref); + assertNotNull(pref.getIntent()); + } + + @Test + public void testGetSummary() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + mController.onResume(appRow, null, null, null); + + assertEquals("On", mController.getSummary()); + + appRow.allowBubbles = false; + mController.onResume(appRow, null, null, null); + + assertEquals("Off", mController.getSummary()); + } +}