From 0c45775a5d9558b447b7f0784b8b800aa30dc887 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Thu, 31 Jan 2019 13:13:30 -0500 Subject: [PATCH 01/10] Add settings for notification bubbling Globally, as well as at the app and channel level Test: atest Bug: 123543052 Change-Id: I1668b592c5d175d77d6a38f35e7576d641175d44 --- res/values/strings.xml | 3 + res/xml/app_notification_settings.xml | 7 +- res/xml/channel_notification_settings.xml | 10 +- res/xml/configure_notification_settings.xml | 6 + .../notification/AppNotificationSettings.java | 1 + .../BadgePreferenceController.java | 2 +- ...ubbleNotificationPreferenceController.java | 124 +++++++ .../BubblePreferenceController.java | 94 ++++++ .../ChannelNotificationSettings.java | 1 + .../notification/NotificationBackend.java | 22 ++ ...eNotificationPreferenceControllerTest.java | 136 ++++++++ .../BubblePreferenceControllerTest.java | 311 ++++++++++++++++++ 12 files changed, 713 insertions(+), 4 deletions(-) create mode 100644 src/com/android/settings/notification/BubbleNotificationPreferenceController.java create mode 100644 src/com/android/settings/notification/BubblePreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/BubbleNotificationPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 039fb13e070..c0526d8da26 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7617,6 +7617,9 @@ Allow notification dots + + Allow notification bubbles + Blink light diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml index 49d64a9731d..54d6fe7dcaf 100644 --- a/res/xml/app_notification_settings.xml +++ b/res/xml/app_notification_settings.xml @@ -60,9 +60,14 @@ settings:useAdditionalSummary="true" android:order="1001" settings:restrictedSwitchSummary="@string/enabled_by_admin" /> + diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml index 9e9e7a96780..94a2cdb3d2c 100644 --- a/res/xml/channel_notification_settings.xml +++ b/res/xml/channel_notification_settings.xml @@ -86,17 +86,23 @@ settings:useAdditionalSummary="true" settings:restrictedSwitchSummary="@string/enabled_by_admin"/> + + diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index ca826095f08..8df1197e997 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -38,6 +38,12 @@ android:title="@string/notification_badging_title" settings:controller="com.android.settings.notification.BadgingNotificationPreferenceController"/> + + + (mControllers); } diff --git a/src/com/android/settings/notification/BadgePreferenceController.java b/src/com/android/settings/notification/BadgePreferenceController.java index ad417b0ad6a..67332a44bb4 100644 --- a/src/com/android/settings/notification/BadgePreferenceController.java +++ b/src/com/android/settings/notification/BadgePreferenceController.java @@ -60,7 +60,7 @@ public class BadgePreferenceController extends NotificationPreferenceController if (isDefaultChannel()) { return true; } else { - return mAppRow.showBadge; + return mAppRow == null ? false : mAppRow.showBadge; } } return true; diff --git a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java new file mode 100644 index 00000000000..caba7d92cd9 --- /dev/null +++ b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java @@ -0,0 +1,124 @@ +/* + * 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 android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; +import android.text.TextUtils; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +public class BubbleNotificationPreferenceController extends TogglePreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener, + LifecycleObserver, OnResume, OnPause { + + private static final String TAG = "BubbleNotifPrefContr"; + @VisibleForTesting + static final int ON = 1; + @VisibleForTesting + static final int OFF = 0; + + private SettingObserver mSettingObserver; + + public BubbleNotificationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + Preference preference = screen.findPreference(NOTIFICATION_BUBBLES); + if (preference != null) { + mSettingObserver = new SettingObserver(preference); + } + } + + @Override + public void onResume() { + if (mSettingObserver != null) { + mSettingObserver.register(mContext.getContentResolver(), true /* register */); + } + } + + @Override + public void onPause() { + if (mSettingObserver != null) { + mSettingObserver.register(mContext.getContentResolver(), false /* register */); + } + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isChecked() { + return Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, ON) == ON; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.Secure.putInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, isChecked ? ON : OFF); + } + + class SettingObserver extends ContentObserver { + + private final Uri NOTIFICATION_BUBBLES_URI = + Settings.Secure.getUriFor(NOTIFICATION_BUBBLES); + + private final Preference mPreference; + + public SettingObserver(Preference preference) { + super(new Handler()); + mPreference = preference; + } + + public void register(ContentResolver cr, boolean register) { + if (register) { + cr.registerContentObserver(NOTIFICATION_BUBBLES_URI, false, this); + } else { + cr.unregisterContentObserver(this); + } + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + if (NOTIFICATION_BUBBLES_URI.equals(uri)) { + updateState(mPreference); + } + } + } +} diff --git a/src/com/android/settings/notification/BubblePreferenceController.java b/src/com/android/settings/notification/BubblePreferenceController.java new file mode 100644 index 00000000000..e61de4ba40f --- /dev/null +++ b/src/com/android/settings/notification/BubblePreferenceController.java @@ -0,0 +1,94 @@ +/* + * 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.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; + +import androidx.preference.Preference; + +public class BubblePreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String TAG = "BubblePrefContr"; + private static final String KEY = "bubble"; + private static final int SYSTEM_WIDE_ON = 1; + private static final int SYSTEM_WIDE_OFF = 0; + + public BubblePreferenceController(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; + } + + public void updateState(Preference preference) { + if (mAppRow != null) { + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setDisabledByAdmin(mAdmin); + if (mChannel != null) { + pref.setChecked(mChannel.canBubble()); + pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin()); + } else { + pref.setChecked(mAppRow.allowBubbles); + } + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean value = (Boolean) newValue; + if (mChannel != null) { + mChannel.setAllowBubbles(value); + saveChannel(); + } else if (mAppRow != null){ + mAppRow.allowBubbles = value; + mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value); + } + return true; + } + +} diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java index 0e1f386f4db..f92e529aab1 100644 --- a/src/com/android/settings/notification/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelNotificationSettings.java @@ -110,6 +110,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { mControllers.add(new BadgePreferenceController(context, mBackend)); mControllers.add(new DndPreferenceController(context, mBackend)); mControllers.add(new NotificationsOffPreferenceController(context)); + mControllers.add(new BubblePreferenceController(context, mBackend)); return new ArrayList<>(mControllers); } } diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index ec563698be1..dbba6161d5a 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -71,6 +71,7 @@ public class NotificationBackend { row.icon = IconDrawableFactory.newInstance(context).getBadgedIcon(app); row.banned = getNotificationsBanned(row.pkg, row.uid); row.showBadge = canShowBadge(row.pkg, row.uid); + row.allowBubbles = canBubble(row.pkg, row.uid); row.userId = UserHandle.getUserId(row.uid); row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid); row.channelCount = getChannelCount(row.pkg, row.uid); @@ -175,6 +176,26 @@ public class NotificationBackend { } } + public boolean canBubble(String pkg, int uid) { + try { + return sINM.areBubblesAllowedForPackage(pkg, uid); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public boolean setAllowBubbles(String pkg, int uid, boolean allow) { + try { + sINM.setBubblesAllowed(pkg, uid, allow); + return true; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public NotificationChannel getChannel(String pkg, int uid, String channelId) { if (channelId == null) { return null; @@ -416,6 +437,7 @@ public class NotificationBackend { public boolean lockedImportance; public String lockedChannelId; public boolean showBadge; + public boolean allowBubbles; public int userId; public int blockedChannelCount; public int channelCount; diff --git a/tests/robotests/src/com/android/settings/notification/BubbleNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleNotificationPreferenceControllerTest.java new file mode 100644 index 00000000000..c4587afe132 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BubbleNotificationPreferenceControllerTest.java @@ -0,0 +1,136 @@ +/* + * 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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; + +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 androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.TwoStatePreference; + +@RunWith(RobolectricTestRunner.class) +public class BubbleNotificationPreferenceControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private BubbleNotificationPreferenceController mController; + private Preference mPreference; + + private static final String KEY_NOTIFICATION_BUBBLES = "notification_bubbles"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = new BubbleNotificationPreferenceController(mContext, + KEY_NOTIFICATION_BUBBLES); + mPreference = new Preference(RuntimeEnvironment.application); + mPreference.setKey(mController.getPreferenceKey()); + when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference); + } + + @Test + public void display_shouldDisplay() { + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test + public void updateState_preferenceSetCheckedWhenSettingIsOn() { + final TwoStatePreference preference = mock(TwoStatePreference.class); + final Context context = RuntimeEnvironment.application; + Settings.Secure.putInt(context.getContentResolver(), NOTIFICATION_BUBBLES, ON); + + mController.updateState(preference); + + verify(preference).setChecked(true); + } + + @Test + public void updateState_preferenceSetUncheckedWhenSettingIsOff() { + final TwoStatePreference preference = mock(TwoStatePreference.class); + final Context context = RuntimeEnvironment.application; + Settings.Secure.putInt(context.getContentResolver(), NOTIFICATION_BUBBLES, OFF); + + mController.updateState(preference); + + verify(preference).setChecked(false); + } + + @Test + public void isChecked_settingIsOff_shouldReturnFalse() { + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_settingIsOn_shouldReturnTrue() { + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void setChecked_setFalse_disablesSetting() { + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON); + + mController.setChecked(false); + int updatedValue = Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, -1); + + assertThat(updatedValue).isEqualTo(OFF); + } + + @Test + public void setChecked_setTrue_enablesSetting() { + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF); + + mController.setChecked(true); + int updatedValue = Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, -1); + + assertThat(updatedValue).isEqualTo(ON); + } + + @Test + public void isSliceable_returnsFalse() { + assertThat(mController.isSliceable()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java new file mode 100644 index 00000000000..99787d824bc --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java @@ -0,0 +1,311 @@ +/* + * 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 org.junit.Assert.assertFalse; +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 BubblePreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private BubblePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = RuntimeEnvironment.application; + mController = spy(new BubblePreferenceController(mContext, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() { + mController.isAvailable(); + mController.updateState(mock(RestrictedSwitchPreference.class)); + mController.onPreferenceChange(mock(RestrictedSwitchPreference.class), true); + } + + @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_notIfChannelBlocked() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_channel_notIfAppOff() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = false; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, 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 testIsAvailable_channel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1); + + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_channelAppOff() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = false; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1); + + assertFalse(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, + mock(RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_channelNotConfigurable() { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_channel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = "a"; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.canBubble()).thenReturn(true); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + + assertTrue(pref.isChecked()); + + when(channel.canBubble()).thenReturn(false); + mController.onResume(appRow, channel, null, null); + mController.updateState(pref); + + assertFalse(pref.isChecked()); + } + + @Test + public void testUpdateState_app() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + mController.onResume(appRow, null, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + assertTrue(pref.isChecked()); + + appRow.allowBubbles = false; + mController.onResume(appRow, null, null, null); + + mController.updateState(pref); + assertFalse(pref.isChecked()); + } + + @Test + public void testOnPreferenceChange_on_channel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW); + channel.setAllowBubbles(false); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + assertTrue(channel.canBubble()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testOnPreferenceChange_off_channel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + channel.setAllowBubbles(true); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, false); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + assertFalse(channel.canBubble()); + } + + @Test + public void testOnPreferenceChange_on_app() { + 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); + + assertTrue(appRow.allowBubbles); + verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(true)); + } + + @Test + public void testOnPreferenceChange_off_app() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + 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, false); + + assertFalse(appRow.allowBubbles); + verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(false)); + } +} From f6d3ac28934360c2b4105fea44baa1a6637f63e4 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Thu, 31 Jan 2019 16:21:26 -0800 Subject: [PATCH 02/10] Add an on/off switch to the top of mobile network details page When a device is in DSDS mode and has two mobile network subscriptions, a user may want to completely disable one of those subscriptions. This implements the UI for this, calling into some recently added APIs in the SubscriptionManager class. Bug: 122670283 Test: make RunSettingsRoboTests Change-Id: I40fd3e777ce3542004e4761406b24196505b97d8 --- res/values/strings.xml | 6 + res/xml/mobile_network_settings_v2.xml | 7 +- .../telephony/MobileNetworkSettings.java | 1 + .../MobileNetworkSwitchController.java | 128 ++++++++++++++++ .../MobileNetworkSwitchControllerTest.java | 140 ++++++++++++++++++ 5 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/network/telephony/MobileNetworkSwitchController.java create mode 100644 tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 039fb13e070..52990aca327 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10466,6 +10466,12 @@ Rename + + Use SIM + + Off Preferred network type diff --git a/res/xml/mobile_network_settings_v2.xml b/res/xml/mobile_network_settings_v2.xml index 6273ad91e42..f269fade5c9 100644 --- a/res/xml/mobile_network_settings_v2.xml +++ b/res/xml/mobile_network_settings_v2.xml @@ -17,7 +17,12 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="mobile_network_pref_screen" - settings:initialExpandedChildrenCount="7"> + settings:initialExpandedChildrenCount="8"> + + { + if (mSubscriptionManager.isSubscriptionEnabled(mSubId) != isChecked) { + mSubscriptionManager.setSubscriptionEnabled(mSubId, isChecked); + } + }); + update(); + } + + private void update() { + if (mSwitchBar == null) { + return; + } + final List subs = SubscriptionUtil.getAvailableSubscriptions( + mSubscriptionManager); + if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID || subs.size() < 2) { + mSwitchBar.hide(); + return; + } + + for (SubscriptionInfo info : subs) { + if (info.getSubscriptionId() == mSubId) { + mSwitchBar.show(); + mSwitchBar.setChecked(mSubscriptionManager.isSubscriptionEnabled(mSubId)); + return; + } + } + // This subscription was not found in the available list. + mSwitchBar.hide(); + } + + @Override + public int getAvailabilityStatus() { + if (FeatureFlagPersistent.isEnabled(mContext, FeatureFlags.NETWORK_INTERNET_V2)) { + return AVAILABLE_UNSEARCHABLE; + } else { + return CONDITIONALLY_UNAVAILABLE; + } + } + + @Override + public void onAirplaneModeChanged(boolean airplaneModeEnabled) {} + + @Override + public void onSubscriptionsChanged() { + update(); + } +} diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java new file mode 100644 index 00000000000..2a596d971a6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java @@ -0,0 +1,140 @@ +/* + * 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.network.telephony; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; + +import com.android.settings.R; +import com.android.settings.network.SubscriptionUtil; +import com.android.settings.widget.SwitchBar; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.LayoutPreference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.Arrays; + +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.PreferenceScreen; + +@RunWith(RobolectricTestRunner.class) +public class MobileNetworkSwitchControllerTest { + @Mock + private SubscriptionManager mSubscriptionManager; + @Mock + private PreferenceScreen mScreen; + @Mock + private LayoutPreference mLayoutPreference; + @Mock + private SubscriptionInfo mSubscription; + + private Context mContext; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; + private MobileNetworkSwitchController mController; + private SwitchBar mSwitchBar; + private int mSubId = 123; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); + + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + + when(mSubscription.getSubscriptionId()).thenReturn(mSubId); + // Most tests want to have 2 available subscriptions so that the switch bar will show. + SubscriptionInfo sub2 = mock(SubscriptionInfo.class); + when(sub2.getSubscriptionId()).thenReturn(456); + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription, sub2)); + + String key = "prefKey"; + mController = new MobileNetworkSwitchController(mContext, key); + mController.init(mLifecycle, mSubscription.getSubscriptionId()); + + mSwitchBar = new SwitchBar(mContext); + when(mScreen.findPreference(key)).thenReturn(mLayoutPreference); + when(mLayoutPreference.findViewById(R.id.switch_bar)).thenReturn(mSwitchBar); + } + + @After + public void cleanUp() { + SubscriptionUtil.setAvailableSubscriptionsForTesting(null); + } + + @Test + public void displayPreference_onlyOneSubscription_switchBarHidden() { + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription)); + mController.displayPreference(mScreen); + assertThat(mSwitchBar.isShowing()).isFalse(); + } + + @Test + public void displayPreference_subscriptionEnabled_switchIsOn() { + when(mSubscriptionManager.isSubscriptionEnabled(mSubId)).thenReturn(true); + mController.displayPreference(mScreen); + assertThat(mSwitchBar.isShowing()).isTrue(); + assertThat(mSwitchBar.isChecked()).isTrue(); + } + + @Test + public void displayPreference_subscriptionDisabled_switchIsOff() { + when(mSubscriptionManager.isSubscriptionEnabled(mSubId)).thenReturn(false); + mController.displayPreference(mScreen); + assertThat(mSwitchBar.isShowing()).isTrue(); + assertThat(mSwitchBar.isChecked()).isFalse(); + } + + @Test + public void switchChangeListener_fromEnabledToDisabled_setSubscriptionEnabledCalledCorrectly() { + when(mSubscriptionManager.isSubscriptionEnabled(mSubId)).thenReturn(true); + mController.displayPreference(mScreen); + assertThat(mSwitchBar.isShowing()).isTrue(); + assertThat(mSwitchBar.isChecked()).isTrue(); + mSwitchBar.setChecked(false); + verify(mSubscriptionManager).setSubscriptionEnabled(eq(mSubId), eq(false)); + } + + @Test + public void switchChangeListener_fromDisabledToEnabled_setSubscriptionEnabledCalledCorrectly() { + when(mSubscriptionManager.isSubscriptionEnabled(mSubId)).thenReturn(false); + mController.displayPreference(mScreen); + assertThat(mSwitchBar.isShowing()).isTrue(); + assertThat(mSwitchBar.isChecked()).isFalse(); + mSwitchBar.setChecked(true); + verify(mSubscriptionManager).setSubscriptionEnabled(eq(mSubId), eq(true)); + } +} From 5042c9b41bee3bd1dcce4f2b25160e9fdb158eca Mon Sep 17 00:00:00 2001 From: Edward Savage-Jones Date: Wed, 25 Jan 2017 00:44:18 +0100 Subject: [PATCH 03/10] Add master audio balance setting This adds an audio balance slider to accessibility allowing the end user to adjust the audio balance between left and right channels. Test: Change Balance through Settings, play audio Bug: 28390736 Change-Id: I70f22d222ea310fd33ebe4f3fb054f44f69d2548 --- res/layout/preference_balance_slider.xml | 109 +++++++++++++ res/values/dimens.xml | 3 + res/values/strings.xml | 6 + res/xml/accessibility_settings.xml | 4 + .../accessibility/BalanceSeekBar.java | 152 ++++++++++++++++++ .../BalanceSeekBarPreference.java | 70 ++++++++ 6 files changed, 344 insertions(+) create mode 100644 res/layout/preference_balance_slider.xml create mode 100644 src/com/android/settings/accessibility/BalanceSeekBar.java create mode 100644 src/com/android/settings/accessibility/BalanceSeekBarPreference.java diff --git a/res/layout/preference_balance_slider.xml b/res/layout/preference_balance_slider.xml new file mode 100644 index 00000000000..32010c36026 --- /dev/null +++ b/res/layout/preference_balance_slider.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 0a35188287e..d42f0731d98 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -38,6 +38,9 @@ 8dip + 14dp + 1dp + 100sp 3dip diff --git a/res/values/strings.xml b/res/values/strings.xml index 039fb13e070..c5562c61c07 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4717,6 +4717,12 @@ Mono audio Combine channels when playing audio + + Audio balance + + Left + + Right Default diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index cc07ce119e9..9cb73a2ea6b 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -130,6 +130,10 @@ android:summary="@string/accessibility_toggle_master_mono_summary" android:persistent="false"/> + + mCenter - mSnapThreshold + && progress < mCenter + mSnapThreshold) { + progress = mCenter; + seekBar.setProgress(progress); // direct update (fromUser becomes false) + } + final float balance = (progress - mCenter) * 0.01f; + Settings.System.putFloatForUser(mContext.getContentResolver(), + Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT); + } + // If fromUser is false, the call is a set from the framework on creation or on + // internal update. The progress may be zero, ignore (don't change system settings). + + // after adjusting the seekbar, notify downstream listener. + // note that progress may have been adjusted in the code above to mCenter. + synchronized(mListenerLock) { + if (mOnSeekBarChangeListener != null) { + mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); + } + } + } + }; + + // Percentage of max to be used as a snap to threshold + private static final float SNAP_TO_PERCENTAGE = 0.03f; + private final Paint mCenterMarkerPaint; + private final Rect mCenterMarkerRect; + // changed in setMax() + private float mSnapThreshold; + private int mCenter; + + public BalanceSeekBar(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.seekBarStyle); + } + + public BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mContext = context; + Resources res = getResources(); + mCenterMarkerRect = new Rect(0 /* left */, 0 /* top */, + res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_width), + res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_height)); + mCenterMarkerPaint = new Paint(); + // TODO use a more suitable colour? + mCenterMarkerPaint.setColor(Color.BLACK); + mCenterMarkerPaint.setStyle(Paint.Style.FILL); + // Remove the progress colour + setProgressTintList(ColorStateList.valueOf(Color.TRANSPARENT)); + + super.setOnSeekBarChangeListener(mProxySeekBarListener); + } + + @Override + public void setOnSeekBarChangeListener(OnSeekBarChangeListener listener) { + synchronized(mListenerLock) { + mOnSeekBarChangeListener = listener; + } + } + + // Note: the superclass AbsSeekBar.setMax is synchronized. + @Override + public synchronized void setMax(int max) { + super.setMax(max); + // update snap to threshold + mCenter = max / 2; + mSnapThreshold = max * SNAP_TO_PERCENTAGE; + } + + // Note: the superclass AbsSeekBar.onDraw is synchronized. + @Override + protected synchronized void onDraw(Canvas canvas) { + // Draw a vertical line at 50% that represents centred balance + int seekBarCenter = (canvas.getHeight() - getPaddingBottom()) / 2; + canvas.save(); + canvas.translate((canvas.getWidth() - mCenterMarkerRect.right) / 2, + seekBarCenter - (mCenterMarkerRect.bottom / 2)); + canvas.drawRect(mCenterMarkerRect, mCenterMarkerPaint); + canvas.restore(); + super.onDraw(canvas); + } +} + diff --git a/src/com/android/settings/accessibility/BalanceSeekBarPreference.java b/src/com/android/settings/accessibility/BalanceSeekBarPreference.java new file mode 100644 index 00000000000..a40282ca929 --- /dev/null +++ b/src/com/android/settings/accessibility/BalanceSeekBarPreference.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.accessibility; + +import android.content.Context; +import android.media.AudioSystem; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.SeekBar; + +import androidx.core.content.res.TypedArrayUtils; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settings.widget.SeekBarPreference; + +/** A slider preference that directly controls audio balance **/ +public class BalanceSeekBarPreference extends SeekBarPreference { + private static final String TAG = "BalanceSeekBarPreference"; + private final Context mContext; + private BalanceSeekBar mSeekBar; + private ImageView mIconView; + + public BalanceSeekBarPreference(Context context, AttributeSet attrs) { + super(context, attrs, TypedArrayUtils.getAttr(context, + R.attr.preferenceStyle, + android.R.attr.preferenceStyle)); + mContext = context; + setLayoutResource(R.layout.preference_balance_slider); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + mSeekBar = (BalanceSeekBar) view.findViewById(com.android.internal.R.id.seekbar); + mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon); + init(); + } + + private void init() { + if (mSeekBar == null) { + return; + } + final float balance = Settings.System.getFloatForUser( + mContext.getContentResolver(), Settings.System.MASTER_BALANCE, + 0.f /* default */, UserHandle.USER_CURRENT); + // Rescale balance to range 0-200 centered at 100. + mSeekBar.setMax(200); + mSeekBar.setProgress((int)(balance * 100.f) + 100); + mSeekBar.setEnabled(isEnabled()); + } +} From 2d77b0192af0e7be6d6231ac7e6348aacc66a798 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 5 Feb 2019 15:23:39 -0800 Subject: [PATCH 04/10] Move android.settings.PRIVACY_SETTINGS to PrivacySettings. Bug: 123878762 Test: manual Change-Id: I9312c8455b0325f7c582523938a1fde21fbd81ce --- AndroidManifest.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dccd1d03bcc..a007f9934db 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1235,6 +1235,10 @@ android:icon="@drawable/ic_settings_backup" android:configChanges="orientation|keyboardHidden|screenSize" android:parentActivityName="Settings"> + + + + @@ -2792,10 +2796,6 @@ - - - - From 82ebf6090dc869d4083105cafea4c1979bb15e4f Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 5 Feb 2019 19:54:59 -0800 Subject: [PATCH 05/10] Update header test to 'Work profile input & assistance' Per suggestion from UX, this CL updates the category header of work-profile specific enties in Languages & input from Work profile to Work profile input & assistance. Other than that, there is no user-visible behavior change. Bug: 123314089 Test: Manually verified as follows. 1. Build and flash an AOSP build 2. Install Test DPC 3. Open Settings -> System -> Languages & input 4. Make sure that the new category header for work-profile specific entries is now shown as "Work profile input & assistance" Change-Id: I4a4fb7c8a31d8d849780e71796b92ec0a2f026e3 --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 59374cf114a..4dab0b895a5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4487,8 +4487,8 @@ Keyboard shortcuts helper Display available shortcuts - - Work profile + + Work profile input & assistance Virtual keyboard for work From e264bf3f6682670f9faaf0b01eb9a342416dace8 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Tue, 5 Feb 2019 18:55:52 -0800 Subject: [PATCH 06/10] Retrieve effectiveUserId after userId is set/re-set Fixes: 123502937 Test: Followed steps in comment #1 of the first bug above Note that the test should be done with unified lock disabled, e.g. have separate pin/pattern/pass for owner and work profile Change-Id: I01d66a8a1d3ed1811497c2acb7db6158d99727a0 --- .../settings/password/ConfirmDeviceCredentialActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java index db0935d08ab..236e3d9a936 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java @@ -157,7 +157,6 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity { boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction()); mUserId = UserHandle.myUserId(); - final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId); if (isInternalActivity()) { try { mUserId = Utils.getUserIdFromBundle(this, intent.getExtras()); @@ -165,6 +164,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity { Log.e(TAG, "Invalid intent extra", se); } } + final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId); final boolean isManagedProfile = UserManager.get(this).isManagedProfile(mUserId); // if the client app did not hand in a title and we are about to show the work challenge, // check whether there is a policy setting the organization name and use that as title From 11482d220f3e8845e3ff07bce902ab9db7bb92e9 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 6 Feb 2019 10:37:03 -0800 Subject: [PATCH 07/10] Add PRIVACY_SETTINGS intent to privacy, not backup, page. Bug: 123878762 Test: updated backupSettingsHelper Change-Id: I53632e4eeb5fa42447aa5fc075be69eeb68cdd52 --- AndroidManifest.xml | 15 +++++++++++---- src/com/android/settings/Settings.java | 1 + .../settings/core/gateway/SettingsGateway.java | 1 + .../backup/BackupSettingsHelperTest.java | 17 +---------------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a007f9934db..90aa55ff1a7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1235,10 +1235,6 @@ android:icon="@drawable/ic_settings_backup" android:configChanges="orientation|keyboardHidden|screenSize" android:parentActivityName="Settings"> - - - - @@ -1250,6 +1246,17 @@ android:value="true" /> + + + + + + + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 8cff5f1d986..e7f543a298e 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -71,6 +71,7 @@ public class Settings extends SettingsActivity { public static class AppUsageAccessSettingsActivity extends SettingsActivity { /* empty */ } public static class LocationSettingsActivity extends SettingsActivity { /* empty */ } public static class ScanningSettingsActivity extends SettingsActivity { /* empty */ } + public static class PrivacyDashboardActivity extends SettingsActivity { /* empty */ } public static class PrivacySettingsActivity extends SettingsActivity { /* empty */ } public static class FactoryResetActivity extends SettingsActivity { /* empty */ } public static class RunningServicesActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index bb292a154f2..fb3d0c55ae7 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -274,6 +274,7 @@ public class SettingsGateway { Settings.StorageDashboardActivity.class.getName(), Settings.PowerUsageSummaryActivity.class.getName(), Settings.AccountDashboardActivity.class.getName(), + Settings.PrivacySettingsActivity.class.getName(), Settings.SecurityDashboardActivity.class.getName(), Settings.AccessibilitySettingsActivity.class.getName(), Settings.SystemDashboardActivity.class.getName(), diff --git a/tests/robotests/src/com/android/settings/backup/BackupSettingsHelperTest.java b/tests/robotests/src/com/android/settings/backup/BackupSettingsHelperTest.java index ee8912af56f..55877832772 100644 --- a/tests/robotests/src/com/android/settings/backup/BackupSettingsHelperTest.java +++ b/tests/robotests/src/com/android/settings/backup/BackupSettingsHelperTest.java @@ -31,16 +31,13 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.UserInfo; import android.content.res.Resources; import android.os.IBinder; import android.os.RemoteException; - import android.os.UserHandle; -import android.os.UserManager; + import com.android.settings.R; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,8 +54,6 @@ import org.robolectric.shadows.ShadowUserManager; @RunWith(RobolectricTestRunner.class) @Config(shadows = BackupSettingsHelperTest.ShadowBackupManagerStub.class) public class BackupSettingsHelperTest { - private static final String DEFAULT_SETTINGS_CLASSNAME = - "com.android.settings.Settings$PrivacySettingsActivity"; private static final int DEFAULT_SUMMARY_RESOURCE = R.string.backup_configure_account_default_summary; @@ -294,16 +289,6 @@ public class BackupSettingsHelperTest { assertThat(backupIntent).isEqualTo(intent); } - @Test - public void testGetIntentForBackupSettings_WithoutIntentFromTransport() throws Exception { - when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(null); - - Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings(); - - assertThat(backupIntent.getComponent().getClassName()) - .isEqualTo(DEFAULT_SETTINGS_CLASSNAME); - } - @Test public void testGetLabelForBackupSettings_WithLabelFromTransport() throws Exception { String label = "test_label"; From 96a40bab77f75ca8bd7c9346265b2a1a348dc3c7 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 6 Feb 2019 10:43:54 -0800 Subject: [PATCH 08/10] Use system defined card corners instead of hardcoding to 8. Change-Id: I690fa4c2d5a52d1cb6e1606b5c961d0116a5707b Fixes: 123985440 Test: visual --- res/values/styles.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/styles.xml b/res/values/styles.xml index 6d153e1d030..935bbf27acd 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -419,7 +419,7 @@ @dimen/homepage_card_vertical_margin @dimen/homepage_card_side_margin @dimen/homepage_card_side_margin - 8dp + @*android:dimen/config_dialogCornerRadius 0dp @color/homepage_card_stroke_color 1dp From fcb6aff14c016ef398e80c4bad7140d0f007c48d Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Wed, 6 Feb 2019 14:09:13 -0800 Subject: [PATCH 09/10] Change device ID developer setting to disable option Bug: 123646983 Test: Manually verified toggling the option set the expected value Change-Id: Ia219c154d9572111c4fce1af4128a103ba0409ec --- res/values/strings.xml | 4 ++-- ...iceIdentifierAccessRestrictionsPreferenceController.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 59374cf114a..10cf6c1684b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5851,9 +5851,9 @@ Only default phone and messaging apps have SMS & call log permissions - Enable device identifier restrictions + Disable device identifier restrictions - Enable the new access restrictions for device identifiers. + Disable the new access restrictions for device identifiers diff --git a/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java b/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java index 1fdbe77641c..05ac8c760c0 100644 --- a/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java +++ b/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java @@ -50,12 +50,12 @@ public class DeviceIdentifierAccessRestrictionsPreferenceController private void writeSetting(boolean isEnabled) { DeviceConfig.setProperty(DeviceConfig.Privacy.NAMESPACE, DeviceConfig.Privacy.PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED, - String.valueOf(!isEnabled), false); + String.valueOf(isEnabled), false); } @Override public void updateState(Preference preference) { - boolean isEnabled = !Boolean.parseBoolean( + boolean isEnabled = Boolean.parseBoolean( DeviceConfig.getProperty(DeviceConfig.Privacy.NAMESPACE, DeviceConfig.Privacy.PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED)); ((SwitchPreference) mPreference).setChecked(isEnabled); @@ -64,7 +64,7 @@ public class DeviceIdentifierAccessRestrictionsPreferenceController @Override protected void onDeveloperOptionsSwitchDisabled() { super.onDeveloperOptionsSwitchDisabled(); - writeSetting(true); + writeSetting(false); ((SwitchPreference) mPreference).setChecked(true); } } From 9bc0fb3c6a3cb8826c4b1cf707cc3b31d2fa5210 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 6 Feb 2019 14:17:53 -0800 Subject: [PATCH 10/10] Merge bool.xml and config.xml into one file. Bug: 124008048 Test: rebuild Change-Id: I3a6522e6d0939ef1f80406d91b35492360916aa1 --- res/values/bools.xml | 191 ---------------------------------- res/values/colors.xml | 3 - res/values/config.xml | 174 +++++++++++++++++++++++++++++++ res/values/donottranslate.xml | 4 - 4 files changed, 174 insertions(+), 198 deletions(-) delete mode 100644 res/values/bools.xml diff --git a/res/values/bools.xml b/res/values/bools.xml deleted file mode 100644 index a5043a2949e..00000000000 --- a/res/values/bools.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - false - - false - - true - - - false - - - false - - - true - - - false - - false - - - true - - - false - - - false - - - false - - - true - - - true - - - true - - - false - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - false - - - true - diff --git a/res/values/colors.xml b/res/values/colors.xml index da01be030f9..f6723a86074 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -15,9 +15,6 @@ --> - #000 - #F00 - #00F #8a000000 diff --git a/res/values/config.xml b/res/values/config.xml index 0e46200e24a..3023067ba4e 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -179,4 +179,178 @@ + + + false + + false + + true + + + false + + + false + + + true + + + false + + false + + + true + + + false + + + false + + + false + + + true + + + true + + + true + + + false + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + false + + + true diff --git a/res/values/donottranslate.xml b/res/values/donottranslate.xml index 611e2b4b021..e6bd9a6bb7b 100644 --- a/res/values/donottranslate.xml +++ b/res/values/donottranslate.xml @@ -33,8 +33,4 @@ @string/input_method_selector_always_show_value @string/input_method_selector_always_hide_value - com.android.settings.category.wireless - com.android.settings.category.device - com.android.settings.category.personal - com.android.settings.category.system