Merge "Add bubble confirmation prompt" into qt-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ce156b5..4e1b32e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7788,9 +7788,9 @@
<!-- Title of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=NONE] -->
<string name="bubbles_feature_disabled_dialog_title">Turn on bubbles</string>
<!-- Description of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=NONE] -->
- <string name="bubbles_feature_disabled_dialog_text">Before you can turn on bubbles for this app, you need to turn on bubbles for your device</string>
+ <string name="bubbles_feature_disabled_dialog_text">To turn on bubbles for this app, first you need to turn them on for your device. This affects other apps in which you previously turned on bubbles.</string>
<!-- Button of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=60]-->
- <string name="bubbles_feature_disabled_button_go_to_bubbles">Go to Bubbles</string>
+ <string name="bubbles_feature_disabled_button_approve">Turn on for device</string>
<!-- Button to cancel out of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=60] -->
<string name="bubbles_feature_disabled_button_cancel">Cancel</string>
diff --git a/src/com/android/settings/notification/AppBubbleNotificationSettings.java b/src/com/android/settings/notification/AppBubbleNotificationSettings.java
index 17909c0..f55c262 100644
--- a/src/com/android/settings/notification/AppBubbleNotificationSettings.java
+++ b/src/com/android/settings/notification/AppBubbleNotificationSettings.java
@@ -18,22 +18,23 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
@SearchIndexable
-public class AppBubbleNotificationSettings extends NotificationSettingsBase {
+public class AppBubbleNotificationSettings extends NotificationSettingsBase implements
+ GlobalBubblePermissionObserverMixin.Listener {
private static final String TAG = "AppBubNotiSettings";
+ private GlobalBubblePermissionObserverMixin mObserverMixin;
@Override
public int getMetricsCategory() {
@@ -65,6 +66,11 @@
}
@Override
+ public void onGlobalBubblePermissionChanged() {
+ updatePreferenceStates();
+ }
+
+ @Override
public void onResume() {
super.onResume();
@@ -79,19 +85,23 @@
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
+
+ mObserverMixin = new GlobalBubblePermissionObserverMixin(getContext(), this);
+ mObserverMixin.onStart();
}
- /**
- * For Search.
- */
- public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ @Override
+ public void onPause() {
+ mObserverMixin.onStop();
+ super.onPause();
+ }
+
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
+
@Override
- public List<SearchIndexableResource> getXmlResourcesToIndex(
- Context context, boolean enabled) {
- final SearchIndexableResource sir = new SearchIndexableResource(context);
- sir.xmlResId = R.xml.app_bubble_notification_settings;
- return Arrays.asList(sir);
+ protected boolean isPageSearchEnabled(Context context) {
+ return false;
}
@Override
diff --git a/src/com/android/settings/notification/BubblePreferenceController.java b/src/com/android/settings/notification/BubblePreferenceController.java
index 5dab374..e5a1a62 100644
--- a/src/com/android/settings/notification/BubblePreferenceController.java
+++ b/src/com/android/settings/notification/BubblePreferenceController.java
@@ -25,6 +25,7 @@
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.RestrictedSwitchPreference;
+import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
public class BubblePreferenceController extends NotificationPreferenceController
@@ -35,10 +36,18 @@
private static final int SYSTEM_WIDE_ON = 1;
private static final int SYSTEM_WIDE_OFF = 0;
+ private FragmentManager mFragmentManager;
+
public BubblePreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
+ public BubblePreferenceController(Context context, FragmentManager fragmentManager,
+ NotificationBackend backend) {
+ super(context, backend);
+ mFragmentManager = fragmentManager;
+ }
+
@Override
public String getPreferenceKey() {
return KEY;
@@ -52,11 +61,11 @@
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 (Settings.Secure.getInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
+ return false;
+ }
if (isDefaultChannel()) {
return true;
} else {
@@ -74,7 +83,9 @@
pref.setChecked(mChannel.canBubble());
pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin());
} else {
- pref.setChecked(mAppRow.allowBubbles);
+ pref.setChecked(mAppRow.allowBubbles
+ && Settings.Secure.getInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_ON);
pref.setSummary(mContext.getString(
R.string.bubbles_app_toggle_summary, mAppRow.label));
}
@@ -87,11 +98,44 @@
if (mChannel != null) {
mChannel.setAllowBubbles(value);
saveChannel();
- } else if (mAppRow != null){
- mAppRow.allowBubbles = value;
- mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
+ return true;
+ } else if (mAppRow != null) {
+ RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
+ // if the global setting is off, toggling app level permission requires extra
+ // confirmation
+ if (Settings.Secure.getInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF
+ && !pref.isChecked()) {
+ new BubbleWarningDialogFragment()
+ .setPkgInfo(mAppRow.pkg, mAppRow.uid)
+ .show(mFragmentManager, "dialog");
+ return false;
+ } else {
+ mAppRow.allowBubbles = value;
+ mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
+ }
}
return true;
}
+ // Used in app level prompt that confirms the user is ok with turning on bubbles
+ // globally. If they aren't, undo what
+ public static void revertBubblesApproval(Context mContext, String pkg, int uid) {
+ NotificationBackend backend = new NotificationBackend();
+ backend.setAllowBubbles(pkg, uid, false);
+ // changing the global settings will cause the observer on the host page to reload
+ // correct preference state
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
+ }
+
+ // Apply global bubbles approval
+ public static void applyBubblesApproval(Context mContext, String pkg, int uid) {
+ NotificationBackend backend = new NotificationBackend();
+ backend.setAllowBubbles(pkg, uid, true);
+ // changing the global settings will cause the observer on the host page to reload
+ // correct preference state
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
+ }
}
diff --git a/src/com/android/settings/notification/BubbleSummaryPreferenceController.java b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java
index 708bbcd..5f58f67 100644
--- a/src/com/android/settings/notification/BubbleSummaryPreferenceController.java
+++ b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java
@@ -52,11 +52,11 @@
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 (Settings.Secure.getInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
+ return false;
+ }
if (isDefaultChannel()) {
return true;
} else {
@@ -91,7 +91,9 @@
if (mChannel != null) {
canBubble |= mChannel.canBubble();
} else {
- canBubble |= mAppRow.allowBubbles;
+ canBubble |= mAppRow.allowBubbles
+ && (Settings.Secure.getInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_ON);
}
}
return mContext.getString(canBubble ? R.string.switch_on_text : R.string.switch_off_text);
diff --git a/src/com/android/settings/notification/BubbleWarningDialogFragment.java b/src/com/android/settings/notification/BubbleWarningDialogFragment.java
new file mode 100644
index 0000000..5086fb0
--- /dev/null
+++ b/src/com/android/settings/notification/BubbleWarningDialogFragment.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 android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
+ static final String KEY_PKG = "p";
+ static final String KEY_UID = "u";
+
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DIALOG_APP_BUBBLE_SETTINGS;
+ }
+
+ public BubbleWarningDialogFragment setPkgInfo(String pkg, int uid) {
+ Bundle args = new Bundle();
+ args.putString(KEY_PKG, pkg);
+ args.putInt(KEY_UID, uid);
+ setArguments(args);
+ return this;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Bundle args = getArguments();
+ final String pkg = args.getString(KEY_PKG);
+ final int uid = args.getInt(KEY_UID);
+
+ final String title =
+ getResources().getString(R.string.bubbles_feature_disabled_dialog_title);
+ final String summary = getResources()
+ .getString(R.string.bubbles_feature_disabled_dialog_text);
+ return new AlertDialog.Builder(getContext())
+ .setMessage(summary)
+ .setTitle(title)
+ .setCancelable(true)
+ .setPositiveButton(R.string.bubbles_feature_disabled_button_approve,
+ (dialog, id) ->
+ BubblePreferenceController.applyBubblesApproval(
+ getContext(), pkg, uid))
+ .setNegativeButton(R.string.bubbles_feature_disabled_button_cancel,
+ (dialog, id) ->
+ BubblePreferenceController.revertBubblesApproval(
+ getContext(), pkg, uid))
+ .create();
+ }
+}
diff --git a/src/com/android/settings/notification/GlobalBubblePermissionObserverMixin.java b/src/com/android/settings/notification/GlobalBubblePermissionObserverMixin.java
new file mode 100644
index 0000000..398931d
--- /dev/null
+++ b/src/com/android/settings/notification/GlobalBubblePermissionObserverMixin.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+public class GlobalBubblePermissionObserverMixin extends ContentObserver {
+
+ public interface Listener {
+ void onGlobalBubblePermissionChanged();
+ }
+
+ private final Context mContext;
+ private final Listener mListener;
+
+ public GlobalBubblePermissionObserverMixin(Context context, Listener listener) {
+ super(new Handler(Looper.getMainLooper()));
+ mContext = context;
+ mListener = listener;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mListener != null) {
+ mListener.onGlobalBubblePermissionChanged();
+ }
+ }
+
+ public void onStart() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.NOTIFICATION_BUBBLES),
+ false /* notifyForDescendants */,
+ this /* observer */);
+ }
+
+ public void onStop() {
+ mContext.getContentResolver().unregisterContentObserver(this /* observer */);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/HeaderPreferenceController.java b/src/com/android/settings/notification/HeaderPreferenceController.java
index 0c091b4..d942113 100644
--- a/src/com/android/settings/notification/HeaderPreferenceController.java
+++ b/src/com/android/settings/notification/HeaderPreferenceController.java
@@ -68,9 +68,13 @@
activity = mFragment.getActivity();
}
+ if (activity == null) {
+ return;
+ }
+
LayoutPreference pref = (LayoutPreference) preference;
mHeaderController = EntityHeaderController.newInstance(
- mFragment.getActivity(), mFragment, pref.findViewById(R.id.entity_header));
+ activity, mFragment, pref.findViewById(R.id.entity_header));
pref = mHeaderController.setIcon(mAppRow.icon)
.setLabel(getLabel())
.setSummary(getSummary())
diff --git a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java
index 6d13798..54bbd08 100644
--- a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -53,6 +54,8 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -68,6 +71,8 @@
private UserManager mUm;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PreferenceScreen mScreen;
+ @Mock
+ private FragmentManager mFragmentManager;
private BubblePreferenceController mController;
@@ -78,7 +83,8 @@
shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
shadowApplication.setSystemService(Context.USER_SERVICE, mUm);
mContext = RuntimeEnvironment.application;
- mController = spy(new BubblePreferenceController(mContext, mBackend));
+ when(mFragmentManager.beginTransaction()).thenReturn(mock(FragmentTransaction.class));
+ mController = spy(new BubblePreferenceController(mContext, mFragmentManager, mBackend));
}
@Test
@@ -117,7 +123,16 @@
}
@Test
- public void testIsAvailable_notIfOffGlobally() {
+ public void testIsAvailable_ifOffGlobally_app() {
+ NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+ mController.onResume(appRow, null, null, null);
+ Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
+ public void testIsAvailable_notIfOffGlobally_channel() {
NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
NotificationChannel channel = mock(NotificationChannel.class);
when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
@@ -243,6 +258,19 @@
}
@Test
+ public void testUpdateState_app_offGlobally() {
+ Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+ NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+ appRow.label = "App!";
+ appRow.allowBubbles = true;
+ mController.onResume(appRow, null, null, null);
+
+ RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
+ mController.updateState(pref);
+ assertFalse(pref.isChecked());
+ }
+
+ @Test
public void testOnPreferenceChange_on_channel() {
NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
appRow.allowBubbles = true;
@@ -313,4 +341,23 @@
assertFalse(appRow.allowBubbles);
verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(false));
}
+
+ @Test
+ public void testOnPreferenceChange_on_app_offGlobally() {
+ Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+ NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+ appRow.allowBubbles = false;
+ mController.onResume(appRow, null, null, null);
+
+ RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
+ when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
+ mController.displayPreference(mScreen);
+ mController.updateState(pref);
+
+ mController.onPreferenceChange(pref, true);
+
+ assertFalse(appRow.allowBubbles);
+ verify(mBackend, never()).setAllowBubbles(any(), anyInt(), eq(true));
+ verify(mFragmentManager, times(1)).beginTransaction();
+ }
}
diff --git a/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java
index 5158e82..0a0addc 100644
--- a/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java
@@ -110,6 +110,15 @@
}
@Test
+ public void testIsAvailable_app_globalOff() {
+ NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+ mController.onResume(appRow, null, null, null);
+ Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
public void testIsAvailable_defaultChannel() {
NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
appRow.allowBubbles = true;
@@ -141,6 +150,10 @@
assertEquals("On", mController.getSummary());
+ Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+ assertEquals("Off", mController.getSummary());
+
+ Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1);
appRow.allowBubbles = false;
mController.onResume(appRow, null, null, null);