From d3358d8a6f86d37852387acfc0751908eb43c56c Mon Sep 17 00:00:00 2001 From: Chilun Date: Mon, 26 Mar 2018 18:28:56 +0800 Subject: [PATCH 01/17] Add index for Hotspot settings page into search Added index provider into TetherSettings and WifiTetherSettings pages. Added robotests to check the keys returned by getNonIndexableKeys. Move repeated code into utility class. Bug: 74777873 Test: make -j RunSettingsRoboTests Test: atest UniquePreferenceTest Change-Id: I6cb09d735afa4867d25a79de733ce8c59634e49b --- res/xml/tether_prefs.xml | 7 +- res/xml/wifi_tether_settings.xml | 3 +- src/com/android/settings/TetherSettings.java | 64 +++++++- .../network/TetherPreferenceController.java | 8 +- .../wifi/tether/WifiTetherSettings.java | 45 +++++- ...randfather_not_implementing_index_provider | 1 - .../android/settings/TetherSettingsTest.java | 150 ++++++++++++++++++ .../wifi/tether/WifiTetherSettingsTest.java | 106 +++++++++++++ 8 files changed, 365 insertions(+), 19 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/TetherSettingsTest.java create mode 100644 tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java diff --git a/res/xml/tether_prefs.xml b/res/xml/tether_prefs.xml index 2a841d71608..70a57d7c98a 100644 --- a/res/xml/tether_prefs.xml +++ b/res/xml/tether_prefs.xml @@ -17,6 +17,7 @@ + android:summary="@string/usb_tethering_subtext" + settings:keywords="@string/keywords_hotspot_tethering" /> + android:summary="@string/bluetooth_tethering_subtext" + settings:keywords="@string/keywords_hotspot_tethering" /> @@ -43,4 +44,4 @@ android:dialogTitle="@string/wifi_hotspot_ap_band_title" android:negativeButtonText="@string/cancel" android:positiveButtonText="@string/apply"/> - \ No newline at end of file + diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java index 8cb8efc27df..848e1765699 100644 --- a/src/com/android/settings/TetherSettings.java +++ b/src/com/android/settings/TetherSettings.java @@ -34,27 +34,41 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.UserManager; -import androidx.preference.SwitchPreference; +import android.provider.SearchIndexableResource; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; +import androidx.preference.SwitchPreference; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.datausage.DataSaverBackend; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; import com.android.settings.wifi.tether.WifiTetherPreferenceController; import com.android.settingslib.TetherUtil; +import com.android.settingslib.search.SearchIndexable; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; /* * Displays preferences for Tethering. */ +@SearchIndexable public class TetherSettings extends RestrictedSettingsFragment implements DataSaverBackend.Listener { - private static final String USB_TETHER_SETTINGS = "usb_tether_settings"; - private static final String ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering"; - private static final String DATA_SAVER_FOOTER = "disabled_on_data_saver"; + @VisibleForTesting + static final String KEY_TETHER_PREFS_SCREEN = "tether_prefs_screen"; + @VisibleForTesting + static final String KEY_WIFI_TETHER = "wifi_tether"; + @VisibleForTesting + static final String KEY_USB_TETHER_SETTINGS = "usb_tether_settings"; + @VisibleForTesting + static final String KEY_ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering"; + private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver"; private static final String TAG = "TetheringSettings"; @@ -110,7 +124,7 @@ public class TetherSettings extends RestrictedSettingsFragment mDataSaverBackend = new DataSaverBackend(getContext()); mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled(); - mDataSaverFooter = findPreference(DATA_SAVER_FOOTER); + mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER); setIfOnlyAvailableForAdmins(true); if (isUiRestricted()) { @@ -126,8 +140,8 @@ public class TetherSettings extends RestrictedSettingsFragment BluetoothProfile.PAN); } - mUsbTether = (SwitchPreference) findPreference(USB_TETHER_SETTINGS); - mBluetoothTether = (SwitchPreference) findPreference(ENABLE_BLUETOOTH_TETHERING); + mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS); + mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING); mDataSaverBackend.addListener(this); @@ -433,6 +447,42 @@ public class TetherSettings extends RestrictedSettingsFragment } }; + public static final Indexable.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.tether_prefs; + return Arrays.asList(sir); + } + + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + final ConnectivityManager cm = + context.getSystemService(ConnectivityManager.class); + + if (!TetherUtil.isTetherAvailable(context)) { + keys.add(KEY_TETHER_PREFS_SCREEN); + keys.add(KEY_WIFI_TETHER); + } + + final boolean usbAvailable = + cm.getTetherableUsbRegexs().length != 0; + if (!usbAvailable || Utils.isMonkeyRunning()) { + keys.add(KEY_USB_TETHER_SETTINGS); + } + + final boolean bluetoothAvailable = + cm.getTetherableBluetoothRegexs().length != 0; + if (!bluetoothAvailable) { + keys.add(KEY_ENABLE_BLUETOOTH_TETHERING); + } + return keys; + } + }; + private static final class OnStartTetheringCallback extends ConnectivityManager.OnStartTetheringCallback { final WeakReference mTetherSettings; diff --git a/src/com/android/settings/network/TetherPreferenceController.java b/src/com/android/settings/network/TetherPreferenceController.java index 636fe0cee07..3ab83cda0b5 100644 --- a/src/com/android/settings/network/TetherPreferenceController.java +++ b/src/com/android/settings/network/TetherPreferenceController.java @@ -17,7 +17,6 @@ package com.android.settings.network; import static android.os.UserManager.DISALLOW_CONFIG_TETHERING; import static com.android.settingslib.RestrictedLockUtils.checkIfRestrictionEnforced; -import static com.android.settingslib.RestrictedLockUtils.hasBaseUserRestriction; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; @@ -40,6 +39,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.TetherSettings; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.TetherUtil; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -112,11 +112,7 @@ public class TetherPreferenceController extends AbstractPreferenceController imp @Override public boolean isAvailable() { - final boolean isBlocked = - (!mConnectivityManager.isTetheringSupported() && !mAdminDisallowedTetherConfig) - || hasBaseUserRestriction(mContext, DISALLOW_CONFIG_TETHERING, - UserHandle.myUserId()); - return !isBlocked; + return TetherUtil.isTetherAvailable(mContext); } @Override diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java index 7c50f0c2cdb..5e202d63471 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java @@ -27,28 +27,42 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.UserManager; -import androidx.annotation.VisibleForTesting; +import android.provider.SearchIndexableResource; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.SwitchBar; import com.android.settings.widget.SwitchBarController; +import com.android.settingslib.TetherUtil; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.search.SearchIndexable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; +@SearchIndexable public class WifiTetherSettings extends RestrictedDashboardFragment implements WifiTetherBasePreferenceController.OnTetherConfigUpdateListener { private static final String TAG = "WifiTetherSettings"; private static final IntentFilter TETHER_STATE_CHANGE_FILTER; - private static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off"; + private static final String KEY_WIFI_TETHER_SCREEN = "wifi_tether_settings_screen"; + @VisibleForTesting + static final String KEY_WIFI_TETHER_NETWORK_NAME = "wifi_tether_network_name"; + @VisibleForTesting + static final String KEY_WIFI_TETHER_NETWORK_PASSWORD = "wifi_tether_network_password"; + @VisibleForTesting + static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off"; + @VisibleForTesting + static final String KEY_WIFI_TETHER_NETWORK_AP_BAND = "wifi_tether_network_ap_band"; private WifiTetherSwitchBarController mSwitchBarController; private WifiTetherSSIDPreferenceController mSSIDPreferenceController; @@ -182,6 +196,33 @@ public class WifiTetherSettings extends RestrictedDashboardFragment .updateDisplay(); } + 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.wifi_tether_settings; + return Arrays.asList(sir); + } + + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + + if (!TetherUtil.isTetherAvailable(context)) { + keys.add(KEY_WIFI_TETHER_NETWORK_NAME); + keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD); + keys.add(KEY_WIFI_TETHER_AUTO_OFF); + keys.add(KEY_WIFI_TETHER_NETWORK_AP_BAND); + } + + // Remove duplicate + keys.add(KEY_WIFI_TETHER_SCREEN); + return keys; + } + }; + @VisibleForTesting class TetherChangeReceiver extends BroadcastReceiver { @Override diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 8bd4f823840..31e3a54ee26 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -21,7 +21,6 @@ com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionMi com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages com.android.settings.enterprise.EnterpriseSetDefaultAppsListFragment com.android.settings.inputmethod.KeyboardLayoutPickerFragment -com.android.settings.wifi.tether.WifiTetherSettings com.android.settings.wifi.SavedAccessPointsWifiSettings com.android.settings.notification.ZenModeEventRuleSettings com.android.settings.notification.ZenModeScheduleRuleSettings diff --git a/tests/robotests/src/com/android/settings/TetherSettingsTest.java b/tests/robotests/src/com/android/settings/TetherSettingsTest.java new file mode 100644 index 00000000000..1ccd958cfb3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/TetherSettingsTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018 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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; +import android.net.ConnectivityManager; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class TetherSettingsTest { + + private Context mContext; + + @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private UserManager mUserManager; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + + MockitoAnnotations.initMocks(this); + doReturn(mConnectivityManager) + .when(mContext).getSystemService(Context.CONNECTIVITY_SERVICE); + doReturn(mUserManager) + .when(mContext).getSystemService(Context.USER_SERVICE); + + setupIsTetherAvailable(true); + + when(mConnectivityManager.getTetherableUsbRegexs()).thenReturn(new String[]{}); + when(mConnectivityManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{}); + } + + @Test + public void testTetherNonIndexableKeys_tetherAvailable_keysNotReturned() { + // To let TetherUtil.isTetherAvailable return true, select one of the combinations + setupIsTetherAvailable(true); + + final List niks = + TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).doesNotContain(TetherSettings.KEY_TETHER_PREFS_SCREEN); + assertThat(niks).doesNotContain(TetherSettings.KEY_WIFI_TETHER); + } + + @Test + public void testTetherNonIndexableKeys_tetherNotAvailable_keysReturned() { + // To let TetherUtil.isTetherAvailable return false, select one of the combinations + setupIsTetherAvailable(false); + + final List niks = + TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).contains(TetherSettings.KEY_TETHER_PREFS_SCREEN); + assertThat(niks).contains(TetherSettings.KEY_WIFI_TETHER); + } + + @Test + public void testTetherNonIndexableKeys_usbNotAvailable_usbKeyReturned() { + when(mConnectivityManager.getTetherableUsbRegexs()).thenReturn(new String[]{}); + + final List niks = + TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).contains(TetherSettings.KEY_USB_TETHER_SETTINGS); + } + + @Test + public void testTetherNonIndexableKeys_usbAvailable_usbKeyNotReturned() { + // We can ignore the condition of Utils.isMonkeyRunning() + // In normal case, monkey and robotest should not execute at the same time + when(mConnectivityManager.getTetherableUsbRegexs()).thenReturn(new String[]{"dummyRegex"}); + + final List niks = + TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).doesNotContain(TetherSettings.KEY_USB_TETHER_SETTINGS); + } + + @Test + public void testTetherNonIndexableKeys_bluetoothNotAvailable_bluetoothKeyReturned() { + when(mConnectivityManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{}); + + final List niks = + TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).contains(TetherSettings.KEY_ENABLE_BLUETOOTH_TETHERING); + } + + @Test + public void testTetherNonIndexableKeys_bluetoothAvailable_bluetoothKeyNotReturned() { + when(mConnectivityManager.getTetherableBluetoothRegexs()) + .thenReturn(new String[]{"dummyRegex"}); + + final List niks = + TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).doesNotContain(TetherSettings.KEY_ENABLE_BLUETOOTH_TETHERING); + } + + private void setupIsTetherAvailable(boolean returnValue) { + when(mConnectivityManager.isTetheringSupported()).thenReturn(true); + + // For RestrictedLockUtils.checkIfRestrictionEnforced + final int userId = UserHandle.myUserId(); + List enforcingUsers = new ArrayList<>(); + when(mUserManager.getUserRestrictionSources( + UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(userId))) + .thenReturn(enforcingUsers); + + // For RestrictedLockUtils.hasBaseUserRestriction + when(mUserManager.hasBaseUserRestriction( + UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(userId))) + .thenReturn(!returnValue); + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java new file mode 100644 index 00000000000..58bb1989d98 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 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.wifi.tether; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; +import android.net.ConnectivityManager; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class WifiTetherSettingsTest { + + private Context mContext; + + @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private UserManager mUserManager; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + + MockitoAnnotations.initMocks(this); + doReturn(mConnectivityManager) + .when(mContext).getSystemService(Context.CONNECTIVITY_SERVICE); + doReturn(mUserManager) + .when(mContext).getSystemService(Context.USER_SERVICE); + } + + @Test + public void testWifiTetherNonIndexableKeys_tetherAvailable_keysNotReturned() { + // To let TetherUtil.isTetherAvailable return true, select one of the combinations + setupIsTetherAvailable(true); + + final List niks = + WifiTetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(niks).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(niks).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(niks).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_AP_BAND); + } + + @Test + public void testWifiTetherNonIndexableKeys_tetherNotAvailable_keysReturned() { + // To let TetherUtil.isTetherAvailable return false, select one of the combinations + setupIsTetherAvailable(false); + + final List niks = + WifiTetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); + + assertThat(niks).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(niks).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(niks).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(niks).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_AP_BAND); + } + + private void setupIsTetherAvailable(boolean returnValue) { + when(mConnectivityManager.isTetheringSupported()).thenReturn(true); + + // For RestrictedLockUtils.checkIfRestrictionEnforced + final int userId = UserHandle.myUserId(); + List enforcingUsers = new ArrayList<>(); + when(mUserManager.getUserRestrictionSources( + UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(userId))) + .thenReturn(enforcingUsers); + + // For RestrictedLockUtils.hasBaseUserRestriction + when(mUserManager.hasBaseUserRestriction( + UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(userId))) + .thenReturn(!returnValue); + } +} From 7f09e00df3f98ccf0d27bdb0002a72141f29b8f1 Mon Sep 17 00:00:00 2001 From: Jeff Gaston Date: Tue, 8 May 2018 14:41:39 -0400 Subject: [PATCH 02/17] Update for support library prebuilt drop Bug: 79267345 Test: m -j checkbuild Change-Id: I086a2253a1c94af6f7c4dca04d2644a4f9e54ef5 --- .../android/settings/search/DeviceIndexUpdateJobService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/search/DeviceIndexUpdateJobService.java b/src/com/android/settings/search/DeviceIndexUpdateJobService.java index 573dcdf5c8b..c8292021349 100644 --- a/src/com/android/settings/search/DeviceIndexUpdateJobService.java +++ b/src/com/android/settings/search/DeviceIndexUpdateJobService.java @@ -115,7 +115,7 @@ public class DeviceIndexUpdateJobService extends JobService { } protected CharSequence findTitle(Slice loadedSlice, SliceMetadata metaData) { - ListContent content = new ListContent(this, loadedSlice); + ListContent content = new ListContent(null, loadedSlice); SliceItem headerItem = content.getHeaderItem(); if (headerItem == null) { if (content.getRowItems().size() != 0) { From f4f68c41179cedf22300ca07be06e116fd649980 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Fri, 11 May 2018 23:23:55 +0000 Subject: [PATCH 03/17] Remove developer menu: AVDTP delay reports/inband ringing Bluetooth AVDTP delay reports and inband ringing are used by default. Developer option menu no longer used. Bug: 79610154 Test: robolectric Change-Id: I67fe10808151f101fc7e891f75953f8539255026 Merged-In: I67fe10808151f101fc7e891f75953f8539255026 --- res/values/strings.xml | 10 -- res/xml/development_settings.xml | 10 -- ...toothDelayReportsPreferenceController.java | 69 ---------- ...oothInbandRingingPreferenceController.java | 78 ------------ .../DevelopmentSettingsDashboardFragment.java | 2 - ...hDelayReportsPreferenceControllerTest.java | 110 ---------------- ...InbandRingingPreferenceControllerTest.java | 120 ------------------ ...elopmentSettingsDashboardFragmentTest.java | 10 -- 8 files changed, 409 deletions(-) delete mode 100644 src/com/android/settings/development/BluetoothDelayReportsPreferenceController.java delete mode 100644 src/com/android/settings/development/BluetoothInbandRingingPreferenceController.java delete mode 100644 tests/robotests/src/com/android/settings/development/BluetoothDelayReportsPreferenceControllerTest.java delete mode 100644 tests/robotests/src/com/android/settings/development/BluetoothInbandRingingPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 8347f9956c9..0938b0ba91a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -402,11 +402,6 @@ Allow your phone to communicate with nearby Bluetooth devices - - Disable in-band ringing - - Don’t play custom phone ringtones on Bluetooth headsets - Available media devices @@ -1691,15 +1686,10 @@ Remember settings - Maximum connected Bluetooth audio devices Select maximum number of connected Bluetooth audio devices - - Disable Bluetooth AVDTP delay reports - - Disallow receiving Bluetooth AVDTP delay reports Cast diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index c491980153c..3836b43930b 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -254,16 +254,6 @@ android:title="@string/bluetooth_disable_absolute_volume" android:summary="@string/bluetooth_disable_absolute_volume_summary" /> - - - - Date: Fri, 11 May 2018 23:23:55 +0000 Subject: [PATCH 04/17] Remove developer menu: AVDTP delay reports/inband ringing Bluetooth AVDTP delay reports and inband ringing are used by default. Developer option menu no longer used. Bug: 79610154 Test: robolectric Change-Id: I67fe10808151f101fc7e891f75953f8539255026 --- res/values/strings.xml | 10 -- res/xml/development_settings.xml | 10 -- ...toothDelayReportsPreferenceController.java | 69 ---------- ...oothInbandRingingPreferenceController.java | 78 ------------ .../DevelopmentSettingsDashboardFragment.java | 2 - ...hDelayReportsPreferenceControllerTest.java | 110 ---------------- ...InbandRingingPreferenceControllerTest.java | 120 ------------------ ...elopmentSettingsDashboardFragmentTest.java | 10 -- 8 files changed, 409 deletions(-) delete mode 100644 src/com/android/settings/development/BluetoothDelayReportsPreferenceController.java delete mode 100644 src/com/android/settings/development/BluetoothInbandRingingPreferenceController.java delete mode 100644 tests/robotests/src/com/android/settings/development/BluetoothDelayReportsPreferenceControllerTest.java delete mode 100644 tests/robotests/src/com/android/settings/development/BluetoothInbandRingingPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 252a601c794..e391cd4a931 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -402,11 +402,6 @@ Allow your phone to communicate with nearby Bluetooth devices - - Disable in-band ringing - - Don’t play custom phone ringtones on Bluetooth headsets - Available media devices @@ -1696,15 +1691,10 @@ Remember settings - Maximum connected Bluetooth audio devices Select maximum number of connected Bluetooth audio devices - - Disable Bluetooth AVDTP delay reports - - Disallow receiving Bluetooth AVDTP delay reports Cast diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 52d8969241f..2e549046be1 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -254,16 +254,6 @@ android:title="@string/bluetooth_disable_absolute_volume" android:summary="@string/bluetooth_disable_absolute_volume_summary" /> - - - - Date: Fri, 4 May 2018 17:09:28 +0100 Subject: [PATCH 05/17] Simplify password length constraints checking Currently minimum password length policy is queried twice: 1. When constructiong the intent in ChooseLockGenericFragment.getIntentForUnlockMethod and then passed into setPasswordLengthRange in getLockPasswordIntent 2. in ChooseLockPasswordFragment.processPasswordRequirements via LockPatternUtils.getRequestedMinimumPasswordLength(). These two values are then combined in processPasswordRequirements using Math.max(), which doesn't make sense since it is the same value. With this CL it is only queried once in processPasswordRequirements. + cleaned up code filling in unused list. + removed unused extras, since they are never set anywhere. Bug: 30558331 Test: atest ChooseLockPasswordTest Test: atest SetupChooseLockPasswordTest Test: atest ChooseLockGenericTest Test: manual, set password policy and change password. Change-Id: Ifc4946d5b3b26131da01178fa9c827de7a52c7c6 --- .../settings/password/ChooseLockGeneric.java | 14 +--- .../settings/password/ChooseLockPassword.java | 71 +++---------------- .../password/SetupChooseLockGeneric.java | 4 +- .../password/ChooseLockPasswordTest.java | 14 ---- 4 files changed, 14 insertions(+), 89 deletions(-) diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index add05e4e4a5..4d0165e1524 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -38,7 +38,6 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; -import android.security.KeyStore; import androidx.annotation.StringRes; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -97,7 +96,6 @@ public class ChooseLockGeneric extends SettingsActivity { public static class ChooseLockGenericFragment extends SettingsPreferenceFragment { private static final String TAG = "ChooseLockGenericFragment"; - private static final int MIN_PASSWORD_LENGTH = 4; private static final String KEY_SKIP_FINGERPRINT = "unlock_skip_fingerprint"; private static final String PASSWORD_CONFIRMED = "password_confirmed"; private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation"; @@ -136,7 +134,6 @@ public class ChooseLockGeneric extends SettingsActivity { private ChooseLockSettingsHelper mChooseLockSettingsHelper; private DevicePolicyManager mDPM; - private KeyStore mKeyStore; private boolean mHasChallenge = false; private long mChallenge; private boolean mPasswordConfirmed = false; @@ -168,7 +165,6 @@ public class ChooseLockGeneric extends SettingsActivity { String chooseLockAction = getActivity().getIntent().getAction(); mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity()); mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); - mKeyStore = KeyStore.getInstance(); mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity()); mLockPatternUtils = new LockPatternUtils(getActivity()); mIsSetNewPassword = ACTION_SET_NEW_PARENT_PROFILE_PASSWORD.equals(chooseLockAction) @@ -585,11 +581,10 @@ public class ChooseLockGeneric extends SettingsActivity { return mManagedPasswordProvider.createIntent(false, password); } - protected Intent getLockPasswordIntent(int quality, int minLength, int maxLength) { + protected Intent getLockPasswordIntent(int quality) { ChooseLockPassword.IntentBuilder builder = new ChooseLockPassword.IntentBuilder(getContext()) .setPasswordQuality(quality) - .setPasswordLengthRange(minLength, maxLength) .setForFingerprint(mForFingerprint) .setUserId(mUserId); if (mHasChallenge) { @@ -668,12 +663,7 @@ public class ChooseLockGeneric extends SettingsActivity { if (quality >= DevicePolicyManager.PASSWORD_QUALITY_MANAGED) { intent = getLockManagedPasswordIntent(mUserPassword); } else if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) { - int minLength = mDPM.getPasswordMinimumLength(null, mUserId); - if (minLength < MIN_PASSWORD_LENGTH) { - minLength = MIN_PASSWORD_LENGTH; - } - final int maxLength = mDPM.getPasswordMaximumLength(quality); - intent = getLockPasswordIntent(quality, minLength, maxLength); + intent = getLockPasswordIntent(quality); } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) { intent = getLockPatternIntent(); } diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 23c50226be9..1013c44700e 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -73,15 +73,6 @@ import java.util.ArrayList; import java.util.List; public class ChooseLockPassword extends SettingsActivity { - public static final String PASSWORD_MIN_KEY = "lockscreen.password_min"; - public static final String PASSWORD_MAX_KEY = "lockscreen.password_max"; - public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters"; - public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase"; - public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase"; - public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric"; - public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols"; - public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter"; - private static final String TAG = "ChooseLockPassword"; @Override @@ -113,12 +104,6 @@ public class ChooseLockPassword extends SettingsActivity { return this; } - public IntentBuilder setPasswordLengthRange(int min, int max) { - mIntent.putExtra(PASSWORD_MIN_KEY, min); - mIntent.putExtra(PASSWORD_MAX_KEY, max); - return this; - } - public IntentBuilder setUserId(int userId) { mIntent.putExtra(Intent.EXTRA_USER_ID, userId); return this; @@ -454,54 +439,32 @@ public class ChooseLockPassword extends SettingsActivity { } private void setupPasswordRequirementsView(View view) { - // Construct passwordRequirements and requirementDescriptions. - List passwordRequirements = new ArrayList<>(); - List requirementDescriptions = new ArrayList<>(); + final List passwordRequirements = new ArrayList<>(); if (mPasswordMinUpperCase > 0) { passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD); - requirementDescriptions.add(getResources().getQuantityString( - R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase, - mPasswordMinUpperCase)); } if (mPasswordMinLowerCase > 0) { passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD); - requirementDescriptions.add(getResources().getQuantityString( - R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase, - mPasswordMinLowerCase)); } if (mPasswordMinLetters > 0) { if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) { passwordRequirements.add(MIN_LETTER_IN_PASSWORD); - requirementDescriptions.add(getResources().getQuantityString( - R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters, - mPasswordMinLetters)); } } if (mPasswordMinNumeric > 0) { passwordRequirements.add(MIN_NUMBER_IN_PASSWORD); - requirementDescriptions.add(getResources().getQuantityString( - R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric, - mPasswordMinNumeric)); } if (mPasswordMinSymbols > 0) { passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD); - requirementDescriptions.add(getResources().getQuantityString( - R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols, - mPasswordMinSymbols)); } if (mPasswordMinNonLetter > 0) { if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) { passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD); - requirementDescriptions.add(getResources().getQuantityString( - R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter, - - mPasswordMinNonLetter)); } } // Convert list to array. mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray(); - mPasswordRestrictionView = - (RecyclerView) view.findViewById(R.id.password_requirements_view); + mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view); mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); mPasswordRequirementAdapter = new PasswordRequirementAdapter(); mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter); @@ -582,29 +545,15 @@ public class ChooseLockPassword extends SettingsActivity { final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId); mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedQuality), dpmPasswordQuality); - mPasswordMinLength = Math.max(Math.max( - LockPatternUtils.MIN_LOCK_PASSWORD_SIZE, - intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)), + mPasswordMinLength = Math.max(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE, mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId)); - mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength); - mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY, - mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters( - mUserId)); - mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY, - mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase( - mUserId)); - mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY, - mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase( - mUserId)); - mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY, - mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric( - mUserId)); - mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY, - mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols( - mUserId)); - mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY, - mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter( - mUserId)); + mPasswordMaxLength = mLockPatternUtils.getMaximumPasswordLength(mRequestedQuality); + mPasswordMinLetters = mLockPatternUtils.getRequestedPasswordMinimumLetters(mUserId); + mPasswordMinUpperCase = mLockPatternUtils.getRequestedPasswordMinimumUpperCase(mUserId); + mPasswordMinLowerCase = mLockPatternUtils.getRequestedPasswordMinimumLowerCase(mUserId); + mPasswordMinNumeric = mLockPatternUtils.getRequestedPasswordMinimumNumeric(mUserId); + mPasswordMinSymbols = mLockPatternUtils.getRequestedPasswordMinimumSymbols(mUserId); + mPasswordMinNonLetter = mLockPatternUtils.getRequestedPasswordMinimumNonLetter(mUserId); // Modify the value based on dpm policy. switch (dpmPasswordQuality) { diff --git a/src/com/android/settings/password/SetupChooseLockGeneric.java b/src/com/android/settings/password/SetupChooseLockGeneric.java index a72047308c2..3edb08301b3 100644 --- a/src/com/android/settings/password/SetupChooseLockGeneric.java +++ b/src/com/android/settings/password/SetupChooseLockGeneric.java @@ -170,9 +170,9 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { } @Override - protected Intent getLockPasswordIntent(int quality, int minLength, int maxLength) { + protected Intent getLockPasswordIntent(int quality) { final Intent intent = SetupChooseLockPassword.modifyIntentForSetup( - getContext(), super.getLockPasswordIntent(quality, minLength, maxLength)); + getContext(), super.getLockPasswordIntent(quality)); SetupWizardUtils.copySetupExtras(getActivity().getIntent(), intent); return intent; } diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java index 75b6bb4b14a..7ee9ea51c45 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java @@ -67,7 +67,6 @@ public class ChooseLockPasswordTest { Intent intent = new IntentBuilder(application) .setPassword("password") .setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) - .setPasswordLengthRange(123, 456) .setUserId(123) .build(); @@ -77,12 +76,6 @@ public class ChooseLockPasswordTest { assertThat(intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD)) .named("EXTRA_KEY_PASSWORD") .isEqualTo("password"); - assertThat(intent.getIntExtra(ChooseLockPassword.PASSWORD_MIN_KEY, 0)) - .named("PASSWORD_MIN_KEY") - .isEqualTo(123); - assertThat(intent.getIntExtra(ChooseLockPassword.PASSWORD_MAX_KEY, 0)) - .named("PASSWORD_MAX_KEY") - .isEqualTo(456); assertThat(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 0)) .named("PASSWORD_TYPE_KEY") .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC); @@ -96,7 +89,6 @@ public class ChooseLockPasswordTest { Intent intent = new IntentBuilder(application) .setChallenge(12345L) .setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) - .setPasswordLengthRange(123, 456) .setUserId(123) .build(); @@ -106,12 +98,6 @@ public class ChooseLockPasswordTest { assertThat(intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0L)) .named("EXTRA_KEY_CHALLENGE") .isEqualTo(12345L); - assertThat(intent.getIntExtra(ChooseLockPassword.PASSWORD_MIN_KEY, 0)) - .named("PASSWORD_MIN_KEY") - .isEqualTo(123); - assertThat(intent.getIntExtra(ChooseLockPassword.PASSWORD_MAX_KEY, 0)) - .named("PASSWORD_MAX_KEY") - .isEqualTo(456); assertThat(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 0)) .named("PASSWORD_TYPE_KEY") .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC); From f487cb9e0ad36e0bcdc32083b131f916534d970c Mon Sep 17 00:00:00 2001 From: Beverly Date: Fri, 11 May 2018 14:03:25 -0400 Subject: [PATCH 06/17] DO NOT MERGE Always show custom zen vis eff option + dnd string edits Change-Id: I2fcbe6c527b40610f997efacb08049c07b3a6dd7 Bug: 79538038 Bug: 78447976 Test: ZenModeViseffectsCustomPreferenceControllerTest --- res/values/strings.xml | 17 ++++- res/xml/zen_mode_calls_settings.xml | 4 +- .../zen_mode_msg_event_reminder_settings.xml | 6 +- .../ZenModeRestrictNotificationsSettings.java | 63 ++----------------- .../notification/ZenModeSettings.java | 24 ++++--- ...eVisEffectsCustomPreferenceController.java | 11 +--- .../notification/ZenModeSettingsTest.java | 4 +- ...EffectsCustomPreferenceControllerTest.java | 13 +--- 8 files changed, 45 insertions(+), 97 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 8bdfb5f857a..3319555f206 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7928,6 +7928,9 @@ Calls + + Allow calls + When Do Not Disturb is on, incoming calls are blocked. You can adjust settings to allow your friends, family, or other contacts to reach you. @@ -7943,6 +7946,9 @@ Messages + + Allow messages + Messages @@ -7982,9 +7988,15 @@ Reminders + + Allow reminders + Events + + Allow events + anyone @@ -7997,8 +8009,11 @@ Repeat callers + + Allow repeat callers + - From %1$s only + From %1$s From %1$s and %2$s diff --git a/res/xml/zen_mode_calls_settings.xml b/res/xml/zen_mode_calls_settings.xml index 62d9ef4490d..1a6655ffde6 100644 --- a/res/xml/zen_mode_calls_settings.xml +++ b/res/xml/zen_mode_calls_settings.xml @@ -25,7 +25,7 @@ @@ -36,7 +36,7 @@ + android:title="@string/zen_mode_repeat_callers_title" /> diff --git a/res/xml/zen_mode_msg_event_reminder_settings.xml b/res/xml/zen_mode_msg_event_reminder_settings.xml index 2f065a6e881..9bee9e94483 100644 --- a/res/xml/zen_mode_msg_event_reminder_settings.xml +++ b/res/xml/zen_mode_msg_event_reminder_settings.xml @@ -25,7 +25,7 @@ @@ -36,12 +36,12 @@ + android:title="@string/zen_mode_reminders_title"/> + android:title="@string/zen_mode_events_title"/> diff --git a/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java b/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java index 14b82f52836..8d0cd0eb5c8 100644 --- a/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java +++ b/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java @@ -16,22 +16,15 @@ package com.android.settings.notification; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ZEN_SHOW_CUSTOM; - import android.content.Context; import android.os.Bundle; import android.provider.SearchIndexableResource; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; -import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.widget.FooterPreference; @@ -40,67 +33,21 @@ import java.util.List; public class ZenModeRestrictNotificationsSettings extends ZenModeSettingsBase implements Indexable { - protected static final int APP_MENU_SHOW_CUSTOM = 1; - protected boolean mShowMenuSelected; - @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, APP_MENU_SHOW_CUSTOM, 0, R.string.zen_mode_restrict_notifications_enable_custom) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - if (menuItem.getItemId() == APP_MENU_SHOW_CUSTOM) { - final FeatureFactory featureFactory = FeatureFactory.getFactory(mContext); - MetricsFeatureProvider metrics = featureFactory.getMetricsFeatureProvider(); - - mShowMenuSelected = !mShowMenuSelected; - - ZenModeVisEffectsCustomPreferenceController custom = - use(ZenModeVisEffectsCustomPreferenceController.class); - custom.setShownByMenu(mShowMenuSelected); - custom.displayPreference(getPreferenceScreen()); - - if (mShowMenuSelected) { - metrics.action(mContext, ACTION_ZEN_SHOW_CUSTOM, true); - } else { - metrics.action(mContext, ACTION_ZEN_SHOW_CUSTOM, false); - } - - return true; - } - return false; - } - - @Override - public void onPrepareOptionsMenu(Menu menu) { - if (mShowMenuSelected) { - menu.findItem(APP_MENU_SHOW_CUSTOM) - .setTitle(R.string.zen_mode_restrict_notifications_disable_custom); - } else { - menu.findItem(APP_MENU_SHOW_CUSTOM) - .setTitle(R.string.zen_mode_restrict_notifications_enable_custom); - } - - if (mShowMenuSelected && use(ZenModeVisEffectsCustomPreferenceController.class) - .areCustomOptionsSelected()) { - menu.findItem(APP_MENU_SHOW_CUSTOM).setEnabled(false); - } else { - menu.findItem(APP_MENU_SHOW_CUSTOM).setEnabled(true); - } - } - @Override protected List createPreferenceControllers(Context context) { return buildPreferenceControllers(context, getLifecycle()); } + @Override + public int getHelpResource() { + return R.string.help_uri_interruptions; + } + private static List buildPreferenceControllers(Context context, Lifecycle lifecycle) { List controllers = new ArrayList<>(); diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index ec0f6192d94..62b3fe55dd6 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -30,6 +30,7 @@ import android.app.FragmentManager; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.Context; +import android.icu.text.ListFormatter; import android.provider.SearchIndexableResource; import android.provider.Settings; import android.service.notification.ZenModeConfig; @@ -166,17 +167,20 @@ public class ZenModeSettings extends ZenModeSettingsBase { return mContext.getString(R.string.join_two_items, enabledCategories.get(0), enabledCategories.get(1).toLowerCase()); } else if (numCategories == 3){ - String secondaryText = mContext.getString(R.string.join_two_unrelated_items, - enabledCategories.get(0), enabledCategories.get(1).toLowerCase()); - return mContext.getString(R.string.join_many_items_last, secondaryText, - enabledCategories.get(2).toLowerCase()); + final List summaries = new ArrayList<>(); + summaries.add(enabledCategories.get(0)); + summaries.add(enabledCategories.get(1).toLowerCase()); + summaries.add(enabledCategories.get(2).toLowerCase()); + + return ListFormatter.getInstance().format(summaries); } else { - String secondaryText = mContext.getString(R.string.join_many_items_middle, - enabledCategories.get(0), enabledCategories.get(1).toLowerCase()); - secondaryText = mContext.getString(R.string.join_many_items_middle, secondaryText, - enabledCategories.get(2).toLowerCase()); - return mContext.getString(R.string.join_many_items_last, secondaryText, - mContext.getString(R.string.zen_mode_other_options)); + final List summaries = new ArrayList<>(); + summaries.add(enabledCategories.get(0)); + summaries.add(enabledCategories.get(1).toLowerCase()); + summaries.add(enabledCategories.get(2).toLowerCase()); + summaries.add(mContext.getString(R.string.zen_mode_other_options)); + + return ListFormatter.getInstance().format(summaries); } } diff --git a/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java index 83ab0377256..0a7f7269ca7 100644 --- a/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java +++ b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java @@ -28,7 +28,6 @@ import com.android.settingslib.core.lifecycle.Lifecycle; public class ZenModeVisEffectsCustomPreferenceController extends AbstractZenModePreferenceController { - protected boolean mShowMenuSelected; protected static final int INTERRUPTIVE_EFFECTS = Policy.SUPPRESSED_EFFECT_AMBIENT | Policy.SUPPRESSED_EFFECT_PEEK | Policy.SUPPRESSED_EFFECT_LIGHTS @@ -41,11 +40,7 @@ public class ZenModeVisEffectsCustomPreferenceController @Override public boolean isAvailable() { - if (mShowMenuSelected) { - return true; - } - - return areCustomOptionsSelected(); + return true; } @Override @@ -64,10 +59,6 @@ public class ZenModeVisEffectsCustomPreferenceController }); } - protected void setShownByMenu(boolean shown) { - mShowMenuSelected = shown; - } - protected boolean areCustomOptionsSelected() { boolean allEffectsSuppressed = Policy.areAllVisualEffectsSuppressed(mBackend.mPolicy.suppressedVisualEffects); diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java index 5b2782fb6b7..39e9271fa2f 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java @@ -134,13 +134,13 @@ public class ZenModeSettingsTest { public void testGetCallsSettingSummary_contacts() { Policy policy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_CONTACTS, 0, 0); - assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From contacts only"); + assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From contacts"); } @Test public void testGetCallsSettingSummary_repeatCallers() { Policy policy = new Policy(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0); - assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From repeat callers only"); + assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From repeat callers"); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java index 14de98cdf27..b1692bc7629 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java @@ -88,23 +88,14 @@ public class ZenModeVisEffectsCustomPreferenceControllerTest { } @Test - public void isAvailable_menuOff_noVisEffects() { + public void isAvailable_noVisEffects() { mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); - mController.mShowMenuSelected = false; - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void isAvailable_menuOn_noVisEffects() { - mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); - mController.mShowMenuSelected = true; assertThat(mController.isAvailable()).isTrue(); } @Test - public void isAvailable_menuOn_visEffects() { + public void isAvailable_visEffects() { mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); - mController.mShowMenuSelected = false; assertThat(mController.isAvailable()).isTrue(); } From 2e76a9ba541ed07cb544fe4b3d8be59de7598266 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Mon, 14 May 2018 11:14:19 -0400 Subject: [PATCH 07/17] Protect against multiple starts of device index job Its rare, but can happen. Test: existing tests pass Change-Id: Ie7aaa47902741d5f7fdd954f35e8f13e9466e07c Fixes: 78898947 --- .../settings/search/DeviceIndexUpdateJobService.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/search/DeviceIndexUpdateJobService.java b/src/com/android/settings/search/DeviceIndexUpdateJobService.java index 12a9cf00fcb..510da3a6226 100644 --- a/src/com/android/settings/search/DeviceIndexUpdateJobService.java +++ b/src/com/android/settings/search/DeviceIndexUpdateJobService.java @@ -53,10 +53,12 @@ public class DeviceIndexUpdateJobService extends JobService { @Override public boolean onStartJob(JobParameters params) { if (DEBUG) Log.d(TAG, "onStartJob"); - mRunningJob = true; - Thread thread = new Thread(() -> updateIndex(params)); - thread.setPriority(Thread.MIN_PRIORITY); - thread.start(); + if (!mRunningJob) { + mRunningJob = true; + Thread thread = new Thread(() -> updateIndex(params)); + thread.setPriority(Thread.MIN_PRIORITY); + thread.start(); + } return true; } From 6885d85a7c3350d0b601e1e39d64a0c6c8912afa Mon Sep 17 00:00:00 2001 From: Naina Nalluri Date: Wed, 2 May 2018 14:30:11 -0700 Subject: [PATCH 08/17] Add Slices for WifiCalling Add Slices for WifiCalling WifiCalling Slice Provider: 1. If there is no activation needed or if the Wifi calling is currently turned on - provide the slice to toggle the value 2. Else display appropriate message with further instructions WifiCalling Slice Broadcast Receiver: 1. If the action is turning off wifi or if there is no activation needed. Change the setting with ImsManager. 2. And Ask to requery the slice in one second to display updated settings if 1 is valid or display appropriate message Bug: 63731862 Bug: 78192106 Test: Use support-slices-demos-debug.apk to test on device Test: Robotests Change-Id: I29e1822fd24ebcff575fa48ad93f84ed91bf4d87 --- res/values/strings.xml | 11 + .../slices/SettingsSliceProvider.java | 13 + .../slices/SliceBroadcastReceiver.java | 7 + .../slices/SlicesFeatureProvider.java | 9 +- .../slices/SlicesFeatureProviderImpl.java | 8 +- .../wifi/calling/WifiCallingSliceHelper.java | 363 ++++++++++++++++++ .../calling/WifiCallingSliceHelperTest.java | 316 +++++++++++++++ 7 files changed, 725 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java create mode 100644 tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 5704c22fddd..b4d55a9ef77 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2355,6 +2355,17 @@ Learn more about Private DNS features + + Setting managed by carrier + + Activate Wi\u2011Fi Calling + + Turn on Wi\u2011Fi calling + + Wi\u2011Fi calling is not supported for %1$s + + Carrier Display diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index edeb2b5f28c..7eecee92cb7 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -18,6 +18,8 @@ package com.android.settings.slices; import static android.Manifest.permission.READ_SEARCH_INDEXABLES; +import static com.android.settings.wifi.calling.WifiCallingSliceHelper.PATH_WIFI_CALLING; + import android.app.PendingIntent; import android.app.slice.SliceManager; import android.content.ContentResolver; @@ -32,7 +34,13 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.utils.ThreadUtils; import java.net.URISyntaxException; @@ -160,6 +168,11 @@ public class SettingsSliceProvider extends SliceProvider { switch (path) { case "/" + PATH_WIFI: return createWifiSlice(sliceUri); + case "/" + PATH_WIFI_CALLING: + return FeatureFactory.getFactory(getContext()) + .getSlicesFeatureProvider() + .getNewWifiCallingSliceHelper(getContext()) + .createWifiCallingSlice(sliceUri); } SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java index 47a7f5a45bd..04097340ed3 100644 --- a/src/com/android/settings/slices/SliceBroadcastReceiver.java +++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java @@ -21,6 +21,7 @@ import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CH import static com.android.settings.slices.SettingsSliceProvider.ACTION_WIFI_CHANGED; import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY; import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED; +import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED; import android.app.slice.Slice; import android.content.BroadcastReceiver; @@ -79,6 +80,12 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { context.getContentResolver().notifyChange(uri, null); }, 1000); break; + case ACTION_WIFI_CALLING_CHANGED: + FeatureFactory.getFactory(context) + .getSlicesFeatureProvider() + .getNewWifiCallingSliceHelper(context) + .handleWifiCallingChanged(intent); + break; } } diff --git a/src/com/android/settings/slices/SlicesFeatureProvider.java b/src/com/android/settings/slices/SlicesFeatureProvider.java index e5bba617e48..8dd6547b398 100644 --- a/src/com/android/settings/slices/SlicesFeatureProvider.java +++ b/src/com/android/settings/slices/SlicesFeatureProvider.java @@ -2,6 +2,8 @@ package com.android.settings.slices; import android.content.Context; +import com.android.settings.wifi.calling.WifiCallingSliceHelper; + /** * Manages Slices in Settings. */ @@ -24,4 +26,9 @@ public interface SlicesFeatureProvider { * If the data is already indexed, the data will not change. */ void indexSliceData(Context context); -} \ No newline at end of file + + /** + * Gets new WifiCallingSliceHelper object + */ + WifiCallingSliceHelper getNewWifiCallingSliceHelper(Context context); +} diff --git a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java index 8e5bc067150..16684bfb022 100644 --- a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java +++ b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java @@ -2,6 +2,7 @@ package com.android.settings.slices; import android.content.Context; +import com.android.settings.wifi.calling.WifiCallingSliceHelper; import com.android.settingslib.utils.ThreadUtils; /** @@ -39,4 +40,9 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider { SlicesIndexer indexer = getSliceIndexer(context); indexer.indexSliceData(); } -} \ No newline at end of file + + @Override + public WifiCallingSliceHelper getNewWifiCallingSliceHelper(Context context) { + return new WifiCallingSliceHelper(context); + } +} diff --git a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java new file mode 100644 index 00000000000..3452af777b0 --- /dev/null +++ b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2018 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.wifi.calling; + +import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.PersistableBundle; +import androidx.core.graphics.drawable.IconCompat; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + +import com.android.ims.ImsManager; +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.slices.SliceBroadcastReceiver; +import com.android.settings.slices.SliceBuilderUtils; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * Helper class to control slices for wifi calling settings. + */ +public class WifiCallingSliceHelper { + + private static final String TAG = "WifiCallingSliceHelper"; + + /** + * Settings slice path to wifi calling setting. + */ + public static final String PATH_WIFI_CALLING = "wifi_calling"; + + /** + * Action passed for changes to wifi calling slice (toggle). + */ + public static final String ACTION_WIFI_CALLING_CHANGED = + "com.android.settings.wifi.calling.action.WIFI_CALLING_CHANGED"; + + /** + * Action for Wifi calling Settings activity which + * allows setting configuration for Wifi calling + * related settings + */ + public static final String ACTION_WIFI_CALLING_SETTINGS_ACTIVITY = + "android.settings.WIFI_CALLING_SETTINGS"; + + /** + * Timeout for querying wifi calling setting from ims manager. + */ + private static final int TIMEOUT_MILLIS = 2000; + + /** + * Time for which data contained in the slice can remain fresh. + */ + private static final int SLICE_TTL_MILLIS = 60000; + + protected SubscriptionManager mSubscriptionManager; + private final Context mContext; + + @VisibleForTesting + public WifiCallingSliceHelper(Context context) { + mContext = context; + } + + /** + * Returns Slice object for wifi calling settings. + * + * If wifi calling is being turned on and if wifi calling activation is needed for the current + * carrier, this method will return Slice with instructions to go to Settings App. + * + * If wifi calling is not supported for the current carrier, this method will return slice with + * not supported message. + * + * If wifi calling setting can be changed, this method will return the slice to toggle wifi + * calling option with ACTION_WIFI_CALLING_CHANGED as endItem. + */ + public Slice createWifiCallingSlice(Uri sliceUri) { + final int subId = getDefaultVoiceSubId(); + final String carrierName = getSimCarrierName(); + + if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + Log.d(TAG, "Invalid subscription Id"); + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString(R.string.wifi_calling_not_supported, carrierName), + sliceUri, SliceBuilderUtils.getSettingsIntent(mContext)); + } + + final ImsManager imsManager = getImsManager(subId); + + if (!imsManager.isWfcEnabledByPlatform() + || !imsManager.isWfcProvisionedOnDevice()) { + Log.d(TAG, "Wifi calling is either not provisioned or not enabled by Platform"); + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString(R.string.wifi_calling_not_supported, carrierName), + sliceUri, SliceBuilderUtils.getSettingsIntent(mContext)); + } + + try { + final boolean isWifiCallingEnabled = isWifiCallingEnabled(imsManager); + final Intent activationAppIntent = + getWifiCallingCarrierActivityIntent(subId); + + // Send this actionable wifi calling slice to toggle the setting + // only when there is no need for wifi calling activation with the server + if (activationAppIntent != null && !isWifiCallingEnabled) { + Log.d(TAG, "Needs Activation"); + // Activation needed for the next action of the user + // Give instructions to go to settings app + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString( + R.string.wifi_calling_settings_activation_instructions), + sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); + } + return getWifiCallingSlice(sliceUri, mContext, isWifiCallingEnabled); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + Log.e(TAG, "Unable to read the current WiFi calling status", e); + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString(R.string.wifi_calling_turn_on), + sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); + } + } + + private boolean isWifiCallingEnabled(ImsManager imsManager) + throws InterruptedException, ExecutionException, TimeoutException { + final FutureTask isWifiOnTask = new FutureTask<>(new Callable() { + @Override + public Boolean call() { + return imsManager.isWfcEnabledByUser(); + } + }); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(isWifiOnTask); + + Boolean isWifiEnabledByUser = false; + isWifiEnabledByUser = isWifiOnTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + + return isWifiEnabledByUser && imsManager.isNonTtyOrTtyOnVolteEnabled(); + } + + /** + * Builds a toggle slice where the intent takes you to the wifi calling page and the toggle + * enables/disables wifi calling. + */ + private Slice getWifiCallingSlice(Uri sliceUri, Context mContext, + boolean isWifiCallingEnabled) { + + final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); + final String title = mContext.getString(R.string.wifi_calling_settings_title); + return new ListBuilder(mContext, sliceUri, SLICE_TTL_MILLIS) + .setColor(R.color.material_blue_500) + .addRow(b -> b + .setTitle(title) + .addEndItem( + new SliceAction( + getBroadcastIntent(ACTION_WIFI_CALLING_CHANGED), + null /* actionTitle */, isWifiCallingEnabled)) + .setPrimaryAction(new SliceAction( + getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY), + icon, + title))) + .build(); + } + + protected ImsManager getImsManager(int subId) { + return ImsManager.getInstance(mContext, SubscriptionManager.getPhoneId(subId)); + } + + private Integer getWfcMode(ImsManager imsManager) + throws InterruptedException, ExecutionException, TimeoutException { + FutureTask wfcModeTask = new FutureTask<>(new Callable() { + @Override + public Integer call() { + return imsManager.getWfcMode(false); + } + }); + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(wfcModeTask); + return wfcModeTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } + + /** + * Handles wifi calling setting change from wifi calling slice and posts notification. Should be + * called when intent action is ACTION_WIFI_CALLING_CHANGED. Executed in @WorkerThread + * + * @param intent action performed + */ + public void handleWifiCallingChanged(Intent intent) { + final int subId = getDefaultVoiceSubId(); + + if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + final ImsManager imsManager = getImsManager(subId); + if (imsManager.isWfcEnabledByPlatform() + || imsManager.isWfcProvisionedOnDevice()) { + final boolean currentValue = imsManager.isWfcEnabledByUser() + && imsManager.isNonTtyOrTtyOnVolteEnabled(); + final boolean newValue = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, + currentValue); + final Intent activationAppIntent = + getWifiCallingCarrierActivityIntent(subId); + if (!newValue || activationAppIntent == null) { + // If either the action is to turn off wifi calling setting + // or there is no activation involved - Update the setting + if (newValue != currentValue) { + imsManager.setWfcSetting(newValue); + } + } + } + } + // notify change in slice in any case to get re-queried. This would result in displaying + // appropriate message with the updated setting. + final Uri uri = SliceBuilderUtils.getUri(PATH_WIFI_CALLING, false /*isPlatformSlice*/); + mContext.getContentResolver().notifyChange(uri, null); + } + + /** + * Returns Slice with the title and subtitle provided as arguments with wifi signal Icon. + * + * @param title Title of the slice + * @param subtitle Subtitle of the slice + * @param sliceUri slice uri + * @return Slice with title and subtitle + */ + // TODO(b/79548264) asses different scenarios and return null instead of non-actionable slice + private Slice getNonActionableWifiCallingSlice(String title, String subtitle, Uri sliceUri, + PendingIntent primaryActionIntent) { + final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); + return new ListBuilder(mContext, sliceUri, SLICE_TTL_MILLIS) + .setColor(R.color.material_blue_500) + .addRow(b -> b + .setTitle(title) + .setSubtitle(subtitle) + .setPrimaryAction(new SliceAction( + primaryActionIntent, icon, + title))) + .build(); + } + + /** + * Returns {@code true} when the key is enabled for the carrier, and {@code false} otherwise. + */ + private boolean isCarrierConfigManagerKeyEnabled(Context mContext, String key, + int subId, boolean defaultValue) { + final CarrierConfigManager configManager = getCarrierConfigManager(mContext); + boolean ret = false; + if (configManager != null) { + final PersistableBundle bundle = configManager.getConfigForSubId(subId); + if (bundle != null) { + ret = bundle.getBoolean(key, defaultValue); + } + } + return ret; + } + + protected CarrierConfigManager getCarrierConfigManager(Context mContext) { + return mContext.getSystemService(CarrierConfigManager.class); + } + + /** + * Returns the current default voice subId obtained from SubscriptionManager + */ + protected int getDefaultVoiceSubId() { + if (mSubscriptionManager == null) { + mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); + } + return SubscriptionManager.getDefaultVoiceSubscriptionId(); + } + + /** + * Returns Intent of the activation app required to activate wifi calling or null if there is no + * need for activation. + */ + protected Intent getWifiCallingCarrierActivityIntent(int subId) { + final CarrierConfigManager configManager = getCarrierConfigManager(mContext); + if (configManager == null) { + return null; + } + + final PersistableBundle bundle = configManager.getConfigForSubId(subId); + if (bundle == null) { + return null; + } + + final String carrierApp = bundle.getString( + CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING); + if (TextUtils.isEmpty(carrierApp)) { + return null; + } + + final ComponentName componentName = ComponentName.unflattenFromString(carrierApp); + if (componentName == null) { + return null; + } + + final Intent intent = new Intent(); + intent.setComponent(componentName); + return intent; + } + + private PendingIntent getBroadcastIntent(String action) { + final Intent intent = new Intent(action); + intent.setClass(mContext, SliceBroadcastReceiver.class); + return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, + PendingIntent.FLAG_CANCEL_CURRENT); + } + + /** + * Returns PendingIntent to start activity specified by action + */ + private PendingIntent getActivityIntent(String action) { + final Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); + } + + /** + * Returns carrier id name of the current Subscription + */ + private String getSimCarrierName() { + final TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); + final CharSequence carrierName = telephonyManager.getSimCarrierIdName(); + if (carrierName == null) { + return mContext.getString(R.string.carrier); + } + return carrierName.toString(); + } + +} diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java new file mode 100644 index 00000000000..ac3ff3ff635 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2018 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.wifi.calling; + +import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; +import static android.app.slice.Slice.HINT_TITLE; +import static android.app.slice.SliceItem.FORMAT_TEXT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.telephony.CarrierConfigManager; + +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.SliceMetadata; +import androidx.slice.SliceProvider; +import androidx.slice.core.SliceAction; +import androidx.slice.core.SliceQuery; +import androidx.slice.widget.SliceLiveData; + +import com.android.ims.ImsManager; +import com.android.settings.R; +import com.android.settings.slices.SettingsSliceProvider; +import com.android.settings.slices.SliceBroadcastReceiver; +import com.android.settings.slices.SliceBuilderUtils; +import com.android.settings.slices.SliceData; +import com.android.settings.slices.SlicesFeatureProvider; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class WifiCallingSliceHelperTest { + + private Context mContext; + @Mock + private CarrierConfigManager mMockCarrierConfigManager; + + @Mock + private ImsManager mMockImsManager; + + private final Uri mWfcURI = Uri.parse("content://com.android.settings.slices/wifi_calling"); + + private FakeWifiCallingSliceHelper mWfcSliceHelper; + private SettingsSliceProvider mProvider; + private SliceBroadcastReceiver mReceiver; + private FakeFeatureFactory mFeatureFactory; + private SlicesFeatureProvider mSlicesFeatureProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + //setup for SettingsSliceProvider tests + mProvider = spy(new SettingsSliceProvider()); + doReturn(mContext).when(mProvider).getContext(); + + //setup for SliceBroadcastReceiver test + mReceiver = spy(new SliceBroadcastReceiver()); + + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mSlicesFeatureProvider = mFeatureFactory.getSlicesFeatureProvider(); + + // Prevent crash in SliceMetadata. + Resources resources = spy(mContext.getResources()); + doReturn(60).when(resources).getDimensionPixelSize(anyInt()); + doReturn(resources).when(mContext).getResources(); + + mWfcSliceHelper = new FakeWifiCallingSliceHelper(mContext); + + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + } + + @Test + public void test_CreateWifiCallingSlice_invalidSubId() { + mWfcSliceHelper.setDefaultVoiceSubId(-1); + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + testWifiCallingSettingsUnavailableSlice(slice, null, + SliceBuilderUtils.getSettingsIntent(mContext)); + } + + @Test + public void test_CreateWifiCallingSlice_wfcNotSupported() { + doReturn(false).when(mMockImsManager).isWfcEnabledByPlatform(); + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsUnavailableSlice(slice, null, + SliceBuilderUtils.getSettingsIntent(mContext)); + } + + @Test + public void test_CreateWifiCallingSlice_needsActivation() { + /* In cases where activation is needed and the user action + would be turning on the wifi calling (i.e. if wifi calling is + turned off) we need to guide the user to wifi calling settings + activity so the user can perform the activation there.(PrimaryAction) + */ + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(false).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(false).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(null).when(mMockCarrierConfigManager).getConfigForSubId(1); + mWfcSliceHelper.setActivationAppIntent(new Intent()); // dummy Intent + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsUnavailableSlice(slice, null, + getActivityIntent(WifiCallingSliceHelper.ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); + } + + @Test + public void test_CreateWifiCallingSlice_success() { + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(true).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(true).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(null).when(mMockCarrierConfigManager).getConfigForSubId(1); + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsToggleSlice(slice, null); + } + + @Test + public void test_SettingSliceProvider_getsRightSliceWifiCalling() { + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(true).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(true).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(null).when(mMockCarrierConfigManager).getConfigForSubId(1); + doReturn(mWfcSliceHelper).when(mSlicesFeatureProvider) + .getNewWifiCallingSliceHelper(mContext); + + final Slice slice = mProvider.onBindSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsToggleSlice(slice, null); + } + + @Test + public void test_SliceBroadcastReceiver_toggleOffWifiCalling() { + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(false).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(true).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(mWfcSliceHelper).when(mSlicesFeatureProvider) + .getNewWifiCallingSliceHelper(mContext); + mWfcSliceHelper.setActivationAppIntent(null); + + ArgumentCaptor mWfcSettingCaptor = ArgumentCaptor.forClass(Boolean.class); + + // turn on Wifi calling setting + Intent intent = new Intent(WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED); + intent.putExtra(EXTRA_TOGGLE_STATE, true); + + // change the setting + mReceiver.onReceive(mContext, intent); + + verify((mMockImsManager)).setWfcSetting(mWfcSettingCaptor.capture()); + + // assert the change + assertThat(mWfcSettingCaptor.getValue()).isTrue(); + } + + private void testWifiCallingSettingsUnavailableSlice(Slice slice, + SliceData sliceData, PendingIntent expectedPrimaryAction) { + final SliceMetadata metadata = SliceMetadata.from(mContext, slice); + + //Check there is no toggle action + final List toggles = metadata.getToggles(); + assertThat(toggles).isEmpty(); + + // Check whether the primary action is to open wifi calling settings activity + final PendingIntent primaryPendingIntent = + metadata.getPrimaryAction().getAction(); + assertThat(primaryPendingIntent).isEqualTo(expectedPrimaryAction); + + // Check the title + final List sliceItems = slice.getItems(); + assertTitle(sliceItems, mContext.getString(R.string.wifi_calling_settings_title)); + } + + private void testWifiCallingSettingsToggleSlice(Slice slice, + SliceData sliceData) { + final SliceMetadata metadata = SliceMetadata.from(mContext, slice); + + final List toggles = metadata.getToggles(); + assertThat(toggles).hasSize(1); + + final SliceAction mainToggleAction = toggles.get(0); + + // Check intent in Toggle Action + final PendingIntent togglePendingIntent = mainToggleAction.getAction(); + final PendingIntent expectedToggleIntent = getBroadcastIntent( + WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED); + assertThat(togglePendingIntent).isEqualTo(expectedToggleIntent); + + // Check primary intent + final PendingIntent primaryPendingIntent = metadata.getPrimaryAction().getAction(); + final PendingIntent expectedPendingIntent = + getActivityIntent(WifiCallingSliceHelper.ACTION_WIFI_CALLING_SETTINGS_ACTIVITY); + assertThat(primaryPendingIntent).isEqualTo(expectedPendingIntent); + + // Check the title + final List sliceItems = slice.getItems(); + assertTitle(sliceItems, mContext.getString(R.string.wifi_calling_settings_title)); + } + + private PendingIntent getBroadcastIntent(String action) { + final Intent intent = new Intent(action); + intent.setClass(mContext, SliceBroadcastReceiver.class); + return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, + PendingIntent.FLAG_CANCEL_CURRENT); + } + + private PendingIntent getActivityIntent(String action) { + final Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); + } + + private void assertTitle(List sliceItems, String title) { + boolean hasTitle = false; + for (SliceItem item : sliceItems) { + List titleItems = SliceQuery.findAll(item, FORMAT_TEXT, HINT_TITLE, + null /* non-hints */); + if (titleItems == null) { + continue; + } + + hasTitle = true; + for (SliceItem subTitleItem : titleItems) { + assertThat(subTitleItem.getText()).isEqualTo(title); + } + } + assertThat(hasTitle).isTrue(); + } + private class FakeWifiCallingSliceHelper extends WifiCallingSliceHelper { + int mSubId = 1; + + private Intent mActivationAppIntent; + FakeWifiCallingSliceHelper(Context context) { + super(context); + mActivationAppIntent = null; + } + + @Override + protected CarrierConfigManager getCarrierConfigManager(Context mContext) { + return mMockCarrierConfigManager; + } + + @Override + protected ImsManager getImsManager(int subId) { + return mMockImsManager; + } + + protected int getDefaultVoiceSubId() { + return mSubId; + } + + protected void setDefaultVoiceSubId(int id) { + mSubId = id; + } + + @Override + protected Intent getWifiCallingCarrierActivityIntent(int subId) { + return mActivationAppIntent; + } + + public void setActivationAppIntent(Intent intent) { + mActivationAppIntent = intent; + } + } +} From 8f5225896266c4b95ed94b38f2cf629df5104f66 Mon Sep 17 00:00:00 2001 From: Beverly Date: Wed, 9 May 2018 15:27:45 -0400 Subject: [PATCH 09/17] Add DND Settings suggestion Cherry-picked from pi-dev ag/4019736 Test: SettingsSuggestionsTest, ZenOnboardingActivityTest Change-Id: Ie78c9cf8287ee56bc4596efe20d27f8eb432ab6c Bug: 78445134 --- AndroidManifest.xml | 23 ++++- res/drawable/ic_zen.xml | 12 +-- res/values/strings.xml | 6 ++ .../SuggestionFeatureProviderImpl.java | 5 ++ .../settings/notification/ZenModeBackend.java | 3 + .../notification/ZenOnboardingActivity.java | 89 ++++++++++++++++++- .../notification/ZenSuggestionActivity.java | 24 +++++ .../ZenOnboardingActivityTest.java | 88 +++++++++++++++++- .../suggestions/SettingsSuggestionsTest.java | 10 +++ .../NewDeviceIntroSuggestionActivityTest.java | 2 +- 10 files changed, 248 insertions(+), 14 deletions(-) create mode 100644 src/com/android/settings/notification/ZenSuggestionActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 92819d86ccd..6d8e408235f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -742,6 +742,28 @@ android:value="true" /> + + + + + + + + + + + + + + + - + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:height="24dp" + android:width="24dp" > + android:fillColor="?android:attr/colorControlActivated" + android:pathData="M 12 2 C 6.48 2 2 6.48 2 12 s 4.48 10 10 10 10 -4.48 10 -10 S 17.52 2 12 2 z m 4 11 H 8 c -.55 0 -1 -.45 -1 -1 s .45 -1 1 -1 h 8c.55 0 1 .45 1 1 s -.45 1 -1 1z" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index 252a601c794..7a11dc69586 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9882,6 +9882,12 @@ + + Update Do Not Disturb + + + Hide notifications to stay focused + What\'s new and exciting? diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java index a9c7713e4d8..e4c99cd87ed 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.service.settings.suggestions.Suggestion; + import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import android.util.Log; @@ -32,6 +33,8 @@ import com.android.settings.Settings.NightDisplaySuggestionActivity; import com.android.settings.display.NightDisplayPreferenceController; import com.android.settings.fingerprint.FingerprintEnrollSuggestionActivity; import com.android.settings.fingerprint.FingerprintSuggestionActivity; +import com.android.settings.notification.ZenOnboardingActivity; +import com.android.settings.notification.ZenSuggestionActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ScreenLockSuggestionActivity; import com.android.settings.support.NewDeviceIntroSuggestionActivity; @@ -88,6 +91,8 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider return NightDisplayPreferenceController.isSuggestionComplete(context); } else if (className.equals(NewDeviceIntroSuggestionActivity.class.getName())) { return NewDeviceIntroSuggestionActivity.isSuggestionComplete(context); + } else if (className.equals(ZenSuggestionActivity.class.getName())) { + return ZenOnboardingActivity.isSuggestionComplete(context); } return false; } diff --git a/src/com/android/settings/notification/ZenModeBackend.java b/src/com/android/settings/notification/ZenModeBackend.java index cb8a048c90c..8c8f14df671 100644 --- a/src/com/android/settings/notification/ZenModeBackend.java +++ b/src/com/android/settings/notification/ZenModeBackend.java @@ -135,6 +135,9 @@ public class ZenModeBackend { } protected void saveVisualEffectsPolicy(int category, boolean suppress) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ZEN_SETTINGS_UPDATED, 1); + int suppressedEffects = getNewSuppressedEffects(suppress, category); savePolicy(mPolicy.priorityCategories, mPolicy.priorityCallSenders, mPolicy.priorityMessageSenders, suppressedEffects); diff --git a/src/com/android/settings/notification/ZenOnboardingActivity.java b/src/com/android/settings/notification/ZenOnboardingActivity.java index 2215a1d9d7c..c4cc355c95a 100644 --- a/src/com/android/settings/notification/ZenOnboardingActivity.java +++ b/src/com/android/settings/notification/ZenOnboardingActivity.java @@ -19,17 +19,34 @@ package com.android.settings.notification; import android.app.Activity; import android.app.NotificationManager; import android.app.NotificationManager.Policy; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.provider.Settings; -import androidx.annotation.VisibleForTesting; +import android.text.format.DateUtils; +import android.util.Log; import android.view.View; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; +import com.android.settings.overlay.FeatureFactory; + +import androidx.annotation.VisibleForTesting; public class ZenOnboardingActivity extends Activity { + private static final String TAG = "ZenOnboardingActivity"; + + @VisibleForTesting + static final String PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME = + "pref_zen_suggestion_first_display_time_ms"; + @VisibleForTesting + static final String PREF_KEY_SUGGESTION_VIEWED = "pref_zen_suggestion_viewed"; + @VisibleForTesting + static final long ALWAYS_SHOW_THRESHOLD = DateUtils.DAY_IN_MILLIS * 14; + private NotificationManager mNm; private MetricsLogger mMetrics; @@ -39,6 +56,10 @@ public class ZenOnboardingActivity extends Activity { setNotificationManager(getSystemService(NotificationManager.class)); setMetricsLogger(new MetricsLogger()); + Context context = getApplicationContext(); + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.ZEN_SETTINGS_SUGGESTION_VIEWED, 1); + setupUI(); } @@ -61,20 +82,80 @@ public class ZenOnboardingActivity extends Activity { public void close(View button) { mMetrics.action(MetricsEvent.ACTION_ZEN_ONBOARDING_KEEP_CURRENT_SETTINGS); + + Settings.Global.putInt(getApplicationContext().getContentResolver(), + Settings.Global.ZEN_SETTINGS_UPDATED, 1); + finishAndRemoveTask(); } public void save(View button) { mMetrics.action(MetricsEvent.ACTION_ZEN_ONBOARDING_OK); - Policy policy = mNm.getNotificationPolicy(); + NotificationManager.Policy policy = mNm.getNotificationPolicy(); - Policy newPolicy = new NotificationManager.Policy( + NotificationManager.Policy newPolicy = new NotificationManager.Policy( Policy.PRIORITY_CATEGORY_REPEAT_CALLERS | policy.priorityCategories, Policy.PRIORITY_SENDERS_STARRED, policy.priorityMessageSenders, - Policy.getAllSuppressedVisualEffects()); + NotificationManager.Policy.getAllSuppressedVisualEffects()); mNm.setNotificationPolicy(newPolicy); + Settings.Global.putInt(getApplicationContext().getContentResolver(), + Settings.Global.ZEN_SETTINGS_UPDATED, 1); + finishAndRemoveTask(); } + + public static boolean isSuggestionComplete(Context context) { + if (wasZenUpdated(context)) { + return true; + } + + if (showSuggestion(context) || withinShowTimeThreshold(context)) { + return false; + } + + return true; + } + + private static boolean wasZenUpdated(Context context) { + // ZEN_SETTINGS_UPDATED is true for: + // - fresh P+ device + // - if zen visual effects values were changed by the user in Settings + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ZEN_SETTINGS_UPDATED, 0) != 0; + } + + private static boolean showSuggestion(Context context) { + // SHOW_ZEN_SETTINGS_SUGGESTION is by default true, but false when: + // - user manually turns on dnd + + // SHOW_ZEN_SETTINGS_SUGGESTION is also true when: + // - automatic rule has started DND and user has not seen the first use dialog + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.SHOW_ZEN_SETTINGS_SUGGESTION, 0) != 0; + + } + + private static boolean withinShowTimeThreshold(Context context) { + final SuggestionFeatureProvider featureProvider = FeatureFactory.getFactory(context) + .getSuggestionFeatureProvider(context); + final SharedPreferences prefs = featureProvider.getSharedPrefs(context); + final long currentTimeMs = System.currentTimeMillis(); + final long firstDisplayTimeMs; + + if (!prefs.contains(PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME)) { + firstDisplayTimeMs = currentTimeMs; + prefs.edit().putLong(PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME, currentTimeMs).commit(); + } else { + firstDisplayTimeMs = prefs.getLong(PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME, -1); + } + + final long showTimeMs = firstDisplayTimeMs + ALWAYS_SHOW_THRESHOLD; + final boolean stillShow = currentTimeMs < showTimeMs; + + Log.d(TAG, "still show zen suggestion based on time: " + stillShow + " showTimeMs=" + + showTimeMs); + return stillShow; + } } diff --git a/src/com/android/settings/notification/ZenSuggestionActivity.java b/src/com/android/settings/notification/ZenSuggestionActivity.java new file mode 100644 index 00000000000..9d2148c7e50 --- /dev/null +++ b/src/com/android/settings/notification/ZenSuggestionActivity.java @@ -0,0 +1,24 @@ +package com.android.settings.notification; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; + +public class ZenSuggestionActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // start up zen settings activity + Intent settingsIntent = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + startActivity(settingsIntent); + + // start up onboarding activity + Intent onboardingActivity = new Intent(Settings.ZEN_MODE_ONBOARDING); + startActivity(onboardingActivity); + + finish(); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java b/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java index 7bc93713977..7fb02a535cc 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java @@ -29,6 +29,11 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OF import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static com.android.settings.notification.ZenOnboardingActivity.ALWAYS_SHOW_THRESHOLD; +import static com.android.settings.notification.ZenOnboardingActivity + .PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME; +import static com.android.settings.notification.ZenOnboardingActivity.isSuggestionComplete; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -39,20 +44,22 @@ import static org.mockito.Mockito.when; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.Context; +import android.content.SharedPreferences; +import android.provider.Settings; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.R; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; @RunWith(SettingsRobolectricTestRunner.class) public class ZenOnboardingActivityTest { @@ -64,6 +71,9 @@ public class ZenOnboardingActivityTest { ZenOnboardingActivity mActivity; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -75,6 +85,11 @@ public class ZenOnboardingActivityTest { mActivity.setMetricsLogger(mMetricsLogger); mActivity.setupUI(); + + mContext = RuntimeEnvironment.application; + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mFeatureFactory.suggestionsFeatureProvider.getSharedPrefs(any(Context.class))) + .thenReturn(getSharedPreferences()); } @Test @@ -124,4 +139,73 @@ public class ZenOnboardingActivityTest { verify(mNm, never()).setNotificationPolicy(any()); } + + @Test + public void isSuggestionComplete_zenUpdated() { + setZenUpdated(true); + setShowSettingsSuggestion(false); + setWithinTimeThreshold(true); + assertThat(isSuggestionComplete(mContext)).isTrue(); + } + + @Test + public void isSuggestionComplete_withinTimeThreshold() { + setZenUpdated(false); + setShowSettingsSuggestion(false); + setWithinTimeThreshold(true); + assertThat(isSuggestionComplete(mContext)).isFalse(); + } + + @Test + public void isSuggestionComplete_showSettingsSuggestionTrue() { + setZenUpdated(false); + setShowSettingsSuggestion(true); + setWithinTimeThreshold(false); + assertThat(isSuggestionComplete(mContext)).isFalse(); + } + + @Test + public void isSuggestionComplete_showSettingsSuggestionFalse_notWithinTimeThreshold() { + setZenUpdated(false); + setShowSettingsSuggestion(false); + setWithinTimeThreshold(false); + assertThat(isSuggestionComplete(mContext)).isTrue(); + } + + private void setZenUpdated(boolean updated) { + int zenUpdated = 0; + if (updated) { + zenUpdated = 1; + } + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ZEN_SETTINGS_UPDATED, zenUpdated); + } + + private void setWithinTimeThreshold(boolean withinTime) { + long firstTime = System.currentTimeMillis(); + + if (withinTime) { + firstTime -= ALWAYS_SHOW_THRESHOLD / 2; + } else { + firstTime -= ALWAYS_SHOW_THRESHOLD * 2; + } + + getSharedPreferences().edit().putLong(PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME, + firstTime).commit(); + } + + private void setShowSettingsSuggestion(boolean show) { + int showZenSuggestion = 0; + if (show) { + showZenSuggestion = 1; + } + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.SHOW_ZEN_SETTINGS_SUGGESTION, showZenSuggestion); + } + + private SharedPreferences getSharedPreferences() { + return mContext.getSharedPreferences("test_zen_sugg", Context.MODE_PRIVATE); + } } diff --git a/tests/robotests/src/com/android/settings/suggestions/SettingsSuggestionsTest.java b/tests/robotests/src/com/android/settings/suggestions/SettingsSuggestionsTest.java index bb14667fce8..c6583c7eb03 100644 --- a/tests/robotests/src/com/android/settings/suggestions/SettingsSuggestionsTest.java +++ b/tests/robotests/src/com/android/settings/suggestions/SettingsSuggestionsTest.java @@ -30,6 +30,8 @@ import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.fingerprint.FingerprintEnrollSuggestionActivity; import com.android.settings.fingerprint.FingerprintSuggestionActivity; +import com.android.settings.notification.ZenOnboardingActivity; +import com.android.settings.notification.ZenSuggestionActivity; import com.android.settings.support.NewDeviceIntroSuggestionActivity; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.wallpaper.WallpaperSuggestionActivity; @@ -82,6 +84,14 @@ public class SettingsSuggestionsTest { R.string.night_display_suggestion_summary); } + @Test + public void zenSuggestion_isValid() { + assertSuggestionEquals( + ZenSuggestionActivity.class.getName(), + R.string.zen_suggestion_title, + R.string.zen_suggestion_summary); + } + @Test public void newDeviceIntroSuggestion_isValid() { assertSuggestionEquals( diff --git a/tests/robotests/src/com/android/settings/support/NewDeviceIntroSuggestionActivityTest.java b/tests/robotests/src/com/android/settings/support/NewDeviceIntroSuggestionActivityTest.java index 15975689c7e..286676dd0d0 100644 --- a/tests/robotests/src/com/android/settings/support/NewDeviceIntroSuggestionActivityTest.java +++ b/tests/robotests/src/com/android/settings/support/NewDeviceIntroSuggestionActivityTest.java @@ -78,7 +78,7 @@ public class NewDeviceIntroSuggestionActivityTest { final long currentTime = System.currentTimeMillis(); getSharedPreferences().edit().putLong(PREF_KEY_SUGGGESTION_FIRST_DISPLAY_TIME, - currentTime - 2 * PERMANENT_DISMISS_THRESHOLD); + currentTime - 2 * PERMANENT_DISMISS_THRESHOLD).commit(); assertThat(isSuggestionComplete(mContext)).isTrue(); } From 55d6aebcc5ab11a6b1dcdefb868cfe5aa8e216ac Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 14 May 2018 14:12:38 -0400 Subject: [PATCH 10/17] Always show custom zen vis eff option Cherry-picked from pi-dev ag/4044915 + dnd string edits Change-Id: I2fcbe6c527b40610f997efacb08049c07b3a6dd7 Fixes: 79538038 Bug: 78447976 Test: ZenModeViseffectsCustomPreferenceControllerTes --- res/values/strings.xml | 17 ++++- res/xml/zen_mode_calls_settings.xml | 4 +- .../zen_mode_msg_event_reminder_settings.xml | 6 +- .../ZenModeRestrictNotificationsSettings.java | 63 ++----------------- .../notification/ZenModeSettings.java | 2 +- ...eVisEffectsCustomPreferenceController.java | 11 +--- .../notification/ZenModeSettingsTest.java | 4 +- ...EffectsCustomPreferenceControllerTest.java | 13 +--- 8 files changed, 32 insertions(+), 88 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 7a11dc69586..165126f2602 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7930,6 +7930,9 @@ Calls + + Allow calls + When Do Not Disturb is on, incoming calls are blocked. You can adjust settings to allow your friends, family, or other contacts to reach you. @@ -7945,6 +7948,9 @@ Messages + + Allow messages + Messages @@ -7984,9 +7990,15 @@ Reminders + + Allow reminders + Events + + Allow events + anyone @@ -7999,8 +8011,11 @@ Repeat callers + + Allow repeat callers + - From %1$s only + From %1$s From %1$s and %2$s diff --git a/res/xml/zen_mode_calls_settings.xml b/res/xml/zen_mode_calls_settings.xml index dd6fcc50c0b..2da022c56a1 100644 --- a/res/xml/zen_mode_calls_settings.xml +++ b/res/xml/zen_mode_calls_settings.xml @@ -26,7 +26,7 @@ @@ -37,7 +37,7 @@ + android:title="@string/zen_mode_repeat_callers_title" /> diff --git a/res/xml/zen_mode_msg_event_reminder_settings.xml b/res/xml/zen_mode_msg_event_reminder_settings.xml index d12fc624461..8fc6b4717be 100644 --- a/res/xml/zen_mode_msg_event_reminder_settings.xml +++ b/res/xml/zen_mode_msg_event_reminder_settings.xml @@ -26,7 +26,7 @@ @@ -37,12 +37,12 @@ + android:title="@string/zen_mode_reminders_title"/> + android:title="@string/zen_mode_events_title"/> diff --git a/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java b/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java index a2a0ba7f008..737c646f929 100644 --- a/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java +++ b/src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java @@ -16,22 +16,15 @@ package com.android.settings.notification; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ZEN_SHOW_CUSTOM; - import android.content.Context; import android.os.Bundle; import android.provider.SearchIndexableResource; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; -import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.widget.FooterPreference; @@ -42,67 +35,21 @@ import java.util.List; @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) public class ZenModeRestrictNotificationsSettings extends ZenModeSettingsBase implements Indexable { - protected static final int APP_MENU_SHOW_CUSTOM = 1; - protected boolean mShowMenuSelected; - @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, APP_MENU_SHOW_CUSTOM, 0, R.string.zen_mode_restrict_notifications_enable_custom) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - if (menuItem.getItemId() == APP_MENU_SHOW_CUSTOM) { - final FeatureFactory featureFactory = FeatureFactory.getFactory(mContext); - MetricsFeatureProvider metrics = featureFactory.getMetricsFeatureProvider(); - - mShowMenuSelected = !mShowMenuSelected; - - ZenModeVisEffectsCustomPreferenceController custom = - use(ZenModeVisEffectsCustomPreferenceController.class); - custom.setShownByMenu(mShowMenuSelected); - custom.displayPreference(getPreferenceScreen()); - - if (mShowMenuSelected) { - metrics.action(mContext, ACTION_ZEN_SHOW_CUSTOM, true); - } else { - metrics.action(mContext, ACTION_ZEN_SHOW_CUSTOM, false); - } - - return true; - } - return false; - } - - @Override - public void onPrepareOptionsMenu(Menu menu) { - if (mShowMenuSelected) { - menu.findItem(APP_MENU_SHOW_CUSTOM) - .setTitle(R.string.zen_mode_restrict_notifications_disable_custom); - } else { - menu.findItem(APP_MENU_SHOW_CUSTOM) - .setTitle(R.string.zen_mode_restrict_notifications_enable_custom); - } - - if (mShowMenuSelected && use(ZenModeVisEffectsCustomPreferenceController.class) - .areCustomOptionsSelected()) { - menu.findItem(APP_MENU_SHOW_CUSTOM).setEnabled(false); - } else { - menu.findItem(APP_MENU_SHOW_CUSTOM).setEnabled(true); - } - } - @Override protected List createPreferenceControllers(Context context) { return buildPreferenceControllers(context, getLifecycle()); } + @Override + public int getHelpResource() { + return R.string.help_uri_interruptions; + } + private static List buildPreferenceControllers(Context context, Lifecycle lifecycle) { List controllers = new ArrayList<>(); diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index 3fb5a4b9be6..240ab68c93a 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -170,7 +170,7 @@ public class ZenModeSettings extends ZenModeSettingsBase { } else if (numCategories == 2) { return mContext.getString(R.string.join_two_items, enabledCategories.get(0), enabledCategories.get(1).toLowerCase()); - } else if (numCategories == 3) { + } else if (numCategories == 3){ final List summaries = new ArrayList<>(); summaries.add(enabledCategories.get(0)); summaries.add(enabledCategories.get(1).toLowerCase()); diff --git a/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java index 5baf0fac11d..5dd99f61568 100644 --- a/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java +++ b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java @@ -30,7 +30,6 @@ import com.android.settingslib.core.lifecycle.Lifecycle; public class ZenModeVisEffectsCustomPreferenceController extends AbstractZenModePreferenceController { - protected boolean mShowMenuSelected; protected static final int INTERRUPTIVE_EFFECTS = Policy.SUPPRESSED_EFFECT_AMBIENT | Policy.SUPPRESSED_EFFECT_PEEK | Policy.SUPPRESSED_EFFECT_LIGHTS @@ -43,11 +42,7 @@ public class ZenModeVisEffectsCustomPreferenceController @Override public boolean isAvailable() { - if (mShowMenuSelected) { - return true; - } - - return areCustomOptionsSelected(); + return true; } @Override @@ -67,10 +62,6 @@ public class ZenModeVisEffectsCustomPreferenceController }); } - protected void setShownByMenu(boolean shown) { - mShowMenuSelected = shown; - } - protected boolean areCustomOptionsSelected() { boolean allEffectsSuppressed = Policy.areAllVisualEffectsSuppressed(mBackend.mPolicy.suppressedVisualEffects); diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java index 5b2782fb6b7..39e9271fa2f 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java @@ -134,13 +134,13 @@ public class ZenModeSettingsTest { public void testGetCallsSettingSummary_contacts() { Policy policy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_CONTACTS, 0, 0); - assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From contacts only"); + assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From contacts"); } @Test public void testGetCallsSettingSummary_repeatCallers() { Policy policy = new Policy(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0); - assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From repeat callers only"); + assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("From repeat callers"); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java index f3d92d17d21..45446229930 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java @@ -88,23 +88,14 @@ public class ZenModeVisEffectsCustomPreferenceControllerTest { } @Test - public void isAvailable_menuOff_noVisEffects() { + public void isAvailable_noVisEffects() { mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); - mController.mShowMenuSelected = false; - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void isAvailable_menuOn_noVisEffects() { - mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); - mController.mShowMenuSelected = true; assertThat(mController.isAvailable()).isTrue(); } @Test - public void isAvailable_menuOn_visEffects() { + public void isAvailable_visEffects() { mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); - mController.mShowMenuSelected = false; assertThat(mController.isAvailable()).isTrue(); } From 5c467566f1a84a86befaa2842d2a8a2e7d7e85cc Mon Sep 17 00:00:00 2001 From: tmfang Date: Wed, 25 Apr 2018 15:58:54 +0800 Subject: [PATCH 11/17] Slices feature supports device model. - Migrate DeviceModelPreferenceController to BasePreferenceController - Because Slice feature enables strict mode, we can't do I/O(DeviceInfoUtils.getMsvSuffix) on main thread. Using FutureTask to get around the foreground thread restriction. - Modify test case for new device model controller. Test: slice uri > content://android.settings.slices/intent/my_device_model Test: make RunSettingsRoboTests -j atest UniquePreferenceTest SettingsGatewayTest Change-Id: Idafc132dfa14138ad5a7edd58e63c4b0f644c994 --- res/xml/device_info_settings.xml | 5 +- res/xml/my_device_info.xml | 4 +- .../deviceinfo/DeviceInfoSettings.java | 5 +- .../DeviceModelPreferenceController.java | 69 ++++++++++++------- .../aboutphone/MyDeviceInfoFragment.java | 2 +- .../DeviceModelPreferenceControllerTest.java | 60 ++++++++++------ 6 files changed, 95 insertions(+), 50 deletions(-) diff --git a/res/xml/device_info_settings.xml b/res/xml/device_info_settings.xml index d364d9e99ab..5f1ce123be3 100644 --- a/res/xml/device_info_settings.xml +++ b/res/xml/device_info_settings.xml @@ -38,11 +38,12 @@ + android:summary="@string/summary_placeholder" + settings:controller="com.android.settings.deviceinfo.DeviceModelPreferenceController" /> + android:summary="@string/summary_placeholder" + settings:controller= + "com.android.settings.deviceinfo.DeviceModelPreferenceController" /> controllers = new ArrayList<>(); controllers.add(new PhoneNumberPreferenceController(context)); controllers.add(new SimStatusPreferenceController(context, fragment)); - controllers.add(new DeviceModelPreferenceController(context, fragment)); controllers.add(new ImeiInfoPreferenceController(context, fragment)); controllers.add(new IpAddressPreferenceController(context, lifecycle)); controllers.add(new WifiMacAddressPreferenceController(context, lifecycle)); diff --git a/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java b/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java index 1fc54cd6755..4fa7afc0a15 100644 --- a/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java +++ b/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java @@ -18,50 +18,54 @@ package com.android.settings.deviceinfo; import android.app.Fragment; import android.content.Context; import android.os.Build; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; import android.text.TextUtils; +import android.util.Log; import com.android.settings.R; -import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.BasePreferenceController; import com.android.settingslib.DeviceInfoUtils; -import com.android.settingslib.core.AbstractPreferenceController; -public class DeviceModelPreferenceController extends AbstractPreferenceController implements - PreferenceControllerMixin { +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; - private static final String KEY_DEVICE_MODEL = "device_model"; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; - private final Fragment mHost; - public DeviceModelPreferenceController(Context context, Fragment host) { - super(context); - mHost = host; +public class DeviceModelPreferenceController extends BasePreferenceController { + + private static final String TAG = "DeviceModelPrefCtrl"; + + private Fragment mHost; + + public DeviceModelPreferenceController(Context context, String key) { + super(context, key); } - @Override - public boolean isAvailable() { - return mContext.getResources().getBoolean(R.bool.config_show_device_model); + public void setHost(Fragment fragment) { + mHost = fragment; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - final Preference pref = screen.findPreference(KEY_DEVICE_MODEL); - if (pref != null) { - pref.setSummary(mContext.getResources().getString(R.string.model_summary, - getDeviceModel())); - } } @Override - public String getPreferenceKey() { - return KEY_DEVICE_MODEL; + public int getAvailabilityStatus() { + return mContext.getResources().getBoolean(R.bool.config_show_device_model) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public CharSequence getSummary() { + return mContext.getResources().getString(R.string.model_summary, getDeviceModel()); } @Override public boolean handlePreferenceTreeClick(Preference preference) { - if (!TextUtils.equals(preference.getKey(), KEY_DEVICE_MODEL)) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { return false; } final HardwareInfoDialogFragment fragment = HardwareInfoDialogFragment.newInstance(); @@ -70,6 +74,25 @@ public class DeviceModelPreferenceController extends AbstractPreferenceControlle } public static String getDeviceModel() { - return Build.MODEL + DeviceInfoUtils.getMsvSuffix(); + FutureTask msvSuffixTask = new FutureTask(new Callable() { + @Override + public String call() { + return DeviceInfoUtils.getMsvSuffix(); + } + }); + + msvSuffixTask.run(); + try { + // Wait for msv suffix value. + final String msvSuffix = msvSuffixTask.get(); + return Build.MODEL + msvSuffix; + } catch (ExecutionException e) { + Log.e(TAG, "Execution error, so we only show model name"); + } catch (InterruptedException e) { + Log.e(TAG, "Interruption error, so we only show model name"); + } + // If we can't get an msv suffix value successfully, + // it's better to return model name. + return Build.MODEL; } } diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java index bdafa82c95d..00be8be0a01 100644 --- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java +++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java @@ -82,6 +82,7 @@ public class MyDeviceInfoFragment extends DashboardFragment public void onAttach(Context context) { super.onAttach(context); use(FirmwareVersionPreferenceController.class).setHost(this /*parent*/); + use(DeviceModelPreferenceController.class).setHost(this /* parent */); } @Override @@ -122,7 +123,6 @@ public class MyDeviceInfoFragment extends DashboardFragment } controllers.add(deviceNamePreferenceController); controllers.add(new SimStatusPreferenceController(context, fragment)); - controllers.add(new DeviceModelPreferenceController(context, fragment)); controllers.add(new ImeiInfoPreferenceController(context, fragment)); controllers.add(new IpAddressPreferenceController(context, lifecycle)); controllers.add(new WifiMacAddressPreferenceController(context, lifecycle)); diff --git a/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java index c91683e00d4..dbffc3182e1 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java @@ -15,40 +15,44 @@ */ package com.android.settings.deviceinfo; -import static com.android.settings.deviceinfo.DeviceModelPreferenceController.getDeviceModel; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.content.Context; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; +import android.os.Build; import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; import com.android.settings.testutils.SettingsRobolectricTestRunner; 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.RuntimeEnvironment; import org.robolectric.annotation.Config; +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + @RunWith(SettingsRobolectricTestRunner.class) public class DeviceModelPreferenceControllerTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Fragment mFragment; - @Mock - private Preference mPreference; - @Mock - private PreferenceScreen mPreferenceScreen; + private final String KEY = "device_model_key"; + @Mock + private Fragment mFragment; + private Preference mPreference; + private PreferenceScreen mPreferenceScreen; private Context mContext; private DeviceModelPreferenceController mController; @@ -56,35 +60,49 @@ public class DeviceModelPreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mController = new DeviceModelPreferenceController(mContext, mFragment); - when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) - .thenReturn(mPreference); - when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + mController = new DeviceModelPreferenceController(mContext, KEY); + mController.setHost(mFragment); + mPreference = new Preference(mContext); + mPreference.setKey(KEY); + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = preferenceManager.createPreferenceScreen(mContext); + mPreferenceScreen.addPreference(mPreference); } @Test public void isAvailable_returnTrueIfVisible() { - assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); } @Test @Config(qualifiers = "mcc999") public void isAvailable_returnFalseIfNotVisible() { - assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.UNSUPPORTED_ON_DEVICE); } @Test - public void displayPref_shouldSetSummary() { - mController.displayPreference(mPreferenceScreen); + public void updatePreference_summaryShouldContainBuildModel() { + mController.updateState(mPreference); - verify(mPreference).setSummary(mContext.getString(R.string.model_summary, getDeviceModel())); + assertThat(containBuildModel(mPreference.getSummary())).isTrue(); } @Test public void clickPreference_shouldLaunchHardwareInfoDialog() { + FragmentManager fragmentManager = mock(FragmentManager.class); + when(mFragment.getFragmentManager()).thenReturn(fragmentManager); + when(fragmentManager.beginTransaction()).thenReturn(mock(FragmentTransaction.class)); + assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue(); - verify(mFragment).getFragmentManager(); - verify(mFragment.getFragmentManager().beginTransaction()) + verify(fragmentManager.beginTransaction()) .add(any(HardwareInfoDialogFragment.class), eq(HardwareInfoDialogFragment.TAG)); } + + private boolean containBuildModel(CharSequence result) { + final String oracle = mContext.getResources().getString(R.string.model_summary, + Build.MODEL); + return result.toString().contains(oracle); + } } From 6d2aaed8ae111db026bf9774a4062e22081db559 Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Wed, 9 May 2018 17:27:17 -0700 Subject: [PATCH 12/17] Update the battery broadcast receiver. Add a type for battery receiver, then in callback client know for which reason it been invoked: 1. battery level change 2. battery saver state change 3. battery plug state change So in this CL, we won't update battery tip for battery level change, then battery tip won't be dismissed by itself. Also note in onResume() we will manually update battery tip. So if user stay in battery settings page and close the screen, once he opens it we will still force update everything. Fixes: 79171742 Test: RunSettingsRoboTests Change-Id: I997844216fd8267e545d74e0d434de9e338f76a1 Merged-In: I997844216fd8267e545d74e0d434de9e338f76a1 --- .../fuelgauge/BatteryBroadcastReceiver.java | 34 +++++++++--- .../fuelgauge/PowerUsageAdvanced.java | 7 ++- .../settings/fuelgauge/PowerUsageBase.java | 55 ++++++++++++------- .../settings/fuelgauge/PowerUsageSummary.java | 25 ++++----- .../BatteryBroadcastReceiverTest.java | 13 +++-- .../fuelgauge/PowerUsageAdvancedTest.java | 3 +- .../fuelgauge/PowerUsageBaseTest.java | 2 +- .../fuelgauge/PowerUsageSummaryTest.java | 26 +++++---- 8 files changed, 101 insertions(+), 64 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java index ca4742d1fcb..3075d9bf0ea 100644 --- a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java @@ -21,9 +21,14 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.PowerManager; +import android.support.annotation.IntDef; import android.support.annotation.VisibleForTesting; import com.android.settings.Utils; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Use this broadcastReceiver to listen to the battery change, and it will invoke @@ -43,7 +48,19 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver { * Battery saver(e.g. off->on) */ public interface OnBatteryChangedListener { - void onBatteryChanged(); + void onBatteryChanged(@BatteryUpdateType int type); + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({BatteryUpdateType.MANUAL, + BatteryUpdateType.BATTERY_LEVEL, + BatteryUpdateType.BATTERY_SAVER, + BatteryUpdateType.BATTERY_STATUS}) + public @interface BatteryUpdateType { + int MANUAL = 0; + int BATTERY_LEVEL = 1; + int BATTERY_SAVER = 2; + int BATTERY_STATUS = 3; } @VisibleForTesting @@ -85,14 +102,17 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver { final String batteryLevel = Utils.getBatteryPercentage(intent); final String batteryStatus = Utils.getBatteryStatus( mContext.getResources(), intent); - if (forceUpdate || !batteryLevel.equals(mBatteryLevel) || !batteryStatus.equals( - mBatteryStatus)) { - mBatteryLevel = batteryLevel; - mBatteryStatus = batteryStatus; - mBatteryListener.onBatteryChanged(); + if (forceUpdate) { + mBatteryListener.onBatteryChanged(BatteryUpdateType.MANUAL); + } else if(!batteryLevel.equals(mBatteryLevel)) { + mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL); + } else if (!batteryStatus.equals(mBatteryStatus)) { + mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_STATUS); } + mBatteryLevel = batteryLevel; + mBatteryStatus = batteryStatus; } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) { - mBatteryListener.onBatteryChanged(); + mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_SAVER); } } } diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java index 327a6c58bfa..64934308820 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java @@ -13,6 +13,8 @@ */ package com.android.settings.fuelgauge; +import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType; + import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -30,7 +32,6 @@ import com.android.settings.SettingsActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.utils.StringUtil; import java.util.ArrayList; import java.util.Arrays; @@ -106,7 +107,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { mMetricsFeatureProvider.action(getContext(), MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, mShowAllApps); - restartBatteryStatsLoader(); + restartBatteryStatsLoader(BatteryUpdateType.MANUAL); return true; default: return super.onOptionsItemSelected(item); @@ -138,7 +139,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { } @Override - protected void refreshUi() { + protected void refreshUi(@BatteryUpdateType int refreshType) { final Context context = getContext(); if (context == null) { return; diff --git a/src/com/android/settings/fuelgauge/PowerUsageBase.java b/src/com/android/settings/fuelgauge/PowerUsageBase.java index b811f208b84..58d9d0885d3 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageBase.java +++ b/src/com/android/settings/fuelgauge/PowerUsageBase.java @@ -15,6 +15,8 @@ */ package com.android.settings.fuelgauge; +import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.*; + import android.app.Activity; import android.app.LoaderManager; import android.content.Context; @@ -26,18 +28,17 @@ import android.view.Menu; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.dashboard.DashboardFragment; -import com.android.settingslib.utils.AsyncLoader; /** * Common base class for things that need to show the battery usage graph. */ -public abstract class PowerUsageBase extends DashboardFragment - implements LoaderManager.LoaderCallbacks { +public abstract class PowerUsageBase extends DashboardFragment { // +1 to allow ordering for PowerUsageSummary. @VisibleForTesting static final int MENU_STATS_REFRESH = Menu.FIRST + 1; private static final String TAG = "PowerUsageBase"; + private static final String KEY_REFRESH_TYPE = "refresh_type"; protected BatteryStatsHelper mStatsHelper; protected UserManager mUm; @@ -57,8 +58,8 @@ public abstract class PowerUsageBase extends DashboardFragment setHasOptionsMenu(true); mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(getContext()); - mBatteryBroadcastReceiver.setBatteryChangedListener(() -> { - restartBatteryStatsLoader(); + mBatteryBroadcastReceiver.setBatteryChangedListener(type -> { + restartBatteryStatsLoader(type); }); } @@ -81,11 +82,14 @@ public abstract class PowerUsageBase extends DashboardFragment mBatteryBroadcastReceiver.unRegister(); } - protected void restartBatteryStatsLoader() { - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); + protected void restartBatteryStatsLoader(int refreshType) { + final Bundle bundle = new Bundle(); + bundle.putInt(KEY_REFRESH_TYPE, refreshType); + + getLoaderManager().restartLoader(0, bundle, new PowerLoaderCallback()); } - protected abstract void refreshUi(); + protected abstract void refreshUi(@BatteryUpdateType int refreshType); protected void updatePreference(BatteryHistoryPreference historyPref) { final long startTime = System.currentTimeMillis(); @@ -93,21 +97,30 @@ public abstract class PowerUsageBase extends DashboardFragment BatteryUtils.logRuntime(TAG, "updatePreference", startTime); } - @Override - public Loader onCreateLoader(int id, - Bundle args) { - return new BatteryStatsHelperLoader(getContext()); - } + /** + * {@link android.app.LoaderManager.LoaderCallbacks} for {@link PowerUsageBase} to load + * the {@link BatteryStatsHelper} + */ + public class PowerLoaderCallback implements LoaderManager.LoaderCallbacks { + private int mRefreshType; - @Override - public void onLoadFinished(Loader loader, - BatteryStatsHelper statsHelper) { - mStatsHelper = statsHelper; - refreshUi(); - } + @Override + public Loader onCreateLoader(int id, + Bundle args) { + mRefreshType = args.getInt(KEY_REFRESH_TYPE); + return new BatteryStatsHelperLoader(getContext()); + } - @Override - public void onLoaderReset(Loader loader) { + @Override + public void onLoadFinished(Loader loader, + BatteryStatsHelper statsHelper) { + mStatsHelper = statsHelper; + refreshUi(mRefreshType); + } + @Override + public void onLoaderReset(Loader loader) { + + } } } diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 0563815dd71..e4f7a726c17 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -16,6 +16,8 @@ package com.android.settings.fuelgauge; +import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType; + import android.app.Activity; import android.app.LoaderManager; import android.app.LoaderManager.LoaderCallbacks; @@ -277,7 +279,7 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList } else { mStatsType = BatteryStats.STATS_SINCE_CHARGED; } - refreshUi(); + refreshUi(BatteryUpdateType.MANUAL); return true; case MENU_ADVANCED_BATTERY: new SubSettingLauncher(getContext()) @@ -291,14 +293,15 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList } } - protected void refreshUi() { + protected void refreshUi(@BatteryUpdateType int refreshType) { final Context context = getContext(); if (context == null) { return; } - // Only skip BatteryTipLoader for the first time when device is rotated - if (mNeedUpdateBatteryTip) { + // Skip BatteryTipLoader if device is rotated or only battery level change + if (mNeedUpdateBatteryTip + && refreshType != BatteryUpdateType.BATTERY_LEVEL) { restartBatteryTipLoader(); } else { mNeedUpdateBatteryTip = true; @@ -397,8 +400,9 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList } @Override - protected void restartBatteryStatsLoader() { - restartBatteryStatsLoader(true /* clearHeader */); + protected void restartBatteryStatsLoader(@BatteryUpdateType int refreshType) { + super.restartBatteryStatsLoader(refreshType); + mBatteryHeaderPreferenceController.quickUpdateHeaderPreference(); } @Override @@ -407,13 +411,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList mBatteryTipPreferenceController.saveInstanceState(outState); } - void restartBatteryStatsLoader(boolean clearHeader) { - super.restartBatteryStatsLoader(); - if (clearHeader) { - mBatteryHeaderPreferenceController.quickUpdateHeaderPreference(); - } - } - @Override public void onBatteryTipHandled(BatteryTip batteryTip) { restartBatteryTipLoader(); @@ -428,7 +425,7 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList mContext = context; mLoader = loader; mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext); - mBatteryBroadcastReceiver.setBatteryChangedListener(() -> { + mBatteryBroadcastReceiver.setBatteryChangedListener(type -> { BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() { @Override public void onBatteryInfoLoaded(BatteryInfo info) { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java index 3fdbe837425..4583dd1b3b4 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java @@ -15,8 +15,11 @@ */ package com.android.settings.fuelgauge; +import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -70,14 +73,14 @@ public class BatteryBroadcastReceiverTest { } @Test - public void testOnReceive_batteryDataChanged_dataUpdated() { + public void testOnReceive_batteryLevelChanged_dataUpdated() { mBatteryBroadcastReceiver.onReceive(mContext, mChargingIntent); assertThat(mBatteryBroadcastReceiver.mBatteryLevel) .isEqualTo(Utils.getBatteryPercentage(mChargingIntent)); assertThat(mBatteryBroadcastReceiver.mBatteryStatus) .isEqualTo(Utils.getBatteryStatus(mContext.getResources(), mChargingIntent)); - verify(mBatteryListener).onBatteryChanged(); + verify(mBatteryListener).onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL); } @Test @@ -85,7 +88,7 @@ public class BatteryBroadcastReceiverTest { mBatteryBroadcastReceiver.onReceive(mContext, new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); - verify(mBatteryListener).onBatteryChanged(); + verify(mBatteryListener).onBatteryChanged(BatteryUpdateType.BATTERY_SAVER); } @Test @@ -100,7 +103,7 @@ public class BatteryBroadcastReceiverTest { assertThat(mBatteryBroadcastReceiver.mBatteryLevel).isEqualTo(batteryLevel); assertThat(mBatteryBroadcastReceiver.mBatteryStatus).isEqualTo(batteryStatus); - verify(mBatteryListener, never()).onBatteryChanged(); + verify(mBatteryListener, never()).onBatteryChanged(anyInt()); } @Test @@ -115,6 +118,6 @@ public class BatteryBroadcastReceiverTest { assertThat(mBatteryBroadcastReceiver.mBatteryStatus) .isEqualTo(Utils.getBatteryStatus(mContext.getResources(), mChargingIntent)); // 2 times because register will force update the battery - verify(mBatteryListener, times(2)).onBatteryChanged(); + verify(mBatteryListener, times(2)).onBatteryChanged(BatteryUpdateType.MANUAL); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java index 4a905b4603a..9b61e5fe131 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -86,7 +87,7 @@ public class PowerUsageAdvancedTest { @Test public void testOptionsMenu_menuAppToggle_metricEventInvoked() { mFragment.mShowAllApps = false; - doNothing().when(mFragment).restartBatteryStatsLoader(); + doNothing().when(mFragment).restartBatteryStatsLoader(anyInt()); mFragment.onOptionsItemSelected(mToggleAppsMenu); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageBaseTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageBaseTest.java index d9f572deefa..eb683c072e0 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageBaseTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageBaseTest.java @@ -74,7 +74,7 @@ public class PowerUsageBaseTest { } @Override - protected void refreshUi() { + protected void refreshUi(int refreshType) { // Do nothing } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index b48f00e295b..07341a14656 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -149,7 +149,7 @@ public class PowerUsageSummaryTest { mFragment.initFeatureProvider(); mBatteryMeterView = new BatteryMeterView(mRealContext); mBatteryMeterView.mDrawable = new BatteryMeterView.BatteryMeterDrawable(mRealContext, 0); - doNothing().when(mFragment).restartBatteryStatsLoader(); + doNothing().when(mFragment).restartBatteryStatsLoader(anyInt()); doReturn(mock(LoaderManager.class)).when(mFragment).getLoaderManager(); doReturn(MENU_ADVANCED_BATTERY).when(mAdvancedPageMenu).getItemId(); @@ -316,15 +316,6 @@ public class PowerUsageSummaryTest { verify(mSummary1, times(2)).setOnLongClickListener(any(View.OnLongClickListener.class)); } - @Test - public void restartBatteryStatsLoader_notClearHeader_quickUpdateNotInvoked() { - mFragment.mBatteryHeaderPreferenceController = mBatteryHeaderPreferenceController; - - mFragment.restartBatteryStatsLoader(false /* clearHeader */); - - verify(mBatteryHeaderPreferenceController, never()).quickUpdateHeaderPreference(); - } - @Test public void optionsMenu_advancedPageEnabled() { when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled()) @@ -360,7 +351,18 @@ public class PowerUsageSummaryTest { when(mFragment.mBatteryTipPreferenceController.needUpdate()).thenReturn(false); mFragment.updateBatteryTipFlag(new Bundle()); - mFragment.refreshUi(); + mFragment.refreshUi(BatteryBroadcastReceiver.BatteryUpdateType.MANUAL); + + verify(mFragment, never()).restartBatteryTipLoader(); + } + + @Test + public void refreshUi_batteryLevelChanged_doNotUpdateBatteryTip() { + mFragment.mBatteryTipPreferenceController = mock(BatteryTipPreferenceController.class); + when(mFragment.mBatteryTipPreferenceController.needUpdate()).thenReturn(true); + mFragment.updateBatteryTipFlag(new Bundle()); + + mFragment.refreshUi(BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_LEVEL); verify(mFragment, never()).restartBatteryTipLoader(); } @@ -371,7 +373,7 @@ public class PowerUsageSummaryTest { when(mFragment.mBatteryTipPreferenceController.needUpdate()).thenReturn(true); mFragment.updateBatteryTipFlag(new Bundle()); - mFragment.refreshUi(); + mFragment.refreshUi(BatteryBroadcastReceiver.BatteryUpdateType.MANUAL); verify(mFragment).restartBatteryTipLoader(); } From 231fe615cc5ae8207d997c6957fe71115043b91e Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Mon, 14 May 2018 13:08:58 -0700 Subject: [PATCH 13/17] Add cancel button for private dns dialog Also update the onclick method. Change-Id: I69e92584e056a4d0a686153315b8df002d91e204 Fixes: 79479021 Test: RunSettingsRoboTests --- res/xml/network_and_internet.xml | 3 +- .../PrivateDnsModeDialogPreference.java | 20 ++++++----- .../PrivateDnsModeDialogPreferenceTest.java | 35 +++++++++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/res/xml/network_and_internet.xml b/res/xml/network_and_internet.xml index 064d625681b..69d8fcac784 100644 --- a/res/xml/network_and_internet.xml +++ b/res/xml/network_and_internet.xml @@ -97,6 +97,7 @@ android:order="15" android:dialogTitle="@string/select_private_dns_configuration_dialog_title" android:dialogLayout="@layout/private_dns_mode_dialog" - android:positiveButtonText="@string/save" /> + android:positiveButtonText="@string/save" + android:negativeButtonText="@android:string/cancel" /> diff --git a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java index 178aa27c188..969cb11920e 100644 --- a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java +++ b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java @@ -156,16 +156,18 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreference imple @Override public void onClick(DialogInterface dialog, int which) { - final Context context = getContext(); - if (mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { - // Only clickable if hostname is valid, so we could save it safely - Settings.Global.putString(context.getContentResolver(), HOSTNAME_KEY, - mEditText.getText().toString()); - } + if (which == DialogInterface.BUTTON_POSITIVE) { + final Context context = getContext(); + if (mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + // Only clickable if hostname is valid, so we could save it safely + Settings.Global.putString(context.getContentResolver(), HOSTNAME_KEY, + mEditText.getText().toString()); + } - FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context, - MetricsProto.MetricsEvent.ACTION_PRIVATE_DNS_MODE, mMode); - Settings.Global.putString(context.getContentResolver(), MODE_KEY, mMode); + FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context, + MetricsProto.MetricsEvent.ACTION_PRIVATE_DNS_MODE, mMode); + Settings.Global.putString(context.getContentResolver(), MODE_KEY, mMode); + } } @Override diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java index e9ffa8ac4c6..dfea6fba515 100644 --- a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java @@ -28,7 +28,10 @@ import static org.mockito.Mockito.when; import android.app.AlertDialog; import android.app.Fragment; +import android.content.ContentResolver; import android.content.Context; +import android.content.DialogInterface; +import android.net.ConnectivityManager; import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; @@ -150,4 +153,36 @@ public class PrivateDnsModeDialogPreferenceTest { assertThat(mSaveButton.isEnabled()).named("provider: " + invalid).isFalse(); } } + + @Test + public void testOnClick_positiveButtonClicked_saveData() { + // Set the default settings to OFF + final ContentResolver contentResolver = mContext.getContentResolver(); + Settings.Global.putString(contentResolver, Settings.Global.PRIVATE_DNS_MODE, + ConnectivityManager.PRIVATE_DNS_MODE_OFF); + + mPreference.mMode = ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; + mPreference.onClick(null, DialogInterface.BUTTON_POSITIVE); + + // Change to OPPORTUNISTIC + assertThat(Settings.Global.getString(contentResolver, + Settings.Global.PRIVATE_DNS_MODE)).isEqualTo( + ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC); + } + + @Test + public void testOnClick_negativeButtonClicked_doNothing() { + // Set the default settings to OFF + final ContentResolver contentResolver = mContext.getContentResolver(); + Settings.Global.putString(contentResolver, Settings.Global.PRIVATE_DNS_MODE, + ConnectivityManager.PRIVATE_DNS_MODE_OFF); + + mPreference.mMode = ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; + mPreference.onClick(null, DialogInterface.BUTTON_NEGATIVE); + + // Still equal to OFF + assertThat(Settings.Global.getString(contentResolver, + Settings.Global.PRIVATE_DNS_MODE)).isEqualTo( + ConnectivityManager.PRIVATE_DNS_MODE_OFF); + } } From e6bf71abedb8da622b634fd8e19cf3ac9cd999b2 Mon Sep 17 00:00:00 2001 From: Naina Nalluri Date: Wed, 2 May 2018 14:30:11 -0700 Subject: [PATCH 14/17] DO NOT MERGE Add Slices for WifiCalling Add Slices for WifiCalling WifiCalling Slice Provider: 1. If there is no activation needed or if the Wifi calling is currently turned on - provide the slice to toggle the value 2. Else display appropriate message with further instructions WifiCalling Slice Broadcast Receiver: 1. If the action is turning off wifi or if there is no activation needed. Change the setting with ImsManager. 2. And Ask to requery the slice in one second to display updated settings if 1 is valid or display appropriate message Bug: 63731862 Bug: 78192106 Test: Use support-slices-demos-debug.apk to test on device Test: Robotests Change-Id: I29e1822fd24ebcff575fa48ad93f84ed91bf4d87 --- res/values/strings.xml | 11 + .../slices/SettingsSliceProvider.java | 18 +- .../slices/SliceBroadcastReceiver.java | 7 + .../slices/SlicesFeatureProvider.java | 9 +- .../slices/SlicesFeatureProviderImpl.java | 8 +- .../wifi/calling/WifiCallingSliceHelper.java | 363 ++++++++++++++++++ .../calling/WifiCallingSliceHelperTest.java | 316 +++++++++++++++ 7 files changed, 725 insertions(+), 7 deletions(-) create mode 100644 src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java create mode 100644 tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index b061e611ab2..3a185c0727b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2348,6 +2348,17 @@ Learn more about Private DNS features + + Setting managed by carrier + + Activate Wi\u2011Fi Calling + + Turn on Wi\u2011Fi calling + + Wi\u2011Fi calling is not supported for %1$s + + Carrier Display diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index df826cb8cef..9889c765356 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -18,6 +18,8 @@ package com.android.settings.slices; import static android.Manifest.permission.READ_SEARCH_INDEXABLES; +import static com.android.settings.wifi.calling.WifiCallingSliceHelper.PATH_WIFI_CALLING; + import android.app.PendingIntent; import android.app.slice.SliceManager; import android.content.ContentResolver; @@ -36,7 +38,13 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Pair; +import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.utils.ThreadUtils; import java.net.URISyntaxException; @@ -46,11 +54,6 @@ import java.util.List; import java.util.Map; import java.util.WeakHashMap; -import androidx.slice.Slice; -import androidx.slice.SliceProvider; -import androidx.slice.builders.ListBuilder; -import androidx.slice.builders.SliceAction; - /** * A {@link SliceProvider} for Settings to enabled inline results in system apps. * @@ -165,6 +168,11 @@ public class SettingsSliceProvider extends SliceProvider { switch (path) { case "/" + PATH_WIFI: return createWifiSlice(sliceUri); + case "/" + PATH_WIFI_CALLING: + return FeatureFactory.getFactory(getContext()) + .getSlicesFeatureProvider() + .getNewWifiCallingSliceHelper(getContext()) + .createWifiCallingSlice(sliceUri); } SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java index 47a7f5a45bd..04097340ed3 100644 --- a/src/com/android/settings/slices/SliceBroadcastReceiver.java +++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java @@ -21,6 +21,7 @@ import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CH import static com.android.settings.slices.SettingsSliceProvider.ACTION_WIFI_CHANGED; import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY; import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED; +import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED; import android.app.slice.Slice; import android.content.BroadcastReceiver; @@ -79,6 +80,12 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { context.getContentResolver().notifyChange(uri, null); }, 1000); break; + case ACTION_WIFI_CALLING_CHANGED: + FeatureFactory.getFactory(context) + .getSlicesFeatureProvider() + .getNewWifiCallingSliceHelper(context) + .handleWifiCallingChanged(intent); + break; } } diff --git a/src/com/android/settings/slices/SlicesFeatureProvider.java b/src/com/android/settings/slices/SlicesFeatureProvider.java index e5bba617e48..8dd6547b398 100644 --- a/src/com/android/settings/slices/SlicesFeatureProvider.java +++ b/src/com/android/settings/slices/SlicesFeatureProvider.java @@ -2,6 +2,8 @@ package com.android.settings.slices; import android.content.Context; +import com.android.settings.wifi.calling.WifiCallingSliceHelper; + /** * Manages Slices in Settings. */ @@ -24,4 +26,9 @@ public interface SlicesFeatureProvider { * If the data is already indexed, the data will not change. */ void indexSliceData(Context context); -} \ No newline at end of file + + /** + * Gets new WifiCallingSliceHelper object + */ + WifiCallingSliceHelper getNewWifiCallingSliceHelper(Context context); +} diff --git a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java index 8e5bc067150..16684bfb022 100644 --- a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java +++ b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java @@ -2,6 +2,7 @@ package com.android.settings.slices; import android.content.Context; +import com.android.settings.wifi.calling.WifiCallingSliceHelper; import com.android.settingslib.utils.ThreadUtils; /** @@ -39,4 +40,9 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider { SlicesIndexer indexer = getSliceIndexer(context); indexer.indexSliceData(); } -} \ No newline at end of file + + @Override + public WifiCallingSliceHelper getNewWifiCallingSliceHelper(Context context) { + return new WifiCallingSliceHelper(context); + } +} diff --git a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java new file mode 100644 index 00000000000..7213148e97f --- /dev/null +++ b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2018 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.wifi.calling; + +import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.PersistableBundle; +import android.support.v4.graphics.drawable.IconCompat; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + +import com.android.ims.ImsManager; +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.slices.SliceBroadcastReceiver; +import com.android.settings.slices.SliceBuilderUtils; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * Helper class to control slices for wifi calling settings. + */ +public class WifiCallingSliceHelper { + + private static final String TAG = "WifiCallingSliceHelper"; + + /** + * Settings slice path to wifi calling setting. + */ + public static final String PATH_WIFI_CALLING = "wifi_calling"; + + /** + * Action passed for changes to wifi calling slice (toggle). + */ + public static final String ACTION_WIFI_CALLING_CHANGED = + "com.android.settings.wifi.calling.action.WIFI_CALLING_CHANGED"; + + /** + * Action for Wifi calling Settings activity which + * allows setting configuration for Wifi calling + * related settings + */ + public static final String ACTION_WIFI_CALLING_SETTINGS_ACTIVITY = + "android.settings.WIFI_CALLING_SETTINGS"; + + /** + * Timeout for querying wifi calling setting from ims manager. + */ + private static final int TIMEOUT_MILLIS = 2000; + + /** + * Time for which data contained in the slice can remain fresh. + */ + private static final int SLICE_TTL_MILLIS = 60000; + + protected SubscriptionManager mSubscriptionManager; + private final Context mContext; + + @VisibleForTesting + public WifiCallingSliceHelper(Context context) { + mContext = context; + } + + /** + * Returns Slice object for wifi calling settings. + * + * If wifi calling is being turned on and if wifi calling activation is needed for the current + * carrier, this method will return Slice with instructions to go to Settings App. + * + * If wifi calling is not supported for the current carrier, this method will return slice with + * not supported message. + * + * If wifi calling setting can be changed, this method will return the slice to toggle wifi + * calling option with ACTION_WIFI_CALLING_CHANGED as endItem. + */ + public Slice createWifiCallingSlice(Uri sliceUri) { + final int subId = getDefaultVoiceSubId(); + final String carrierName = getSimCarrierName(); + + if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + Log.d(TAG, "Invalid subscription Id"); + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString(R.string.wifi_calling_not_supported, carrierName), + sliceUri, SliceBuilderUtils.getSettingsIntent(mContext)); + } + + final ImsManager imsManager = getImsManager(subId); + + if (!imsManager.isWfcEnabledByPlatform() + || !imsManager.isWfcProvisionedOnDevice()) { + Log.d(TAG, "Wifi calling is either not provisioned or not enabled by Platform"); + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString(R.string.wifi_calling_not_supported, carrierName), + sliceUri, SliceBuilderUtils.getSettingsIntent(mContext)); + } + + try { + final boolean isWifiCallingEnabled = isWifiCallingEnabled(imsManager); + final Intent activationAppIntent = + getWifiCallingCarrierActivityIntent(subId); + + // Send this actionable wifi calling slice to toggle the setting + // only when there is no need for wifi calling activation with the server + if (activationAppIntent != null && !isWifiCallingEnabled) { + Log.d(TAG, "Needs Activation"); + // Activation needed for the next action of the user + // Give instructions to go to settings app + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString( + R.string.wifi_calling_settings_activation_instructions), + sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); + } + return getWifiCallingSlice(sliceUri, mContext, isWifiCallingEnabled); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + Log.e(TAG, "Unable to read the current WiFi calling status", e); + return getNonActionableWifiCallingSlice( + mContext.getString(R.string.wifi_calling_settings_title), + mContext.getString(R.string.wifi_calling_turn_on), + sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); + } + } + + private boolean isWifiCallingEnabled(ImsManager imsManager) + throws InterruptedException, ExecutionException, TimeoutException { + final FutureTask isWifiOnTask = new FutureTask<>(new Callable() { + @Override + public Boolean call() { + return imsManager.isWfcEnabledByUser(); + } + }); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(isWifiOnTask); + + Boolean isWifiEnabledByUser = false; + isWifiEnabledByUser = isWifiOnTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + + return isWifiEnabledByUser && imsManager.isNonTtyOrTtyOnVolteEnabled(); + } + + /** + * Builds a toggle slice where the intent takes you to the wifi calling page and the toggle + * enables/disables wifi calling. + */ + private Slice getWifiCallingSlice(Uri sliceUri, Context mContext, + boolean isWifiCallingEnabled) { + + final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); + final String title = mContext.getString(R.string.wifi_calling_settings_title); + return new ListBuilder(mContext, sliceUri, SLICE_TTL_MILLIS) + .setColor(R.color.material_blue_500) + .addRow(b -> b + .setTitle(title) + .addEndItem( + new SliceAction( + getBroadcastIntent(ACTION_WIFI_CALLING_CHANGED), + null /* actionTitle */, isWifiCallingEnabled)) + .setPrimaryAction(new SliceAction( + getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY), + icon, + title))) + .build(); + } + + protected ImsManager getImsManager(int subId) { + return ImsManager.getInstance(mContext, SubscriptionManager.getPhoneId(subId)); + } + + private Integer getWfcMode(ImsManager imsManager) + throws InterruptedException, ExecutionException, TimeoutException { + FutureTask wfcModeTask = new FutureTask<>(new Callable() { + @Override + public Integer call() { + return imsManager.getWfcMode(false); + } + }); + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(wfcModeTask); + return wfcModeTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } + + /** + * Handles wifi calling setting change from wifi calling slice and posts notification. Should be + * called when intent action is ACTION_WIFI_CALLING_CHANGED. Executed in @WorkerThread + * + * @param intent action performed + */ + public void handleWifiCallingChanged(Intent intent) { + final int subId = getDefaultVoiceSubId(); + + if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + final ImsManager imsManager = getImsManager(subId); + if (imsManager.isWfcEnabledByPlatform() + || imsManager.isWfcProvisionedOnDevice()) { + final boolean currentValue = imsManager.isWfcEnabledByUser() + && imsManager.isNonTtyOrTtyOnVolteEnabled(); + final boolean newValue = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, + currentValue); + final Intent activationAppIntent = + getWifiCallingCarrierActivityIntent(subId); + if (!newValue || activationAppIntent == null) { + // If either the action is to turn off wifi calling setting + // or there is no activation involved - Update the setting + if (newValue != currentValue) { + imsManager.setWfcSetting(newValue); + } + } + } + } + // notify change in slice in any case to get re-queried. This would result in displaying + // appropriate message with the updated setting. + final Uri uri = SliceBuilderUtils.getUri(PATH_WIFI_CALLING, false /*isPlatformSlice*/); + mContext.getContentResolver().notifyChange(uri, null); + } + + /** + * Returns Slice with the title and subtitle provided as arguments with wifi signal Icon. + * + * @param title Title of the slice + * @param subtitle Subtitle of the slice + * @param sliceUri slice uri + * @return Slice with title and subtitle + */ + // TODO(b/79548264) asses different scenarios and return null instead of non-actionable slice + private Slice getNonActionableWifiCallingSlice(String title, String subtitle, Uri sliceUri, + PendingIntent primaryActionIntent) { + final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); + return new ListBuilder(mContext, sliceUri, SLICE_TTL_MILLIS) + .setColor(R.color.material_blue_500) + .addRow(b -> b + .setTitle(title) + .setSubtitle(subtitle) + .setPrimaryAction(new SliceAction( + primaryActionIntent, icon, + title))) + .build(); + } + + /** + * Returns {@code true} when the key is enabled for the carrier, and {@code false} otherwise. + */ + private boolean isCarrierConfigManagerKeyEnabled(Context mContext, String key, + int subId, boolean defaultValue) { + final CarrierConfigManager configManager = getCarrierConfigManager(mContext); + boolean ret = false; + if (configManager != null) { + final PersistableBundle bundle = configManager.getConfigForSubId(subId); + if (bundle != null) { + ret = bundle.getBoolean(key, defaultValue); + } + } + return ret; + } + + protected CarrierConfigManager getCarrierConfigManager(Context mContext) { + return mContext.getSystemService(CarrierConfigManager.class); + } + + /** + * Returns the current default voice subId obtained from SubscriptionManager + */ + protected int getDefaultVoiceSubId() { + if (mSubscriptionManager == null) { + mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); + } + return SubscriptionManager.getDefaultVoiceSubscriptionId(); + } + + /** + * Returns Intent of the activation app required to activate wifi calling or null if there is no + * need for activation. + */ + protected Intent getWifiCallingCarrierActivityIntent(int subId) { + final CarrierConfigManager configManager = getCarrierConfigManager(mContext); + if (configManager == null) { + return null; + } + + final PersistableBundle bundle = configManager.getConfigForSubId(subId); + if (bundle == null) { + return null; + } + + final String carrierApp = bundle.getString( + CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING); + if (TextUtils.isEmpty(carrierApp)) { + return null; + } + + final ComponentName componentName = ComponentName.unflattenFromString(carrierApp); + if (componentName == null) { + return null; + } + + final Intent intent = new Intent(); + intent.setComponent(componentName); + return intent; + } + + private PendingIntent getBroadcastIntent(String action) { + final Intent intent = new Intent(action); + intent.setClass(mContext, SliceBroadcastReceiver.class); + return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, + PendingIntent.FLAG_CANCEL_CURRENT); + } + + /** + * Returns PendingIntent to start activity specified by action + */ + private PendingIntent getActivityIntent(String action) { + final Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); + } + + /** + * Returns carrier id name of the current Subscription + */ + private String getSimCarrierName() { + final TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); + final CharSequence carrierName = telephonyManager.getSimCarrierIdName(); + if (carrierName == null) { + return mContext.getString(R.string.carrier); + } + return carrierName.toString(); + } + +} diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java new file mode 100644 index 00000000000..ac3ff3ff635 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2018 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.wifi.calling; + +import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; +import static android.app.slice.Slice.HINT_TITLE; +import static android.app.slice.SliceItem.FORMAT_TEXT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.telephony.CarrierConfigManager; + +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.SliceMetadata; +import androidx.slice.SliceProvider; +import androidx.slice.core.SliceAction; +import androidx.slice.core.SliceQuery; +import androidx.slice.widget.SliceLiveData; + +import com.android.ims.ImsManager; +import com.android.settings.R; +import com.android.settings.slices.SettingsSliceProvider; +import com.android.settings.slices.SliceBroadcastReceiver; +import com.android.settings.slices.SliceBuilderUtils; +import com.android.settings.slices.SliceData; +import com.android.settings.slices.SlicesFeatureProvider; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class WifiCallingSliceHelperTest { + + private Context mContext; + @Mock + private CarrierConfigManager mMockCarrierConfigManager; + + @Mock + private ImsManager mMockImsManager; + + private final Uri mWfcURI = Uri.parse("content://com.android.settings.slices/wifi_calling"); + + private FakeWifiCallingSliceHelper mWfcSliceHelper; + private SettingsSliceProvider mProvider; + private SliceBroadcastReceiver mReceiver; + private FakeFeatureFactory mFeatureFactory; + private SlicesFeatureProvider mSlicesFeatureProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + //setup for SettingsSliceProvider tests + mProvider = spy(new SettingsSliceProvider()); + doReturn(mContext).when(mProvider).getContext(); + + //setup for SliceBroadcastReceiver test + mReceiver = spy(new SliceBroadcastReceiver()); + + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mSlicesFeatureProvider = mFeatureFactory.getSlicesFeatureProvider(); + + // Prevent crash in SliceMetadata. + Resources resources = spy(mContext.getResources()); + doReturn(60).when(resources).getDimensionPixelSize(anyInt()); + doReturn(resources).when(mContext).getResources(); + + mWfcSliceHelper = new FakeWifiCallingSliceHelper(mContext); + + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + } + + @Test + public void test_CreateWifiCallingSlice_invalidSubId() { + mWfcSliceHelper.setDefaultVoiceSubId(-1); + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + testWifiCallingSettingsUnavailableSlice(slice, null, + SliceBuilderUtils.getSettingsIntent(mContext)); + } + + @Test + public void test_CreateWifiCallingSlice_wfcNotSupported() { + doReturn(false).when(mMockImsManager).isWfcEnabledByPlatform(); + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsUnavailableSlice(slice, null, + SliceBuilderUtils.getSettingsIntent(mContext)); + } + + @Test + public void test_CreateWifiCallingSlice_needsActivation() { + /* In cases where activation is needed and the user action + would be turning on the wifi calling (i.e. if wifi calling is + turned off) we need to guide the user to wifi calling settings + activity so the user can perform the activation there.(PrimaryAction) + */ + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(false).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(false).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(null).when(mMockCarrierConfigManager).getConfigForSubId(1); + mWfcSliceHelper.setActivationAppIntent(new Intent()); // dummy Intent + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsUnavailableSlice(slice, null, + getActivityIntent(WifiCallingSliceHelper.ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); + } + + @Test + public void test_CreateWifiCallingSlice_success() { + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(true).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(true).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(null).when(mMockCarrierConfigManager).getConfigForSubId(1); + + final Slice slice = mWfcSliceHelper.createWifiCallingSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsToggleSlice(slice, null); + } + + @Test + public void test_SettingSliceProvider_getsRightSliceWifiCalling() { + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(true).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(true).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(null).when(mMockCarrierConfigManager).getConfigForSubId(1); + doReturn(mWfcSliceHelper).when(mSlicesFeatureProvider) + .getNewWifiCallingSliceHelper(mContext); + + final Slice slice = mProvider.onBindSlice(mWfcURI); + + assertThat(mWfcSliceHelper.getDefaultVoiceSubId()).isEqualTo(1); + testWifiCallingSettingsToggleSlice(slice, null); + } + + @Test + public void test_SliceBroadcastReceiver_toggleOffWifiCalling() { + doReturn(true).when(mMockImsManager).isWfcEnabledByPlatform(); + doReturn(true).when(mMockImsManager).isWfcProvisionedOnDevice(); + doReturn(false).when(mMockImsManager).isWfcEnabledByUser(); + doReturn(true).when(mMockImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(mWfcSliceHelper).when(mSlicesFeatureProvider) + .getNewWifiCallingSliceHelper(mContext); + mWfcSliceHelper.setActivationAppIntent(null); + + ArgumentCaptor mWfcSettingCaptor = ArgumentCaptor.forClass(Boolean.class); + + // turn on Wifi calling setting + Intent intent = new Intent(WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED); + intent.putExtra(EXTRA_TOGGLE_STATE, true); + + // change the setting + mReceiver.onReceive(mContext, intent); + + verify((mMockImsManager)).setWfcSetting(mWfcSettingCaptor.capture()); + + // assert the change + assertThat(mWfcSettingCaptor.getValue()).isTrue(); + } + + private void testWifiCallingSettingsUnavailableSlice(Slice slice, + SliceData sliceData, PendingIntent expectedPrimaryAction) { + final SliceMetadata metadata = SliceMetadata.from(mContext, slice); + + //Check there is no toggle action + final List toggles = metadata.getToggles(); + assertThat(toggles).isEmpty(); + + // Check whether the primary action is to open wifi calling settings activity + final PendingIntent primaryPendingIntent = + metadata.getPrimaryAction().getAction(); + assertThat(primaryPendingIntent).isEqualTo(expectedPrimaryAction); + + // Check the title + final List sliceItems = slice.getItems(); + assertTitle(sliceItems, mContext.getString(R.string.wifi_calling_settings_title)); + } + + private void testWifiCallingSettingsToggleSlice(Slice slice, + SliceData sliceData) { + final SliceMetadata metadata = SliceMetadata.from(mContext, slice); + + final List toggles = metadata.getToggles(); + assertThat(toggles).hasSize(1); + + final SliceAction mainToggleAction = toggles.get(0); + + // Check intent in Toggle Action + final PendingIntent togglePendingIntent = mainToggleAction.getAction(); + final PendingIntent expectedToggleIntent = getBroadcastIntent( + WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED); + assertThat(togglePendingIntent).isEqualTo(expectedToggleIntent); + + // Check primary intent + final PendingIntent primaryPendingIntent = metadata.getPrimaryAction().getAction(); + final PendingIntent expectedPendingIntent = + getActivityIntent(WifiCallingSliceHelper.ACTION_WIFI_CALLING_SETTINGS_ACTIVITY); + assertThat(primaryPendingIntent).isEqualTo(expectedPendingIntent); + + // Check the title + final List sliceItems = slice.getItems(); + assertTitle(sliceItems, mContext.getString(R.string.wifi_calling_settings_title)); + } + + private PendingIntent getBroadcastIntent(String action) { + final Intent intent = new Intent(action); + intent.setClass(mContext, SliceBroadcastReceiver.class); + return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, + PendingIntent.FLAG_CANCEL_CURRENT); + } + + private PendingIntent getActivityIntent(String action) { + final Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); + } + + private void assertTitle(List sliceItems, String title) { + boolean hasTitle = false; + for (SliceItem item : sliceItems) { + List titleItems = SliceQuery.findAll(item, FORMAT_TEXT, HINT_TITLE, + null /* non-hints */); + if (titleItems == null) { + continue; + } + + hasTitle = true; + for (SliceItem subTitleItem : titleItems) { + assertThat(subTitleItem.getText()).isEqualTo(title); + } + } + assertThat(hasTitle).isTrue(); + } + private class FakeWifiCallingSliceHelper extends WifiCallingSliceHelper { + int mSubId = 1; + + private Intent mActivationAppIntent; + FakeWifiCallingSliceHelper(Context context) { + super(context); + mActivationAppIntent = null; + } + + @Override + protected CarrierConfigManager getCarrierConfigManager(Context mContext) { + return mMockCarrierConfigManager; + } + + @Override + protected ImsManager getImsManager(int subId) { + return mMockImsManager; + } + + protected int getDefaultVoiceSubId() { + return mSubId; + } + + protected void setDefaultVoiceSubId(int id) { + mSubId = id; + } + + @Override + protected Intent getWifiCallingCarrierActivityIntent(int subId) { + return mActivationAppIntent; + } + + public void setActivationAppIntent(Intent intent) { + mActivationAppIntent = intent; + } + } +} From 80f6aab9b18cb54b955265e83c29bb6b965a8552 Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Mon, 14 May 2018 13:42:12 -0700 Subject: [PATCH 15/17] Remove the dynamic summary for bt preference. Starting from b/62069132, we should use static summary for it. Change-Id: I8ba5a9d6e8b5bcbcd9f4369cdf44004d557257a3 Fixes: 79666649 Test: Manual --- src/com/android/settings/TetherSettings.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java index 94c3fb581fa..1e56e540dcc 100644 --- a/src/com/android/settings/TetherSettings.java +++ b/src/com/android/settings/TetherSettings.java @@ -387,7 +387,6 @@ public class TetherSettings extends RestrictedSettingsFragment if (adapter.getState() == BluetoothAdapter.STATE_OFF) { mBluetoothEnableForTether = true; adapter.enable(); - mBluetoothTether.setSummary(R.string.bluetooth_turning_on); mBluetoothTether.setEnabled(false); return; } From dae3c912d770cfcafc7d357d72a72eb2ea16eced Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Mon, 14 May 2018 17:55:21 -0700 Subject: [PATCH 16/17] Add string for disabled FAS toggle. If FAS toggle is disabled because app is in doze whitelist(not optimized), show a dialog that has this string. Bug: 77924141 Test: Manual Change-Id: I30518cd887125bff00b7c567e26204d5b1814858 --- res/values/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 5c33e9c0700..c190282888f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4915,6 +4915,8 @@ Limit background activity? If you limit background activity for an app, it may misbehave + + Since this app is not set to optimize battery, you can\u2019t restrict it.\n\nTo restrict the app, first turn on battery optimization. Screen usage since full charge From bafae44cb26c5751006d639e906537c5d3ffca1d Mon Sep 17 00:00:00 2001 From: timhypeng Date: Tue, 24 Apr 2018 21:45:23 +0800 Subject: [PATCH 17/17] Click left side of list in "previously connected devices" should connect to the device * In "previously connected devices", to connect device * In "Available media devices", to activate device Bug: 77607104 Test: make -j50 RunSettingsRoboTests Change-Id: If5005031bd3f5dc4950abf2c77575785cd6f67b1 Merged-In: If5005031bd3f5dc4950abf2c77575785cd6f67b1 --- .../AvailableMediaBluetoothDeviceUpdater.java | 11 +++++++++- .../bluetooth/BluetoothDeviceUpdater.java | 20 +++++-------------- .../SavedBluetoothDeviceUpdater.java | 14 ++++++++++++- ...ilableMediaBluetoothDeviceUpdaterTest.java | 9 +++++++++ .../SavedBluetoothDeviceUpdaterTest.java | 11 +++++++++- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java index c8e6f26038f..01c1ff61832 100644 --- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java @@ -25,11 +25,13 @@ import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import android.support.v7.preference.Preference; /** * Controller to maintain available media Bluetooth devices */ -public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater { +public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater + implements Preference.OnPreferenceClickListener { private static final String TAG = "AvailableMediaBluetoothDeviceUpdater"; private static final boolean DBG = false; @@ -116,5 +118,12 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater } return isFilterMatched; } + + @Override + public boolean onPreferenceClick(Preference preference) { + final CachedBluetoothDevice device = ((BluetoothDevicePreference) preference) + .getBluetoothDevice(); + return device.setActive(); + } } diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index 8937b177a64..bab71713a2a 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -59,24 +59,12 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, protected DashboardFragment mFragment; private final boolean mShowDeviceWithoutNames; - private Preference.OnPreferenceClickListener mDevicePreferenceClickListener = null; - + @VisibleForTesting final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> { launchDeviceDetails(pref); }; - private class PreferenceClickListener implements - Preference.OnPreferenceClickListener { - @Override - public boolean onPreferenceClick(Preference preference) { - final CachedBluetoothDevice device = - ((BluetoothDevicePreference) preference).getBluetoothDevice(); - Log.i(TAG, "OnPreferenceClickListener: device=" + device); - return device.setActive(); - } - } - public BluetoothDeviceUpdater(Context context, DashboardFragment fragment, DevicePreferenceCallback devicePreferenceCallback) { this(fragment, devicePreferenceCallback, Utils.getLocalBtManager(context)); @@ -91,7 +79,6 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false); mPreferenceMap = new HashMap<>(); mLocalManager = localManager; - mDevicePreferenceClickListener = new PreferenceClickListener(); } /** @@ -212,7 +199,10 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, new BluetoothDevicePreference(mPrefContext, cachedDevice, mShowDeviceWithoutNames); btPreference.setOnGearClickListener(mDeviceProfilesListener); - btPreference.setOnPreferenceClickListener(mDevicePreferenceClickListener); + if (this instanceof Preference.OnPreferenceClickListener) { + btPreference.setOnPreferenceClickListener( + (Preference.OnPreferenceClickListener)this); + } mPreferenceMap.put(device, btPreference); mDevicePreferenceCallback.onDeviceAdded(btPreference); } diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java index 34cb5746c60..f087c6ac7d3 100644 --- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java @@ -24,11 +24,15 @@ import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import android.support.v7.preference.Preference; +import android.util.Log; /** * Maintain and update saved bluetooth devices(bonded but not connected) */ -public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater { +public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater + implements Preference.OnPreferenceClickListener { + private static final String TAG = "SavedBluetoothDeviceUpdater"; public SavedBluetoothDeviceUpdater(Context context, DashboardFragment fragment, DevicePreferenceCallback devicePreferenceCallback) { @@ -57,4 +61,12 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater { final BluetoothDevice device = cachedDevice.getDevice(); return device.getBondState() == BluetoothDevice.BOND_BONDED && !device.isConnected(); } + + @Override + public boolean onPreferenceClick(Preference preference) { + final CachedBluetoothDevice device = ((BluetoothDevicePreference) preference) + .getBluetoothDevice(); + device.connect(true); + return true; + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java index ed9501554eb..ced8fc4a2e4 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java @@ -73,6 +73,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { private AvailableMediaBluetoothDeviceUpdater mBluetoothDeviceUpdater; private Collection cachedDevices; private ShadowAudioManager mShadowAudioManager; + private BluetoothDevicePreference mPreference; @Before public void setUp() { @@ -93,6 +94,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { mBluetoothDeviceUpdater = spy(new AvailableMediaBluetoothDeviceUpdater(mDashboardFragment, mDevicePreferenceCallback, mLocalManager)); mBluetoothDeviceUpdater.setPrefContext(mContext); + mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, false); doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); } @@ -208,5 +210,12 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); } + + @Test + public void onClick_Preference_setActive() { + mBluetoothDeviceUpdater.onPreferenceClick(mPreference); + + verify(mCachedBluetoothDevice).setActive(); + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java index fae014f7b10..547727beced 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java @@ -57,7 +57,8 @@ public class SavedBluetoothDeviceUpdaterTest { private LocalBluetoothProfileManager mLocalBluetoothProfileManager; private Context mContext; - private BluetoothDeviceUpdater mBluetoothDeviceUpdater; + private SavedBluetoothDeviceUpdater mBluetoothDeviceUpdater; + private BluetoothDevicePreference mPreference; @Before public void setUp() { @@ -71,6 +72,7 @@ public class SavedBluetoothDeviceUpdaterTest { mBluetoothDeviceUpdater = spy(new SavedBluetoothDeviceUpdater(mDashboardFragment, mDevicePreferenceCallback, mLocalManager)); mBluetoothDeviceUpdater.setPrefContext(mContext); + mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, false); doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); } @@ -110,4 +112,11 @@ public class SavedBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); } + + @Test + public void onClick_Preference_setConnect() { + mBluetoothDeviceUpdater.onPreferenceClick(mPreference); + + verify(mCachedBluetoothDevice).connect(true); + } }