From 79d7fdc4f326875163de598aac0c0cee50516a77 Mon Sep 17 00:00:00 2001 From: timhypeng Date: Fri, 25 May 2018 14:01:02 +0800 Subject: [PATCH 01/19] To show hearing aids device in the available devices group Bug: 116725094 Bug: 116044083 Bug: 79553082 Test: make -j50 RunSettingsRoboTests Change-Id: I8fe5eb653d332ef5ac4a566146f150774581167c Merged-In: I8fe5eb653d332ef5ac4a566146f150774581167c (cherry picked from commit 1c21bff1034b5124b69bda22c2fc9f70a449ffa7) --- .../AvailableMediaBluetoothDeviceUpdater.java | 5 ++++ .../ConnectedBluetoothDeviceUpdater.java | 5 ++++ ...ilableMediaBluetoothDeviceUpdaterTest.java | 27 ++++++++++++++++++ .../ConnectedBluetoothDeviceUpdaterTest.java | 28 +++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java index 01c1ff61832..a0a87006d71 100644 --- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java @@ -97,6 +97,11 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater if (DBG) { Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile); } + // If device is Hearing Aid, it is compatible with HFP and A2DP. + // It would show in Available Devices group. + if (cachedDevice.isConnectedHearingAidDevice()) { + return true; + } // According to the current audio profile type, // this page will show the bluetooth device that have corresponding profile. // For example: diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java index d43290c9566..b3be18735f7 100644 --- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java @@ -97,6 +97,11 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater { if (DBG) { Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile); } + // If device is Hearing Aid, it is compatible with HFP and A2DP. + // It would not show in Connected Devices group. + if (cachedDevice.isConnectedHearingAidDevice()) { + return false; + } // According to the current audio profile type, // this page will show the bluetooth device that doesn't have corresponding profile. // For example: diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java index ced8fc4a2e4..0b1311d9364 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java @@ -203,6 +203,33 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); } + @Test + public void onProfileConnectionStateChanged_hearingAidDeviceConnected_notInCall_addPreference() + { + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + when(mBluetoothDeviceUpdater. + isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + + mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + + verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); + } + + @Test + public void onProfileConnectionStateChanged_hearingAidDeviceConnected_inCall_addPreference() { + mShadowAudioManager.setMode(AudioManager.MODE_IN_CALL); + when(mBluetoothDeviceUpdater. + isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + + mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + + verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); + } + @Test public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() { mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index 483df01a6b8..8120d91d28f 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -204,6 +204,34 @@ public class ConnectedBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); } + @Test + public void onProfileConnectionStateChanged_hearingAidDeviceConnected_inCall_removePreference() + { + mShadowAudioManager.setMode(AudioManager.MODE_IN_CALL); + when(mBluetoothDeviceUpdater. + isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + + mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + + verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); + } + + @Test + public void onProfileConnectionStateChanged_hearingAidDeviceConnected_notInCall_removePreference + () { + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + when(mBluetoothDeviceUpdater. + isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + + mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + + verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); + } + @Test public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() { mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, From 020b0b04764f9ca850304cda60d5af3ab734f30d Mon Sep 17 00:00:00 2001 From: Stanley Tng Date: Wed, 20 Jun 2018 15:32:02 -0700 Subject: [PATCH 02/19] Add Feature Flag for Hearing Aid Profile Using the Settings App-Developer Options-Feature Flag, allow the user to enable or disable the Hearing Aid Profile. Bug: 116725094 Bug: 116044083 Test: Manual testing using Settings App Change-Id: I16b51d7feabc914219c24731eb39a23bd1782571 Merged-In: I16b51d7feabc914219c24731eb39a23bd1782571 (cherry picked from commit 068c2547f6f877273b09bf3ace5b4a4b61c7647a) --- .../android/settings/core/FeatureFlags.java | 1 + .../featureflags/FeatureFlagPersistent.java | 66 +++++++ .../featureflags/FeatureFlagPreference.java | 17 +- .../FeatureFlagPersistentTest.java | 162 ++++++++++++++++++ 4 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/development/featureflags/FeatureFlagPersistent.java create mode 100644 tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPersistentTest.java diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index e77c27b6a71..db941a6dd7c 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -26,4 +26,5 @@ public class FeatureFlags { public static final String BLUETOOTH_WHILE_DRIVING = "settings_bluetooth_while_driving"; public static final String DATA_USAGE_SETTINGS_V2 = "settings_data_usage_v2"; public static final String AUDIO_SWITCHER_SETTINGS = "settings_audio_switcher"; + public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; } diff --git a/src/com/android/settings/development/featureflags/FeatureFlagPersistent.java b/src/com/android/settings/development/featureflags/FeatureFlagPersistent.java new file mode 100644 index 00000000000..457b36219f5 --- /dev/null +++ b/src/com/android/settings/development/featureflags/FeatureFlagPersistent.java @@ -0,0 +1,66 @@ +/* + * 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.development.featureflags; + +import android.content.Context; +import android.os.SystemProperties; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; +import android.util.FeatureFlagUtils; +import android.util.Log; + +import com.android.settings.core.FeatureFlags; + +import java.util.HashSet; + +/** + * Helper class to get feature persistent flag information. + */ +public class FeatureFlagPersistent { + private static final HashSet PERSISTENT_FLAGS; + static { + PERSISTENT_FLAGS = new HashSet<>(); + PERSISTENT_FLAGS.add(FeatureFlags.HEARING_AID_SETTINGS); + } + + public static boolean isEnabled(Context context, String feature) { + String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + feature); + if (!TextUtils.isEmpty(value)) { + return Boolean.parseBoolean(value); + } else { + return FeatureFlagUtils.isEnabled(context, feature); + } + } + + public static void setEnabled(Context context, String feature, boolean enabled) { + SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX + feature, enabled ? "true" : "false"); + FeatureFlagUtils.setEnabled(context, feature, enabled); + } + + public static boolean isPersistent(String feature) { + return PERSISTENT_FLAGS.contains(feature); + } + + /** + * Returns all persistent flags in their raw form. + */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + static HashSet getAllPersistentFlags() { + return PERSISTENT_FLAGS; + } +} + diff --git a/src/com/android/settings/development/featureflags/FeatureFlagPreference.java b/src/com/android/settings/development/featureflags/FeatureFlagPreference.java index b5a4a158fe3..a6cdbaa7a0a 100644 --- a/src/com/android/settings/development/featureflags/FeatureFlagPreference.java +++ b/src/com/android/settings/development/featureflags/FeatureFlagPreference.java @@ -19,23 +19,36 @@ package com.android.settings.development.featureflags; import android.content.Context; import android.support.v14.preference.SwitchPreference; import android.util.FeatureFlagUtils; +import android.util.Log; public class FeatureFlagPreference extends SwitchPreference { private final String mKey; + private final boolean mIsPersistent; public FeatureFlagPreference(Context context, String key) { super(context); mKey = key; setKey(key); setTitle(key); - setCheckedInternal(FeatureFlagUtils.isEnabled(context, mKey)); + mIsPersistent = FeatureFlagPersistent.isPersistent(key); + boolean isFeatureEnabled; + if (mIsPersistent) { + isFeatureEnabled = FeatureFlagPersistent.isEnabled(context, key); + } else { + isFeatureEnabled = FeatureFlagUtils.isEnabled(context, key); + } + setCheckedInternal(isFeatureEnabled); } @Override public void setChecked(boolean isChecked) { setCheckedInternal(isChecked); - FeatureFlagUtils.setEnabled(getContext(), mKey, isChecked); + if (mIsPersistent) { + FeatureFlagPersistent.setEnabled(getContext(), mKey, isChecked); + } else { + FeatureFlagUtils.setEnabled(getContext(), mKey, isChecked); + } } private void setCheckedInternal(boolean isChecked) { diff --git a/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPersistentTest.java b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPersistentTest.java new file mode 100644 index 00000000000..c9f452de7d6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPersistentTest.java @@ -0,0 +1,162 @@ +/* + * 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.development.featureflags; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.settings.development.featureflags.FeatureFlagPersistent; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import android.content.Context; +import android.os.SystemProperties; +import android.provider.Settings; +import android.util.FeatureFlagUtils; +import android.util.Log; + +@RunWith(SettingsRobolectricTestRunner.class) +public class FeatureFlagPersistentTest { + + private static final String TEST_FEATURE_NAME = "test_feature"; + + private static final String PERSISTENT_FALSE_NAME = "false_persistent"; + private static final String PERSISTENT_TRUE_NAME = "true_persistent"; + private static final String VOLATILE_FALSE_NAME = "volatile_false_volatile"; + private static final String VOLATILE_TRUE_NAME = "volatile_true_volatile"; + + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + + FeatureFlagPersistent.getAllPersistentFlags().add(TEST_FEATURE_NAME); + FeatureFlagUtils.getAllFeatureFlags().put(TEST_FEATURE_NAME, "false"); + + FeatureFlagUtils.getAllFeatureFlags().put(VOLATILE_FALSE_NAME, "false"); + FeatureFlagUtils.getAllFeatureFlags().put(VOLATILE_TRUE_NAME, "true"); + + FeatureFlagPersistent.getAllPersistentFlags().add(PERSISTENT_FALSE_NAME); + FeatureFlagUtils.getAllFeatureFlags().put(PERSISTENT_FALSE_NAME, "false"); + + FeatureFlagPersistent.getAllPersistentFlags().add(PERSISTENT_TRUE_NAME); + FeatureFlagUtils.getAllFeatureFlags().put(PERSISTENT_TRUE_NAME, "true"); + } + + @After + public void tearDown() { + cleanup(PERSISTENT_FALSE_NAME); + cleanup(PERSISTENT_TRUE_NAME); + cleanup(VOLATILE_FALSE_NAME); + cleanup(VOLATILE_TRUE_NAME); + cleanup(TEST_FEATURE_NAME); + } + + private void cleanup(String flagName) { + Settings.Global.putString(mContext.getContentResolver(), flagName, ""); + SystemProperties.set(FeatureFlagUtils.FFLAG_PREFIX + flagName, ""); + SystemProperties.set(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX + flagName, ""); + SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX + flagName, ""); + } + + /** + * Test to verify a non-persistent flag is indeed not persistent. + */ + @Test + public void isPersistent_notPersistent_shouldReturnFalse() { + assertThat(FeatureFlagPersistent.isPersistent(VOLATILE_FALSE_NAME)).isFalse(); + } + + /** + * Test to verify a persistent flag is indeed persistent. + */ + @Test + public void isPersistent_persistent_shouldReturnTrue() { + assertThat(FeatureFlagPersistent.isPersistent(PERSISTENT_TRUE_NAME)).isTrue(); + } + + /** + * Test to verify a persistent flag that is enabled should return true. + */ + @Test + public void isEnabled_enabled_shouldReturnTrue() { + assertThat(FeatureFlagPersistent.isEnabled(mContext, PERSISTENT_TRUE_NAME)).isTrue(); + } + + /** + * Test to verify a persistent flag that is disabled should return false. + */ + @Test + public void isEnabled_disabled_shouldReturnFalse() { + assertThat(FeatureFlagPersistent.isEnabled(mContext, PERSISTENT_FALSE_NAME)).isFalse(); + } + + /** + * Test to verify a persistent flag that has an enabled in system property should return true. + */ + @Test + public void isEnabled_sysPropEnabled_shouldReturnTrue() { + SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX + TEST_FEATURE_NAME, "true"); + FeatureFlagUtils.setEnabled(mContext, TEST_FEATURE_NAME, false); + + assertThat(FeatureFlagPersistent.isEnabled(mContext, TEST_FEATURE_NAME)).isTrue(); + } + + /** + * Test to verify a persistent flag that is disabled in system property should return false. + */ + @Test + public void isEnabled_sysPropDisabled_shouldReturnFalse() { + SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX + TEST_FEATURE_NAME, "false"); + FeatureFlagUtils.setEnabled(mContext, TEST_FEATURE_NAME, true); + + assertThat(FeatureFlagPersistent.isEnabled(mContext, TEST_FEATURE_NAME)).isFalse(); + } + + /** + * Test to verify setting persistent flag to enable works. + */ + @Test + public void setEnabled_sysPropTrue_shouldSetValues() { + SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX + TEST_FEATURE_NAME, ""); + + FeatureFlagPersistent.setEnabled(mContext, TEST_FEATURE_NAME, true); + + assertThat(SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + TEST_FEATURE_NAME)).isEqualTo("true"); + assertThat(FeatureFlagUtils.isEnabled(mContext, TEST_FEATURE_NAME)).isTrue(); + } + + /** + * Test to verify setting persistent flag to disable works. + */ + @Test + public void setEnabled_sysPropFalse_shouldSetValues() { + SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX + TEST_FEATURE_NAME, ""); + + FeatureFlagPersistent.setEnabled(mContext, TEST_FEATURE_NAME, false); + + assertThat(SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + TEST_FEATURE_NAME)).isEqualTo("false"); + assertThat(FeatureFlagUtils.isEnabled(mContext, TEST_FEATURE_NAME)).isFalse(); + } +} + From e04daba6719beddb7a42b7573de81eefe1f7c140 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 26 Sep 2018 15:41:40 -0700 Subject: [PATCH 03/19] Set the layoutPreference not selectable In this way it won't yell out "Double-tap to activate" Change-Id: Ie5d97f91d32ab7db1f1cec308521e7327d1fbaa4 Fixes: 116666315 Test: Manual --- .../android/settings/notification/BlockPreferenceController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/notification/BlockPreferenceController.java b/src/com/android/settings/notification/BlockPreferenceController.java index 9e4c4632004..bee32f511a2 100644 --- a/src/com/android/settings/notification/BlockPreferenceController.java +++ b/src/com/android/settings/notification/BlockPreferenceController.java @@ -65,6 +65,7 @@ public class BlockPreferenceController extends NotificationPreferenceController public void updateState(Preference preference) { LayoutPreference pref = (LayoutPreference) preference; + pref.setSelectable(false); SwitchBar bar = pref.findViewById(R.id.switch_bar); if (bar != null) { bar.setSwitchBarText(R.string.notification_switch_label, From e402cb2744c0dd3c42ca80df13ec2f1d4e1f8814 Mon Sep 17 00:00:00 2001 From: tmfang Date: Fri, 22 Jun 2018 16:56:37 +0800 Subject: [PATCH 04/19] Support phone number in Settings slices - Let controller of phoner number extends BasePreferenceController, so it can be supported showing slice view by SettingsSliceProvider. Test: make RunSettingsRoboTests Bug: 74900516 Change-Id: Id98e6aac981025159a0530c6e0709ba0f2ebcd15 --- res/xml/my_device_info.xml | 5 ++- .../PhoneNumberPreferenceController.java | 41 ++++++++++++------- .../aboutphone/MyDeviceInfoFragment.java | 2 - .../PhoneNumberPreferenceControllerTest.java | 40 +++++++++++++----- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml index 535f61afa5d..c05d6ddb0e5 100644 --- a/res/xml/my_device_info.xml +++ b/res/xml/my_device_info.xml @@ -47,7 +47,10 @@ android:order="3" android:title="@string/status_number" android:summary="@string/summary_placeholder" - android:selectable="false"/> + android:selectable="false" + settings:allowDynamicSummaryInSlice="true" + settings:controller= + "com.android.settings.deviceinfo.PhoneNumberPreferenceController" /> mPreferenceList = new ArrayList<>(); - public PhoneNumberPreferenceController(Context context) { - super(context); - mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - mSubscriptionManager = (SubscriptionManager) context.getSystemService( - Context.TELEPHONY_SUBSCRIPTION_SERVICE); + public PhoneNumberPreferenceController(Context context, String key) { + super(context, key); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); } @Override - public String getPreferenceKey() { - return KEY_PHONE_NUMBER; + public int getAvailabilityStatus() { + return mTelephonyManager.isVoiceCapable() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override - public boolean isAvailable() { - return mTelephonyManager.isVoiceCapable(); + public CharSequence getSummary() { + return getFirstPhoneNumber(); } @Override @@ -89,10 +86,26 @@ public class PhoneNumberPreferenceController extends AbstractPreferenceControlle } } + @Override + public boolean isSliceable() { + return true; + } + + private CharSequence getFirstPhoneNumber() { + final List subscriptionInfoList = + mSubscriptionManager.getActiveSubscriptionInfoList(); + if (subscriptionInfoList == null) { + return mContext.getText(R.string.device_info_default); + } + + // For now, We only return first result for slice view. + return getFormattedPhoneNumber(subscriptionInfoList.get(0)); + } + private CharSequence getPhoneNumber(int simSlot) { final SubscriptionInfo subscriptionInfo = getSubscriptionInfo(simSlot); if (subscriptionInfo == null) { - return mContext.getString(R.string.device_info_default); + return mContext.getText(R.string.device_info_default); } return getFormattedPhoneNumber(subscriptionInfo); diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java index 5503ea97677..37f80b736ea 100644 --- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java +++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java @@ -41,7 +41,6 @@ import com.android.settings.deviceinfo.FccEquipmentIdPreferenceController; import com.android.settings.deviceinfo.FeedbackPreferenceController; import com.android.settings.deviceinfo.IpAddressPreferenceController; import com.android.settings.deviceinfo.ManualPreferenceController; -import com.android.settings.deviceinfo.PhoneNumberPreferenceController; import com.android.settings.deviceinfo.RegulatoryInfoPreferenceController; import com.android.settings.deviceinfo.SafetyInfoPreferenceController; import com.android.settings.deviceinfo.UptimePreferenceController; @@ -110,7 +109,6 @@ public class MyDeviceInfoFragment extends DashboardFragment Lifecycle lifecycle) { final List controllers = new ArrayList<>(); controllers.add(new EmergencyInfoPreferenceController(context)); - controllers.add(new PhoneNumberPreferenceController(context)); controllers.add(new BrandedAccountPreferenceController(context)); DeviceNamePreferenceController deviceNamePreferenceController = new DeviceNamePreferenceController(context); diff --git a/tests/robotests/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java index 4748135a461..82056a2d22b 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java @@ -16,8 +16,7 @@ package com.android.settings.deviceinfo; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -27,12 +26,14 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; @@ -41,7 +42,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; -import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) public class PhoneNumberPreferenceControllerTest { @@ -55,6 +55,8 @@ public class PhoneNumberPreferenceControllerTest { @Mock private SubscriptionInfo mSubscriptionInfo; @Mock + private SubscriptionManager mSubscriptionManager; + @Mock private PreferenceScreen mScreen; private Context mContext; @@ -63,9 +65,10 @@ public class PhoneNumberPreferenceControllerTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mController = spy(new PhoneNumberPreferenceController(mContext)); - ReflectionHelpers.setField(mController, "mTelephonyManager", mTelephonyManager); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + mController = spy(new PhoneNumberPreferenceController(mContext, "phone_number")); final String prefKey = mController.getPreferenceKey(); when(mScreen.findPreference(prefKey)).thenReturn(mPreference); when(mScreen.getContext()).thenReturn(mContext); @@ -75,17 +78,19 @@ public class PhoneNumberPreferenceControllerTest { } @Test - public void isAvailable_shouldBeTrueIfCallCapable() { + public void getAvailabilityStatus_isVoiceCapable_shouldBeAVAILABLE() { when(mTelephonyManager.isVoiceCapable()).thenReturn(true); - assertTrue(mController.isAvailable()); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); } @Test - public void isAvailable_shouldBeFalseIfNotCallCapable() { + public void getAvailabilityStatus_isNotVoiceCapable_shouldBeUNSUPPORTED_ON_DEVICE() { when(mTelephonyManager.isVoiceCapable()).thenReturn(false); - assertFalse(mController.isAvailable()); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.UNSUPPORTED_ON_DEVICE); } @Test @@ -126,4 +131,19 @@ public class PhoneNumberPreferenceControllerTest { mContext.getString(R.string.status_number_sim_slot, 2 /* sim slot */)); verify(mSecondPreference).setSummary(phoneNumber); } + + @Test + public void getSummary_cannotGetActiveSubscriptionInfo_shouldShowUnknown() { + when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null); + + CharSequence primaryNumber = mController.getSummary(); + + assertThat(primaryNumber).isNotNull(); + assertThat(primaryNumber).isEqualTo(mContext.getString(R.string.device_info_default)); + } + + @Test + public void isSliceable_shouldBeTrue() { + assertThat(mController.isSliceable()).isTrue(); + } } From e9c8a0f264768cca863d2a8dcfa9741525966cfe Mon Sep 17 00:00:00 2001 From: Irina Dumitrescu Date: Tue, 11 Sep 2018 15:19:38 +0100 Subject: [PATCH 05/19] Settings change for device-wide unknown sources block. Update the "Install unknown apps" preferences to display the option is disabled if the global user restriction for unknown apps is turned on. Test: Manual test, disallowing installs in TestDPC disables installing unknown sources apps. Also: atest packages/apps/Settings/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java Bug: 111335021 Change-Id: I235e5fd110e296ed43aa89d7a5ec0d48470c4023 --- .../appinfo/ExternalSourcesDetails.java | 23 +-- .../appinfo/ExternalSourcesDetailsTest.java | 138 +++++++++++++++++- 2 files changed, 148 insertions(+), 13 deletions(-) diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java index be41c0ff5cc..38e70d1ee17 100644 --- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java +++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java @@ -80,21 +80,20 @@ public class ExternalSourcesDetails extends AppInfoWithHeader } public static CharSequence getPreferenceSummary(Context context, AppEntry entry) { + final UserHandle userHandle = UserHandle.getUserHandleForUid(entry.info.uid); final UserManager um = UserManager.get(context); final int userRestrictionSource = um.getUserRestrictionSource( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, - UserHandle.getUserHandleForUid(entry.info.uid)); - switch (userRestrictionSource) { - case UserManager.RESTRICTION_SOURCE_DEVICE_OWNER: - case UserManager.RESTRICTION_SOURCE_PROFILE_OWNER: - return context.getString(R.string.disabled_by_admin); - case UserManager.RESTRICTION_SOURCE_SYSTEM: - return context.getString(R.string.disabled); + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, userHandle) + | um.getUserRestrictionSource( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, + userHandle); + if ((userRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) { + return context.getString(R.string.disabled_by_admin); + } else if (userRestrictionSource != 0) { + return context.getString(R.string.disabled); } - final InstallAppsState appsState = new AppStateInstallAppsBridge(context, null, null) .createInstallAppsStateFor(entry.info.packageName, entry.info.uid); - return context.getString(appsState.canInstallApps() ? R.string.app_permission_summary_allowed : R.string.app_permission_summary_not_allowed); @@ -119,6 +118,10 @@ public class ExternalSourcesDetails extends AppInfoWithHeader return true; } mSwitchPref.checkRestrictionAndSetDisabled(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); + if (!mSwitchPref.isDisabledByAdmin()) { + mSwitchPref.checkRestrictionAndSetDisabled( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY); + } if (mSwitchPref.isDisabledByAdmin()) { return true; } diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java index d636fa468ef..e625eee5c9c 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java @@ -17,29 +17,36 @@ package com.android.settings.applications.appinfo; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.Context; +import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.os.UserHandle; import android.os.UserManager; - import com.android.settings.applications.AppStateInstallAppsBridge; import com.android.settings.applications.AppStateInstallAppsBridge.InstallAppsState; +import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedPreferenceHelper; import com.android.settingslib.RestrictedSwitchPreference; - 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 org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {ShadowUserManager.class}) public class ExternalSourcesDetailsTest { @Mock @@ -47,6 +54,8 @@ public class ExternalSourcesDetailsTest { @Mock private RestrictedSwitchPreference mSwitchPref; @Mock + private RestrictedPreferenceHelper mHelper; + @Mock private PackageInfo mPackageInfo; private ExternalSourcesDetails mFragment; @@ -90,5 +99,128 @@ public class ExternalSourcesDetailsTest { mFragment.refreshUi(); assertThat(mFragment.refreshUi()).isTrue(); + assertThat(mSwitchPref.isDisabledByAdmin()).isFalse(); + } + + @Test + public void refreshUi_userRestrictionsUnknownSources_disablesSwitchPreference() { + // Mocks set up + final ExternalSourcesDetails fragment = new ExternalSourcesDetails(); + final ContextWrapper context = RuntimeEnvironment.application; + final UserManager userManager = (UserManager) context.getSystemService( + Context.USER_SERVICE); + final ShadowUserManager shadowUserManager = Shadow.extract(userManager); + + ReflectionHelpers.setField(fragment, "mSwitchPref", mSwitchPref); + ReflectionHelpers.setField(fragment, "mPackageInfo", mPackageInfo); + mPackageInfo.applicationInfo = new ApplicationInfo(); + ReflectionHelpers.setField(fragment, "mUserManager", userManager); + ReflectionHelpers.setField(mSwitchPref, "mHelper", mHelper); + + final AppStateInstallAppsBridge appBridge = mock(AppStateInstallAppsBridge.class); + ReflectionHelpers.setField(fragment, "mAppBridge", appBridge); + when(appBridge.createInstallAppsStateFor(nullable(String.class), anyInt())) + .thenReturn(mock(InstallAppsState.class)); + + // Test restriction set up + shadowUserManager.setUserRestriction(UserHandle.of(UserHandle.myUserId()), + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true); + doAnswer((answer) -> { + when(mSwitchPref.isDisabledByAdmin()).thenReturn(true); + return null; + }).when(mSwitchPref).checkRestrictionAndSetDisabled( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); + + // Code execution + assertThat(fragment.refreshUi()).isTrue(); + + // Assertions + assertThat(shadowUserManager.hasUserRestriction( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserHandle.of(UserHandle.myUserId()))).isTrue(); + assertThat(mSwitchPref.isDisabledByAdmin()).isTrue(); + } + + @Test + public void refreshUi_userRestrictionsUnknownSourcesGlobally_disablesSwitchPreference() { + // Mocks set up + final ExternalSourcesDetails fragment = new ExternalSourcesDetails(); + final ContextWrapper context = RuntimeEnvironment.application; + final UserManager userManager = (UserManager) context.getSystemService( + Context.USER_SERVICE); + final ShadowUserManager shadowUserManager = Shadow.extract(userManager); + + ReflectionHelpers.setField(fragment, "mSwitchPref", mSwitchPref); + ReflectionHelpers.setField(fragment, "mPackageInfo", mPackageInfo); + mPackageInfo.applicationInfo = new ApplicationInfo(); + ReflectionHelpers.setField(fragment, "mUserManager", userManager); + ReflectionHelpers.setField(mSwitchPref, "mHelper", mHelper); + + final AppStateInstallAppsBridge appBridge = mock(AppStateInstallAppsBridge.class); + ReflectionHelpers.setField(fragment, "mAppBridge", appBridge); + when(appBridge.createInstallAppsStateFor(nullable(String.class), anyInt())) + .thenReturn(mock(InstallAppsState.class)); + + // Test restriction set up + shadowUserManager.setUserRestriction(UserHandle.of(UserHandle.myUserId()), + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, true); + doAnswer((answer) -> { + when(mSwitchPref.isDisabledByAdmin()).thenReturn(true); + return null; + }).when(mSwitchPref).checkRestrictionAndSetDisabled( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY); + + // Code execution + assertThat(fragment.refreshUi()).isTrue(); + + // Assertions + assertThat(shadowUserManager.hasUserRestriction( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, + UserHandle.of(UserHandle.myUserId()))).isTrue(); + assertThat(mSwitchPref.isDisabledByAdmin()).isTrue(); + } + + @Test + public void refreshUi_bothUnknownSourcesUserRestrictions_disableSwitchPreference() { + // Mocks set up + final ExternalSourcesDetails fragment = new ExternalSourcesDetails(); + final ContextWrapper context = RuntimeEnvironment.application; + final UserManager userManager = (UserManager) context.getSystemService( + Context.USER_SERVICE); + final ShadowUserManager shadowUserManager = Shadow.extract(userManager); + + ReflectionHelpers.setField(fragment, "mSwitchPref", mSwitchPref); + ReflectionHelpers.setField(fragment, "mPackageInfo", mPackageInfo); + mPackageInfo.applicationInfo = new ApplicationInfo(); + ReflectionHelpers.setField(fragment, "mUserManager", userManager); + ReflectionHelpers.setField(mSwitchPref, "mHelper", mHelper); + + final AppStateInstallAppsBridge appBridge = mock(AppStateInstallAppsBridge.class); + ReflectionHelpers.setField(fragment, "mAppBridge", appBridge); + when(appBridge.createInstallAppsStateFor(nullable(String.class), anyInt())) + .thenReturn(mock(InstallAppsState.class)); + + // Test restriction set up + shadowUserManager.setUserRestriction(UserHandle.of(UserHandle.myUserId()), + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, true); + shadowUserManager.setUserRestriction(UserHandle.of(UserHandle.myUserId()), + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true); + doAnswer((answer) -> { + when(mSwitchPref.isDisabledByAdmin()).thenReturn(true); + return null; + }).when(mSwitchPref).checkRestrictionAndSetDisabled( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY); + + // Code execution + assertThat(fragment.refreshUi()).isTrue(); + + // Assertions + assertThat(shadowUserManager.hasUserRestriction( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, + UserHandle.of(UserHandle.myUserId()))).isTrue(); + assertThat(shadowUserManager.hasUserRestriction( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserHandle.of(UserHandle.myUserId()))).isTrue(); + assertThat(mSwitchPref.isDisabledByAdmin()).isTrue(); } } From 92dce919680eb48d7a25a5bf145801fdcd5b8759 Mon Sep 17 00:00:00 2001 From: Chienyuan Date: Thu, 31 May 2018 14:24:06 +0800 Subject: [PATCH 06/19] Extend limitation of A2DP HW offload related string. With current limitation, the UI of A2DP HW offload can't be display in some language, e.g., Arabic (ar) and Hindi (hi). Bug: 80149220 Bug: 80164750 Bug: 80149375 Test: build pass Change-Id: I95f6b4b21201bb466260b71f238b9c6b700bc850 --- res/values/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 119e1fb7f43..90a46aebab5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -384,15 +384,15 @@ Allow your phone to communicate with nearby Bluetooth devices - + Disable Bluetooth A2DP hardware offload - + Restart Device? - + You need to restart your device to change this setting. - + Restart - + Cancel From af9eb2d7a7bcdb0b4e99aa59b863e317e61a5e60 Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Mon, 1 Oct 2018 18:34:22 +0800 Subject: [PATCH 07/19] Application accepts null intents causing a crash Change-Id: Ia7f98f43567f25ae7abc6e5ac7e07323e75d9047 Fixes: 116869239 Test: adb shell am start -n com.android.settings/com.android.settings.Settings\\\$ApnEditorActivity Test: make RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.network" --- src/com/android/settings/network/ApnEditor.java | 6 +++++- .../com/android/settings/network/ApnEditorTest.java | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/network/ApnEditor.java b/src/com/android/settings/network/ApnEditor.java index 6b99e1ffeab..633fc45cb3c 100644 --- a/src/com/android/settings/network/ApnEditor.java +++ b/src/com/android/settings/network/ApnEditor.java @@ -231,9 +231,13 @@ public class ApnEditor extends SettingsPreferenceFragment final Intent intent = getIntent(); final String action = intent.getAction(); + if (TextUtils.isEmpty(action)) { + finish(); + return; + } + mSubId = intent.getIntExtra(ApnSettings.SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mReadOnlyApn = false; mReadOnlyApnTypes = null; mReadOnlyApnFields = null; diff --git a/tests/robotests/src/com/android/settings/network/ApnEditorTest.java b/tests/robotests/src/com/android/settings/network/ApnEditorTest.java index cc480c373e7..b6242c38ec7 100644 --- a/tests/robotests/src/com/android/settings/network/ApnEditorTest.java +++ b/tests/robotests/src/com/android/settings/network/ApnEditorTest.java @@ -19,11 +19,13 @@ package com.android.settings.network; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.ContentValues; @@ -450,6 +452,15 @@ public class ApnEditorTest { assertThat(ApnEditor.formatInteger("not an int")).isEqualTo("not an int"); } + @Test + public void onCreate_noAction_shouldFinishAndNoCrash() { + doNothing().when(mApnEditorUT).addPreferencesFromResource(anyInt()); + + mApnEditorUT.onCreate(null); + + verify(mApnEditorUT).finish(); + } + private void initCursor() { doReturn(2).when(mCursor).getColumnCount(); doReturn(Integer.valueOf(2)).when(mCursor).getInt(CURSOR_INTEGER_INDEX); From 863c60ffcfc058b95db14355c14682355086b8dc Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Mon, 1 Oct 2018 11:34:41 -0700 Subject: [PATCH 08/19] Add version 2 of AppDataUsagePreferenceController. - a simple copy of AppDataUsagePreferenceController to AppDataUsagePreferenceControllerV2, so that future modification can be compared more easily. Bug: 111751694 Test: make RunSettingsRoboTests Change-Id: Ibeed7775a223794bf50f36b3b933e270a55e3b39 --- .../AppDataUsagePreferenceController.java | 6 + .../AppDataUsagePreferenceControllerV2.java | 149 ++++++++++++++++++ ...ppDataUsagePreferenceControllerV2Test.java | 138 ++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2.java create mode 100644 tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java diff --git a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java index 4337b88bd53..8eb2dfcdf28 100644 --- a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java @@ -45,6 +45,12 @@ import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.net.ChartData; import com.android.settingslib.net.ChartDataLoaderCompat; +/** + * Deprecated in favor of {@link AppDataUsagePreferenceControllerV2} + * + * @deprecated + */ +@Deprecated public class AppDataUsagePreferenceController extends AppInfoPreferenceControllerBase implements LoaderManager.LoaderCallbacks, LifecycleObserver, OnResume, OnPause { diff --git a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2.java b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2.java new file mode 100644 index 00000000000..8e318bdbefb --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 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.applications.appinfo; + +import android.content.Context; +import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.NetworkTemplate; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.format.DateUtils; +import android.text.format.Formatter; + +import androidx.annotation.VisibleForTesting; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.datausage.AppDataUsage; +import com.android.settings.datausage.DataUsageList; +import com.android.settings.datausage.DataUsageUtils; +import com.android.settingslib.AppItem; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoaderCompat; + +public class AppDataUsagePreferenceControllerV2 extends AppInfoPreferenceControllerBase + implements LoaderManager.LoaderCallbacks, LifecycleObserver, OnResume, OnPause { + + private ChartData mChartData; + private INetworkStatsSession mStatsSession; + + public AppDataUsagePreferenceControllerV2(Context context,String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return isBandwidthControlEnabled() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + try { + mStatsSession = statsService.openSession(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getDataSummary()); + } + + @Override + public void onResume() { + if (mStatsSession != null) { + final int uid = mParent.getAppEntry().info.uid; + final AppItem app = new AppItem(uid); + app.addUid(uid); + mParent.getLoaderManager().restartLoader(mParent.LOADER_CHART_DATA, + ChartDataLoaderCompat.buildArgs(getTemplate(mContext), app), + this); + } + } + + @Override + public void onPause() { + mParent.getLoaderManager().destroyLoader(mParent.LOADER_CHART_DATA); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new ChartDataLoaderCompat(mContext, mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader loader, ChartData data) { + mChartData = data; + updateState(mPreference); + } + + @Override + public void onLoaderReset(Loader loader) { + // Leave last result. + } + + @Override + protected Class getDetailFragmentClass() { + return AppDataUsage.class; + } + + private CharSequence getDataSummary() { + if (mChartData != null) { + final long totalBytes = mChartData.detail.getTotalBytes(); + if (totalBytes == 0) { + return mContext.getString(R.string.no_data_usage); + } + return mContext.getString(R.string.data_summary_format, + Formatter.formatFileSize(mContext, totalBytes), + DateUtils.formatDateTime(mContext, mChartData.detail.getStart(), + DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH)); + } + return mContext.getString(R.string.computing_size); + } + + private static NetworkTemplate getTemplate(Context context) { + if (DataUsageUtils.hasReadyMobileRadio(context)) { + return NetworkTemplate.buildTemplateMobileWildcard(); + } + if (DataUsageUtils.hasWifiRadio(context)) { + return NetworkTemplate.buildTemplateWifiWildcard(); + } + return NetworkTemplate.buildTemplateEthernet(); + } + + @VisibleForTesting + boolean isBandwidthControlEnabled() { + return Utils.isBandwidthControlEnabled(); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java new file mode 100644 index 00000000000..0023250849b --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java @@ -0,0 +1,138 @@ +/* + * 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.applications.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.net.ConnectivityManager; +import android.net.INetworkStatsSession; +import android.os.Bundle; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.datausage.AppDataUsage; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.ApplicationsState.AppEntry; + +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 org.robolectric.util.ReflectionHelpers; + +import androidx.loader.app.LoaderManager; +import androidx.preference.Preference; + +@RunWith(SettingsRobolectricTestRunner.class) +public class AppDataUsagePreferenceControllerV2Test { + + @Mock + private LoaderManager mLoaderManager; + @Mock + private AppInfoDashboardFragment mFragment; + + private Context mContext; + private AppDataUsagePreferenceControllerV2 mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application.getApplicationContext()); + mController = spy(new AppDataUsagePreferenceControllerV2(mContext, "test_key")); + mController.setParentFragment(mFragment); + } + + @Test + public void getAvailabilityStatus_bandwidthControlEnabled_shouldReturnAvailable() { + doReturn(true).when(mController).isBandwidthControlEnabled(); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_bandwidthControlDisabled_shouldReturnDisabled() { + doReturn(false).when(mController).isBandwidthControlEnabled(); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void onResume_noSession_shouldNotRestartDataLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onResume(); + + verify(mLoaderManager, never()).restartLoader( + AppInfoDashboardFragment.LOADER_CHART_DATA, Bundle.EMPTY, mController); + } + + @Test + public void onResume_hasSession_shouldRestartDataLoader() { + final ConnectivityManager connectivityManager = mock(ConnectivityManager.class); + when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) + .thenReturn(connectivityManager); + when(connectivityManager.isNetworkSupported(anyInt())).thenReturn(true); + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + ReflectionHelpers.setField(mController, "mStatsSession", mock(INetworkStatsSession.class)); + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.onResume(); + + verify(mLoaderManager).restartLoader( + eq(AppInfoDashboardFragment.LOADER_CHART_DATA), any(Bundle.class), eq(mController)); + } + + @Test + public void onPause_shouldDestroyDataLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onPause(); + + verify(mLoaderManager).destroyLoader(AppInfoDashboardFragment.LOADER_CHART_DATA); + } + + @Test + public void getDetailFragmentClass_shouldReturnAppDataUsage() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(AppDataUsage.class); + } + + @Test + public void updateState_shouldUpdatePreferenceSummary() { + final Preference preference = mock(Preference.class); + + mController.updateState(preference); + + verify(preference).setSummary(any()); + } +} From 5fbdeaae7c51597ea4af1d2fc7d0b6e992c52250 Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 1 Oct 2018 14:29:37 -0400 Subject: [PATCH 09/19] Use BidiFormatter in ZenDeleteRuleDialog Test: visual inspection Change-Id: I99231ed5801f3286421f331554fc5c51c2a2d08c Fixes: 35960612 --- .../settings/notification/ZenDeleteRuleDialog.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/notification/ZenDeleteRuleDialog.java b/src/com/android/settings/notification/ZenDeleteRuleDialog.java index ea582d19b88..804196edccb 100644 --- a/src/com/android/settings/notification/ZenDeleteRuleDialog.java +++ b/src/com/android/settings/notification/ZenDeleteRuleDialog.java @@ -19,15 +19,16 @@ package com.android.settings.notification; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; +import android.text.BidiFormatter; import android.view.View; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; - import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + public class ZenDeleteRuleDialog extends InstrumentedDialogFragment { protected static final String TAG = "ZenDeleteRuleDialog"; private static final String EXTRA_ZEN_RULE_NAME = "zen_rule_name"; @@ -43,8 +44,9 @@ public class ZenDeleteRuleDialog extends InstrumentedDialogFragment { public static void show(Fragment parent, String ruleName, String id, PositiveClickListener listener) { + final BidiFormatter bidi = BidiFormatter.getInstance(); final Bundle args = new Bundle(); - args.putString(EXTRA_ZEN_RULE_NAME, ruleName); + args.putString(EXTRA_ZEN_RULE_NAME, bidi.unicodeWrap(ruleName)); args.putString(EXTRA_ZEN_RULE_ID, id); mPositiveClickListener = listener; From cf809c34b79c46de8a606b966b2dd7bbb2516776 Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Mon, 1 Oct 2018 13:48:43 -0700 Subject: [PATCH 10/19] Do not resolve permission in settings. Settings already uses the RuntimePermissionPresenter to resolve permissions for an app. This code must be the previous implementation. It can be removed without any issues. Test: Built Change-Id: Ie1375de1e23f4c9bb882a13ef7ade9adc543c82a --- .../settings/applications/AppPermissions.java | 169 ------------------ 1 file changed, 169 deletions(-) delete mode 100644 src/com/android/settings/applications/AppPermissions.java diff --git a/src/com/android/settings/applications/AppPermissions.java b/src/com/android/settings/applications/AppPermissions.java deleted file mode 100644 index 6299921e510..00000000000 --- a/src/com/android/settings/applications/AppPermissions.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2015 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.applications; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PermissionInfo; -import android.os.Build; -import android.util.ArrayMap; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * Based off from - * packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/AppPermissions.java - * Except we only care about the number rather than the details. - */ -public final class AppPermissions { - private static final String TAG = "AppPermissions"; - - private final ArrayMap mGroups = new ArrayMap<>(); - private final Context mContext; - private final PackageInfo mPackageInfo; - - public AppPermissions(Context context, String packageName) { - mContext = context; - mPackageInfo = getPackageInfo(packageName); - refresh(); - } - - private PackageInfo getPackageInfo(String packageName) { - try { - return mContext.getPackageManager().getPackageInfo(packageName, - PackageManager.GET_PERMISSIONS); - } catch (NameNotFoundException e) { - Log.e(TAG, "Unable to find " + packageName, e); - return null; - } - } - - public void refresh() { - if (mPackageInfo != null) { - loadPermissionGroups(); - } - } - - public int getPermissionCount() { - return mGroups.size(); - } - - public int getGrantedPermissionsCount() { - int ct = 0; - for (int i = 0; i < mGroups.size(); i++) { - if (mGroups.valueAt(i).areRuntimePermissionsGranted()) { - ct++; - } - } - return ct; - } - - private void loadPermissionGroups() { - mGroups.clear(); - if (mPackageInfo.requestedPermissions == null) { - return; - } - - final boolean appSupportsRuntimePermissions = appSupportsRuntime( - mPackageInfo.applicationInfo); - - for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { - String requestedPerm = mPackageInfo.requestedPermissions[i]; - - final PermissionInfo permInfo; - try { - permInfo = mContext.getPackageManager().getPermissionInfo(requestedPerm, 0); - } catch (NameNotFoundException e) { - Log.w(TAG, "Unknown permission: " + requestedPerm); - continue; - } - - String permName = permInfo.name; - String groupName = permInfo.group != null ? permInfo.group : permName; - - PermissionGroup group = mGroups.get(groupName); - if (group == null) { - group = new PermissionGroup(); - mGroups.put(groupName, group); - } - - final boolean runtime = appSupportsRuntimePermissions - && permInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS; - final boolean granted = (mPackageInfo.requestedPermissionsFlags[i] - & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; - - Permission permission = new Permission(runtime, granted); - group.addPermission(permission, permName); - } - // Only care about runtime perms for now. - for (int i = mGroups.size() - 1; i >= 0; i--) { - if (!mGroups.valueAt(i).mHasRuntimePermissions) { - mGroups.removeAt(i); - } - } - } - - public static boolean appSupportsRuntime(ApplicationInfo info) { - return info.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; - } - - private static final class PermissionGroup { - private final ArrayMap mPermissions = new ArrayMap<>(); - private boolean mHasRuntimePermissions; - - public boolean hasRuntimePermissions() { - return mHasRuntimePermissions; - } - - public boolean areRuntimePermissionsGranted() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.runtime && !permission.granted) { - return false; - } - } - return true; - } - - public List getPermissions() { - return new ArrayList<>(mPermissions.values()); - } - - void addPermission(Permission permission, String permName) { - mPermissions.put(permName, permission); - if (permission.runtime) { - mHasRuntimePermissions = true; - } - } - } - - private static final class Permission { - private final boolean runtime; - private boolean granted; - - public Permission(boolean runtime, boolean granted) { - this.runtime = runtime; - this.granted = granted; - } - } -} From d15bcdeb606bc2c68726a6513e4ecda6d5798f03 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Mon, 1 Oct 2018 17:47:35 -0700 Subject: [PATCH 11/19] Do PackageManager check before getting system service Fixes: 117074737 Test: Does not crash Change-Id: I51b04f46f8d467c3a854c132415a2cad90b77203 --- .../face/FaceSettingsAttentionPreferenceController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java index e26f8813fca..9c4f964c7fd 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java @@ -18,8 +18,8 @@ package com.android.settings.biometrics.face; import android.content.Context; import android.hardware.face.FaceManager; -import android.util.Log; +import com.android.settings.Utils; import com.android.settings.core.TogglePreferenceController; import androidx.preference.PreferenceScreen; @@ -39,7 +39,7 @@ public class FaceSettingsAttentionPreferenceController extends TogglePreferenceC public FaceSettingsAttentionPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); - mFaceManager = context.getSystemService(FaceManager.class); + mFaceManager = Utils.getFaceManagerOrNull(context); } public FaceSettingsAttentionPreferenceController(Context context) { From 69e1bb1cf3b2f7d76ff044c9f7d26b98dd4a600d Mon Sep 17 00:00:00 2001 From: Stanley Tng Date: Sun, 30 Sep 2018 22:47:22 -0700 Subject: [PATCH 12/19] Fix Connect State message in Device details for Hearing Aids In the Device details of Settings App and when using two Hearing Aids devices (left and right sides), this will fix the connect state messages for these two devices. Also added Robo tests for the changes. Bug: 116725094 Bug: 117074814 Test: Manual tests and also ran RunSettingsLibRoboTests and RunSettingsRoboTests. Change-Id: I169cda4a1658b0a67cc7c7367b38d57a021e6953 Merged-In: I0b1a170967ddcce7f388603fd521f6ed1eeba30b Merged-In: I169cda4a1658b0a67cc7c7367b38d57a021e6953 --- .../BluetoothDetailsHeaderController.java | 14 ++++++++++---- .../BluetoothDetailsHeaderControllerTest.java | 11 ++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java index 8679b5566c7..2170423e55f 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.support.v14.preference.PreferenceFragment; import android.support.v7.preference.PreferenceScreen; +import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; @@ -36,6 +37,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; */ public class BluetoothDetailsHeaderController extends BluetoothDetailsController { private static final String KEY_DEVICE_HEADER = "bluetooth_device_header"; + private static final String TAG = "BluetoothDetailsHeaderController"; private EntityHeaderController mHeaderController; private LocalBluetoothManager mLocalManager; @@ -63,12 +65,16 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController .getBtClassDrawableWithDescription(mContext, mCachedDevice, mContext.getResources().getFraction(R.fraction.bt_battery_scale_fraction, 1, 1)); String summaryText = mCachedDevice.getConnectionSummary(); - // If both the hearing aids are connected, two battery status should be shown. - final String pairDeviceSummary = mDeviceManager - .getHearingAidPairDeviceSummary(mCachedDevice); - if (pairDeviceSummary != null) { + + if (mCachedDevice.isHearingAidDevice()) { + // For Hearing Aid device, display the other battery status. + final String pairDeviceSummary = mDeviceManager + .getHearingAidPairDeviceSummary(mCachedDevice); + Log.d(TAG, "setHeaderProperties: HearingAid: summaryText=" + summaryText + + ", pairDeviceSummary=" + pairDeviceSummary); mHeaderController.setSecondSummary(pairDeviceSummary); } + mHeaderController.setLabel(mCachedDevice.getName()); mHeaderController.setIcon(pair.first); mHeaderController.setIconContentDescription(pair.second); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java index 2567f534f66..e3f26b2c78b 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,7 +97,7 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro verify(mHeaderController).setIcon(any(Drawable.class)); verify(mHeaderController).setIconContentDescription(any(String.class)); verify(mHeaderController).setSummary(any(String.class)); - verify(mHeaderController).setSecondSummary(any(String.class)); + verify(mHeaderController, never()).setSecondSummary(any(String.class)); verify(mHeaderController).done(mActivity, true); } @@ -119,4 +120,12 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro inOrder.verify(mHeaderController) .setSummary(mContext.getString(R.string.bluetooth_connecting)); } + + @Test + public void testSecondSummary_isHearingAidDevice_showSecondSummary() { + when(mCachedDevice.isHearingAidDevice()).thenReturn(true); + showScreen(mController); + + verify(mHeaderController).setSecondSummary(any(String.class)); + } } From 3936e9f3d62ab743336ec12ba1341ae1b540597e Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Fri, 28 Sep 2018 17:42:43 +0800 Subject: [PATCH 13/19] Add SettingsContextualCardProvider - provide contextual cards for SettingsIntelligence - add contextualcards libs Fixes: 116837093 Test: make RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.homepage.contextualcards" Change-Id: I873e77fa43e9a8f6056be1f583b1910002efbd2e --- Android.mk | 10 ++- AndroidManifest.xml | 9 +++ libs/contextualcards.aar | Bin 0 -> 5277 bytes .../SettingsContextualCardProvider.java | 56 +++++++++++++++ .../SettingsContextualCardProviderTest.java | 64 ++++++++++++++++++ 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100755 libs/contextualcards.aar create mode 100644 src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java create mode 100644 tests/robotests/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProviderTest.java diff --git a/Android.mk b/Android.mk index 6b0d077a214..f10b3ec2d08 100644 --- a/Android.mk +++ b/Android.mk @@ -41,6 +41,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ androidx.lifecycle_lifecycle-extensions \ guava \ jsr305 \ + contextualcards \ settings-logtags \ LOCAL_PROGUARD_FLAG_FILES := proguard.flags @@ -57,7 +58,14 @@ include frameworks/base/packages/SettingsLib/search/common.mk include $(BUILD_PACKAGE) +# ==== prebuilt library ======================== +include $(CLEAR_VARS) + +LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \ + contextualcards:libs/contextualcards.aar +include $(BUILD_MULTI_PREBUILT) + # Use the following include to make our test apk. ifeq (,$(ONE_SHOT_MAKEFILE)) include $(call all-makefiles-under,$(LOCAL_PATH)) -endif +endif \ No newline at end of file diff --git a/AndroidManifest.xml b/AndroidManifest.xml index aa53bdbdab0..94be61c9312 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3192,6 +3192,15 @@ android:exported="true" android:permission="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA" /> + + + + + diff --git a/libs/contextualcards.aar b/libs/contextualcards.aar new file mode 100755 index 0000000000000000000000000000000000000000..a44a26c38b1f958295cb640c37dfefe21189f583 GIT binary patch literal 5277 zcmaKwRZtwjmWFW&u7ihQg9T^80KwfQxNC5SK@te=9wayfg4+;+LvRT0E`gu}3aer+a_>gDwjFFfqiXTMRRotj5Jd*MFD9TUgNUXlf zn(nltYEtt3C?*lC-*WENGUC2J`V6e5z-cI|O|az3?9cIbciBK%|R_L;+G$uTW#wo9uqV+l_*ihT+ z`&b}?@F7z8bJt-nj)pQOmc;skO~F(9M^97Z-x|bu%33*DczW7+ayeLdbm(IWGvKGp9SNsiz6zL36CMlV01z=lPhiiAO<<4uPavcHok; z0E6i*G`KT63WyD3=Ex5TJeh&KVGOhurXhwJMwykQl}Sk%BsBJ}$H=n&h2c4cVj7O5 z(`s0UwuxSmD@M|nM?g*s@jMPKUyV{N;o-5sHxlF z4O+y>MQQGF)&$N2^Y7c+3u}BdieD@J7=H-cn+19hdk}Z<1D?UH1J8ZGp&z)CclCRH z`*|}H(S;cV`fyN-9CW|Zklg-=;pdP${ssAwHp?vi&WWp~YwWUj{-FmCg^-8{`GdF{ z^`F7jB=PkZKsZfCw@-}*>7fOC&rUmo&4N3AM(x!x2i8JN#&{zmg{;B7f#6txZ*ek< zyS>HTIO~l3?j|tty?D*9@rtqe^X)e0A9Draf%7dJExklX8{IkT)^t-pFL3?8WAth&R0{_-Pc z6TO=lG>hmV&hcon7B3~=>axaVeT}O`Z-hd%pNM_37`{*C$JJk&1(A%)m;dNOL zE9TRiH_}iw(?2j_=>-%xCaJ4)Bw!)`-1dl zUuetC*WW-&m&EZe_5VJb6D>`?k^rF*pQP`NmsiGRx#3#?}bT&);aj zf_bVLVEL-`2TF^$;K#n2H)k`f&@e9HP)yV1W5=<|7Nq|?l{xLrmb*UaetXF=WSN`z zmca`cG}x_vAsx3pwU}*B{q?-$u6Gp2Lc3*?6G|?9!76mHrVu2X3mkW`Mzd2^d1e`@ zjtN({{!KqHF-}Y>(e>#w?G6&CrLs{-FCR`|X;DiLbkg`OO6}UqKcUy#L3GPKX$N@4EphYfGeU)qrsdc0PZVwiT@vU*8_^dWe2 zA>?jh$?0li+?Lt3x$-scjicH^i9xG2M@W(Fo=L%%kW1U&m3Y@=hXR8o+bv~{Rw`Tz z9tg$ug>IH^rgeP>+5k5*>pAHha}I`KSx((?`dQWlNI- zUW~`^EzS>!RG4{{xSA1bIq$O=9??{M`bd6R?m20dX-n7sGbs4%FmB|VX-mF}?h##8 z?=D;F03n`H^OPy!To`Vw2gNZd_|*f9T$l3}eV3B4+m2R5CqvH<;$Qb6?!=`JW9lNe zX-!OMNsD)iA47iO9pP-M=w?_%h&i-wMf*7)Ph^M`*Gzfh6xKZF*5-8!<@OE_ZM z8ME12WDpu^D*zJ4hw?ZYg(Bua)U2Da~t=4#@{g7;= z2%MR+j#Cve0bmd|W9OhAc#}=?jAn*nGI+?9KWo}WWl-O{U$393qAktXY>&?73pvq_ zN*sb=)CKONI=OzHu(I4~PH#N|-;VP8%!x1<+pc^rEu8k2^YV5lA>Uff={qXd^ql{@ zEFCukH8G}#^gZme7sVi;UyTBy_P6n4SI%D7&NUtYRSrApC>^IrE>2l4AK2Ku`9fIm z_d)lVg@X&ih%KBwYWK)+Ekt7MuKU`&Px6msFu4_^exK-c-=9l)0!-Ico%iiM-u6_w z%~8d2&HY&ecbs#GnU+rQ4V+8bw{ah|)a+Aen z3L=m_C+R$}JS`nQWTJR*&B{qvFZn?bP9TdmDONxys4M5ieAmDK<6Fsk`)4Y&*1&Jy zR!IVQB=}j68IwLYKW?%l#)ehGV7;WKV+WojT;LhBosK@{(XfZ#={oZ47xBODdC5Yi zP5Tp4p($3n&gH5*Gx=gi4a=pY@!~wY3w}pjbb+q250dc;F{R{ueg_4IsVb>38ZbjV|8r#>l26aT4R>IA+U9?MwoWaDV>-h!wI`4n-M_ZJYH0wVR};aB+qi)L+Wl!}+Z8#0uxhjB{2fR@7OX`6EoS)l8+jjoTB4zOK&r8reLl8r!!?_& z=T-1;Bjfyc6P^#N>7gV{3JGu{3PHD|J@3a8g***tPo%P?RF6ok$VVy@X8Y;o2wNNXjoh0RT+XH@4^XN*1Keqm*)o37S#kw_JCcN_1>OS2#pxxv$T z6X>Syc~4Gb-0+6sEn}#( zxkG$CY5XfZ9mcrBal?W{WZ(?Qq%@rPV&d0(xM)tU&rgz2!$d*LTkh1Xwu?0``i)9F z?Myw;1w+!p+e43JIbJ56?-< z_P9r`7J^_lgF1|RkElKS_kybL3Ff}SLaRacZ|aGXFBR6cl5sWFg7^nIBWXruT94U| z3l!J;?dk2AR6WDVbZ$`Jo0fFMR@VAK**DR@A|MeER)8>?N^awiGs1_WqY)G@5~>!8 zYSI&Tko!5D#G_es&(b1?IlOR-cbFTxuY1{L}rT+8GwVY_(!64 zs(SLtvc*NxYUPhzV&dItiHj*!CX#XO1V)ptpX-G%0#}CY!H)K_o5RjHdqFM9{tA%i zi2V2|JkO1Dxq@0@235;`=3oA2a_yp@>5n?3m^}kQmSOtJT4yP0U&zsC3I>_&9&NmQ26z(lTk@ z7pP|KvjKyuMhlgES?BvWDqtq;_G~4toPLEd%qLlL4_YV!rwHv}QeJ?K<)x_Su|-?$ z{Uz%_yO!Sw_T!W=VWOe#ET(o5^JOPzd|{vxV-xQA ze4d&MZV{94iS692*cQXJ>I+|+2}WI?Or)P)UepYRF<8O6W8^aVO&t!oK4G9G9lj2} zap;QM2K#4p8$OpvqXjUHLaFkK+T~Qb(oGtbqNSkEPiQi{(He6C=jLh>Y-wN|H2+Zz zwd1N?V#pmf!lINID9SSB^hJ2eH=ascS$kpmf|~N4D>s*lfYaEX@{JFFSl^n+SJoY5 z*)W^%!<*q7*!QY4e{nt)dCg^x@>MDS`Ux8-*27^i*gR#v=*UTPpK*3?O`%NH_OvMD zV;vi|6*oM%{-at{%btjOjx2STOzy}Phgs)tAu)#d2v>eDsBhhpZBgi8_Gg`8jTS6h zK0{?kY3;H+l3XanAq49yxx{r?Dmnlkx9NAD2+Fm}7=_Yhl*G-Rgh}}gsoKlA3NErl zyCXg`B_*QkvQB=LPx}i;Zbqhrt=G)@g==NFhCDLcGB?(3$==cesnsLo2ifml)x(c} z3x+~q#o;;GInTYovr)pVPG=P^A&kuYpm-+?cNcHA!m@H6a>~q=uvgfaWns}R1>CRt z^NG4pfa*7ET|F8a6M{9;JcTiGZU;)tOGI+`gSM%htf%&RuD+n&nl2O^5% zdL6$xImfOsH$oH|7IO=uo*f{H{jxosC@dN#|ASb_YC93!{Mo>}WFTv02# z2Q$d8rwVA~Gxpf@?)vpJ0lN`km_5b|+s~yT21Hx9aI getContextualCards() { + final List cards = new ArrayList<>(); + final ContextualCard wifiCard = + new ContextualCard.Builder() + .setSliceUri(WifiSlice.WIFI_URI.toString()) + .setName(KEY_WIFI) + .build(); + final ContextualCard dataUsageCard = + new ContextualCard.Builder() + .setSliceUri(DataUsageSlice.DATA_USAGE_CARD_URI.toString()) + .setName(DataUsageSlice.PATH_DATA_USAGE_CARD) + .build(); + + cards.add(wifiCard); + cards.add(dataUsageCard); + return cards; + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProviderTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProviderTest.java new file mode 100644 index 00000000000..80d94977607 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProviderTest.java @@ -0,0 +1,64 @@ +/* + * 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.homepage.contextualcards; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import com.google.android.settings.intelligence.libs.contextualcards.ContextualCard; +import com.google.android.settings.intelligence.libs.contextualcards.ContextualCardProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; + +@RunWith(SettingsRobolectricTestRunner.class) +public class SettingsContextualCardProviderTest { + + private Context mContext; + private ContentResolver mResolver; + private Uri mUri; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mResolver = mContext.getContentResolver(); + mUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsContextualCardProvider.CARD_AUTHORITY) + .build(); + } + + @Test + public void contentProviderCall_returnCorrectSize() { + final Bundle returnValue = + mResolver.call(mUri, ContextualCardProvider.METHOD_GET_CARD_LIST, "", null); + final ArrayList cards = + returnValue.getParcelableArrayList(ContextualCardProvider.BUNDLE_CARD_LIST); + assertThat(cards.size()).isEqualTo(2); + } +} \ No newline at end of file From 11be3acfe08288fe15fb509ab35cbea177d270fd Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Tue, 2 Oct 2018 10:15:36 +0100 Subject: [PATCH 14/19] Use ICU APIs not impl types It's possible to use BasicTimeZone in place of OlsonTimeZone. Although neither are currently public SDK APIs the BasicTimeZone is preferable. Bug: 113148576 Test: build Change-Id: I745b17feee678ecd053fadc6c447107fa80918e8 --- .../timezone/TimeZoneInfoPreferenceController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java b/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java index 233111aa2f1..a819f101a87 100644 --- a/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java +++ b/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java @@ -17,10 +17,10 @@ package com.android.settings.datetime.timezone; import android.content.Context; -import android.icu.impl.OlsonTimeZone; import android.icu.text.DateFormat; import android.icu.text.DisplayContext; import android.icu.text.SimpleDateFormat; +import android.icu.util.BasicTimeZone; import android.icu.util.Calendar; import android.icu.util.TimeZone; import android.icu.util.TimeZoneTransition; @@ -115,17 +115,17 @@ public class TimeZoneInfoPreferenceController extends BaseTimeZonePreferenceCont } private TimeZoneTransition findNextDstTransition(TimeZone timeZone) { - if (!(timeZone instanceof OlsonTimeZone)) { + if (!(timeZone instanceof BasicTimeZone)) { return null; } - final OlsonTimeZone olsonTimeZone = (OlsonTimeZone) timeZone; - TimeZoneTransition transition = olsonTimeZone.getNextTransition( + final BasicTimeZone basicTimeZone = (BasicTimeZone) timeZone; + TimeZoneTransition transition = basicTimeZone.getNextTransition( mDate.getTime(), /* inclusive */ false); do { if (transition.getTo().getDSTSavings() != transition.getFrom().getDSTSavings()) { break; } - transition = olsonTimeZone.getNextTransition( + transition = basicTimeZone.getNextTransition( transition.getTime(), /*inclusive */ false); } while (transition != null); return transition; From b81181b9f333bd1df87bbf40c614f705ff75f2e3 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Tue, 2 Oct 2018 13:27:44 +0100 Subject: [PATCH 15/19] Avoid deprecated PrivateKeyInfo.getAlgorithmId() Switch from using the PrivateKeyInfo.getAlgorithmId() method to using the functionally identical PrivateKeyInfo.getPrivateKeyAlgorithm() method instead. Bug: 113148576 Test: build only / inspection Change-Id: Iab2d3b23ad969f683d716cb981e32fd554c67a81 --- src/com/android/settings/CredentialStorage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java index 319d599018d..7b0be943bc5 100644 --- a/src/com/android/settings/CredentialStorage.java +++ b/src/com/android/settings/CredentialStorage.java @@ -205,7 +205,7 @@ public final class CredentialStorage extends FragmentActivity { try { final ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData)); final PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject()); - final String algOid = pki.getAlgorithmId().getAlgorithm().getId(); + final String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId(); final String algName = new AlgorithmId(new ObjectIdentifier(algOid)).getName(); return KeyChain.isBoundKeyAlgorithm(algName); From 78ab907aa6f3107ec4b745037561bae2f45ec2d3 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Tue, 2 Oct 2018 09:28:11 -0700 Subject: [PATCH 16/19] Fix build Test: compile Change-Id: I3d236ea1b1f5ee5b4b5b2aa118416383b5b4bb6d --- .../development/featureflags/FeatureFlagPersistent.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/development/featureflags/FeatureFlagPersistent.java b/src/com/android/settings/development/featureflags/FeatureFlagPersistent.java index 457b36219f5..30d5cd76f32 100644 --- a/src/com/android/settings/development/featureflags/FeatureFlagPersistent.java +++ b/src/com/android/settings/development/featureflags/FeatureFlagPersistent.java @@ -18,11 +18,12 @@ package com.android.settings.development.featureflags; import android.content.Context; import android.os.SystemProperties; -import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.FeatureFlagUtils; import android.util.Log; +import androidx.annotation.VisibleForTesting; + import com.android.settings.core.FeatureFlags; import java.util.HashSet; From 7b91f0dd3e92c8e484bb5a2d07f3a94b7e98ad6e Mon Sep 17 00:00:00 2001 From: Stanley Tng Date: Sun, 30 Sep 2018 22:47:22 -0700 Subject: [PATCH 17/19] Fix Connect State message in Device details for Hearing Aids In the Device details of Settings App and when using two Hearing Aids devices (left and right sides), this will fix the connect state messages for these two devices. Also added Robo tests for the changes. Bug: 116725094 Bug: 117074814 Test: Manual tests and also ran RunSettingsLibRoboTests and RunSettingsRoboTests. Change-Id: I5c9af9ec6a2a17668597a537fd1f7b70368b7ed8 Merged-In: I0b1a170967ddcce7f388603fd521f6ed1eeba30b Merged-In: I169cda4a1658b0a67cc7c7367b38d57a021e6953 --- .../BluetoothDetailsHeaderController.java | 17 ++++++++++++----- .../BluetoothDetailsHeaderControllerTest.java | 11 ++++++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java index dca7e201bd4..0bfc494418b 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java @@ -18,9 +18,11 @@ package com.android.settings.bluetooth; import android.content.Context; import android.graphics.drawable.Drawable; +import android.util.Log; +import android.util.Pair; + import androidx.preference.PreferenceFragment; import androidx.preference.PreferenceScreen; -import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; @@ -36,6 +38,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; */ public class BluetoothDetailsHeaderController extends BluetoothDetailsController { private static final String KEY_DEVICE_HEADER = "bluetooth_device_header"; + private static final String TAG = "BluetoothDetailsHeaderController"; private EntityHeaderController mHeaderController; private LocalBluetoothManager mLocalManager; @@ -63,12 +66,16 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController .getBtClassDrawableWithDescription(mContext, mCachedDevice, mContext.getResources().getFraction(R.fraction.bt_battery_scale_fraction, 1, 1)); String summaryText = mCachedDevice.getConnectionSummary(); - // If both the hearing aids are connected, two battery status should be shown. - final String pairDeviceSummary = mDeviceManager - .getHearingAidPairDeviceSummary(mCachedDevice); - if (pairDeviceSummary != null) { + + if (mCachedDevice.isHearingAidDevice()) { + // For Hearing Aid device, display the other battery status. + final String pairDeviceSummary = mDeviceManager + .getHearingAidPairDeviceSummary(mCachedDevice); + Log.d(TAG, "setHeaderProperties: HearingAid: summaryText=" + summaryText + + ", pairDeviceSummary=" + pairDeviceSummary); mHeaderController.setSecondSummary(pairDeviceSummary); } + mHeaderController.setLabel(mCachedDevice.getName()); mHeaderController.setIcon(pair.first); mHeaderController.setIconContentDescription(pair.second); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java index 2567f534f66..e3f26b2c78b 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,7 +97,7 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro verify(mHeaderController).setIcon(any(Drawable.class)); verify(mHeaderController).setIconContentDescription(any(String.class)); verify(mHeaderController).setSummary(any(String.class)); - verify(mHeaderController).setSecondSummary(any(String.class)); + verify(mHeaderController, never()).setSecondSummary(any(String.class)); verify(mHeaderController).done(mActivity, true); } @@ -119,4 +120,12 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro inOrder.verify(mHeaderController) .setSummary(mContext.getString(R.string.bluetooth_connecting)); } + + @Test + public void testSecondSummary_isHearingAidDevice_showSecondSummary() { + when(mCachedDevice.isHearingAidDevice()).thenReturn(true); + showScreen(mController); + + verify(mHeaderController).setSecondSummary(any(String.class)); + } } From 53bdd9961cd6629cfc8bbc659396a59a5c2f646a Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 2 Oct 2018 13:36:33 -0700 Subject: [PATCH 18/19] Check instance before casting Change-Id: I1917b2d8e045904145ea833abf35fa1d008fa224 Fixes: 116855783 Test: robotests --- .../SpecialAppAccessPreferenceController.java | 5 +++-- ...ecialAppAccessPreferenceControllerTest.java | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java index a395f9833f0..1763d84491a 100644 --- a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java +++ b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java @@ -23,6 +23,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.datausage.AppStateDataUsageBridge; +import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; import com.android.settings.datausage.DataSaverBackend; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -108,8 +109,8 @@ public class SpecialAppAccessPreferenceController extends BasePreferenceControll if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry)) { continue; } - if (entry.extraInfo != null && ((AppStateDataUsageBridge.DataUsageState) - entry.extraInfo).isDataSaverWhitelisted) { + if (entry.extraInfo instanceof DataUsageState + && ((DataUsageState) entry.extraInfo).isDataSaverWhitelisted) { count++; } } diff --git a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java index b6429151b67..694625ef082 100644 --- a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java @@ -94,4 +94,22 @@ public class SpecialAppAccessPreferenceControllerTest { .isEqualTo(mContext.getResources().getQuantityString( R.plurals.special_access_summary, 1, 1)); } + + @Test + public void updateState_wrongExtraInfo_shouldNotIncludeInSummary() { + final ArrayList apps = new ArrayList<>(); + final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class); + entry.hasLauncherEntry = true; + entry.info = new ApplicationInfo(); + entry.extraInfo = new AppStateNotificationBridge.NotificationsSentState(); + apps.add(entry); + when(mSession.getAllApps()).thenReturn(apps); + + mController.displayPreference(mScreen); + mController.onExtraInfoUpdated(); + + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getResources().getQuantityString( + R.plurals.special_access_summary, 0, 0)); + } } From b758e2b4734c55f5e49c2f43e4e049b1a89057e3 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Mon, 1 Oct 2018 17:23:22 -0700 Subject: [PATCH 19/19] Use the new loader to show app data usage summary. - this is for getting the summary text for Apps & notifications -> (select an app) -> Mobile data & Wi-Fi Bug: 111751694 Test: make RunSettingsRoboTests Change-Id: Ib4d8e7308ea8a782b9af3bbb99db2b1235e0aca2 --- res/xml/app_info_settings.xml | 6 ++ .../AppDataUsagePreferenceController.java | 9 ++- .../AppDataUsagePreferenceControllerV2.java | 79 +++++++++++-------- .../appinfo/AppInfoDashboardFragment.java | 8 +- .../settings/datausage/DataUsageListV2.java | 8 +- .../AppDataUsagePreferenceControllerTest.java | 1 + ...ppDataUsagePreferenceControllerV2Test.java | 24 +++--- 7 files changed, 84 insertions(+), 51 deletions(-) diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml index 684d0325417..81afc1a11e3 100644 --- a/res/xml/app_info_settings.xml +++ b/res/xml/app_info_settings.xml @@ -67,6 +67,12 @@ android:summary="@string/summary_placeholder" settings:controller="com.android.settings.applications.appinfo.AppDataUsagePreferenceController" /> + + , LifecycleObserver, OnResume, OnPause { + implements LoaderManager.LoaderCallbacks>, LifecycleObserver, + OnResume, OnPause { - private ChartData mChartData; - private INetworkStatsSession mStatsSession; + private List mAppUsageData; - public AppDataUsagePreferenceControllerV2(Context context,String key) { + public AppDataUsagePreferenceControllerV2(Context context, String key) { super(context, key); } @Override public int getAvailabilityStatus() { + if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlags.DATA_USAGE_V2)) { + return UNSUPPORTED_ON_DEVICE; + } return isBandwidthControlEnabled() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - if (isAvailable()) { - final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - try { - mStatsSession = statsService.openSession(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } } @Override @@ -81,51 +74,67 @@ public class AppDataUsagePreferenceControllerV2 extends AppInfoPreferenceControl @Override public void onResume() { - if (mStatsSession != null) { + if (isAvailable()) { final int uid = mParent.getAppEntry().info.uid; final AppItem app = new AppItem(uid); app.addUid(uid); - mParent.getLoaderManager().restartLoader(mParent.LOADER_CHART_DATA, - ChartDataLoaderCompat.buildArgs(getTemplate(mContext), app), + mParent.getLoaderManager().restartLoader(mParent.LOADER_CHART_DATA, null /* args */, this); } } @Override public void onPause() { - mParent.getLoaderManager().destroyLoader(mParent.LOADER_CHART_DATA); + if (isAvailable()) { + mParent.getLoaderManager().destroyLoader(mParent.LOADER_CHART_DATA); + } } @Override - public Loader onCreateLoader(int id, Bundle args) { - return new ChartDataLoaderCompat(mContext, mStatsSession, args); + public Loader> onCreateLoader(int id, Bundle args) { + final NetworkTemplate template = getTemplate(mContext); + return NetworkCycleDataForUidLoader.builder(mContext) + .setUid(mParent.getAppEntry().info.uid) + .setRetrieveDetail(false) + .setNetworkTemplate(template) + .setSubscriberId(template.getSubscriberId()) + .build(); } @Override - public void onLoadFinished(Loader loader, ChartData data) { - mChartData = data; + public void onLoadFinished(Loader> loader, + List data) { + mAppUsageData = data; updateState(mPreference); } @Override - public void onLoaderReset(Loader loader) { + public void onLoaderReset(Loader> loader) { // Leave last result. } @Override protected Class getDetailFragmentClass() { - return AppDataUsage.class; + return AppDataUsageV2.class; } private CharSequence getDataSummary() { - if (mChartData != null) { - final long totalBytes = mChartData.detail.getTotalBytes(); + if (mAppUsageData != null) { + long totalBytes = 0; + long startTime = System.currentTimeMillis(); + for (NetworkCycleDataForUid data : mAppUsageData) { + totalBytes += data.getTotalUsage(); + final long cycleStart = data.getStartTime(); + if (cycleStart < startTime) { + startTime = cycleStart; + } + } if (totalBytes == 0) { return mContext.getString(R.string.no_data_usage); } return mContext.getString(R.string.data_summary_format, Formatter.formatFileSize(mContext, totalBytes), - DateUtils.formatDateTime(mContext, mChartData.detail.getStart(), + DateUtils.formatDateTime(mContext, startTime, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH)); } return mContext.getString(R.string.computing_size); diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index 7b9cc796ab0..64fd36d2450 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -48,6 +49,7 @@ import com.android.settings.SettingsPreferenceFragment; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.applications.specialaccess.pictureinpicture .PictureInPictureDetailPreferenceController; +import com.android.settings.core.FeatureFlags; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -139,7 +141,11 @@ public class AppInfoDashboardFragment extends DashboardFragment final String packageName = getPackageName(); use(TimeSpentInAppPreferenceController.class).setPackageName(packageName); - use(AppDataUsagePreferenceController.class).setParentFragment(this); + if (FeatureFlagUtils.isEnabled(context, FeatureFlags.DATA_USAGE_V2)) { + use(AppDataUsagePreferenceControllerV2.class).setParentFragment(this); + } else { + use(AppDataUsagePreferenceController.class).setParentFragment(this); + } final AppInstallerInfoPreferenceController installer = use(AppInstallerInfoPreferenceController.class); installer.setPackageName(packageName); diff --git a/src/com/android/settings/datausage/DataUsageListV2.java b/src/com/android/settings/datausage/DataUsageListV2.java index ea652b3138a..c6d3b4d23c3 100644 --- a/src/com/android/settings/datausage/DataUsageListV2.java +++ b/src/com/android/settings/datausage/DataUsageListV2.java @@ -426,11 +426,11 @@ public class DataUsageListV2 extends DataUsageBaseFragment { private void startAppDataUsage(AppItem item) { final Bundle args = new Bundle(); - args.putParcelable(AppDataUsage.ARG_APP_ITEM, item); - args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate); + args.putParcelable(AppDataUsageV2.ARG_APP_ITEM, item); + args.putParcelable(AppDataUsageV2.ARG_NETWORK_TEMPLATE, mTemplate); new SubSettingLauncher(getContext()) - .setDestination(AppDataUsage.class.getName()) + .setDestination(AppDataUsageV2.class.getName()) .setTitleRes(R.string.app_data_usage) .setArguments(args) .setSourceMetricsCategory(getMetricsCategory()) @@ -490,8 +490,6 @@ public class DataUsageListV2 extends DataUsageBaseFragment { @Override public Loader> onCreateLoader(int id, Bundle args) { return NetworkCycleChartDataLoader.builder(getContext()) - .setNetworkPolicy(services.mPolicyEditor.getPolicy(mTemplate)) - .setNetworkType(mNetworkType) .setNetworkTemplate(mTemplate) .setSubscriberId(mTelephonyManager.getSubscriberId(mSubId)) .build(); diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java index 74966a2a958..e77106d2a01 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java @@ -115,6 +115,7 @@ public class AppDataUsagePreferenceControllerTest { @Test public void onPause_shouldDestroyDataLoader() { + ReflectionHelpers.setField(mController, "mStatsSession", mock(INetworkStatsSession.class)); doReturn(mLoaderManager).when(mFragment).getLoaderManager(); mController.onPause(); diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java index 0023250849b..5e9179f0fb6 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerV2Test.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -31,18 +32,20 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; import android.net.ConnectivityManager; -import android.net.INetworkStatsSession; import android.os.Bundle; import com.android.settings.core.BasePreferenceController; -import com.android.settings.datausage.AppDataUsage; +import com.android.settings.core.FeatureFlags; +import com.android.settings.datausage.AppDataUsageV2; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settingslib.applications.ApplicationsState.AppEntry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; +import org.mockito.Mock;import android.net.INetworkStatsSession; +import android.util.FeatureFlagUtils; + import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.ReflectionHelpers; @@ -67,6 +70,7 @@ public class AppDataUsagePreferenceControllerV2Test { mContext = spy(RuntimeEnvironment.application.getApplicationContext()); mController = spy(new AppDataUsagePreferenceControllerV2(mContext, "test_key")); mController.setParentFragment(mFragment); + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.DATA_USAGE_V2, true); } @Test @@ -86,7 +90,8 @@ public class AppDataUsagePreferenceControllerV2Test { } @Test - public void onResume_noSession_shouldNotRestartDataLoader() { + public void onResume_notAvailable_shouldNotRestartDataLoader() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.DATA_USAGE_V2, false); doReturn(mLoaderManager).when(mFragment).getLoaderManager(); mController.onResume(); @@ -96,25 +101,26 @@ public class AppDataUsagePreferenceControllerV2Test { } @Test - public void onResume_hasSession_shouldRestartDataLoader() { + public void onResume_isAvailable_shouldRestartDataLoader() { final ConnectivityManager connectivityManager = mock(ConnectivityManager.class); when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) .thenReturn(connectivityManager); when(connectivityManager.isNetworkSupported(anyInt())).thenReturn(true); doReturn(mLoaderManager).when(mFragment).getLoaderManager(); - ReflectionHelpers.setField(mController, "mStatsSession", mock(INetworkStatsSession.class)); + doReturn(BasePreferenceController.AVAILABLE).when(mController).getAvailabilityStatus(); final AppEntry appEntry = mock(AppEntry.class); appEntry.info = new ApplicationInfo(); when(mFragment.getAppEntry()).thenReturn(appEntry); mController.onResume(); - verify(mLoaderManager).restartLoader( - eq(AppInfoDashboardFragment.LOADER_CHART_DATA), any(Bundle.class), eq(mController)); + verify(mLoaderManager).restartLoader(eq(AppInfoDashboardFragment.LOADER_CHART_DATA), + nullable(Bundle.class), eq(mController)); } @Test public void onPause_shouldDestroyDataLoader() { + doReturn(BasePreferenceController.AVAILABLE).when(mController).getAvailabilityStatus(); doReturn(mLoaderManager).when(mFragment).getLoaderManager(); mController.onPause(); @@ -124,7 +130,7 @@ public class AppDataUsagePreferenceControllerV2Test { @Test public void getDetailFragmentClass_shouldReturnAppDataUsage() { - assertThat(mController.getDetailFragmentClass()).isEqualTo(AppDataUsage.class); + assertThat(mController.getDetailFragmentClass()).isEqualTo(AppDataUsageV2.class); } @Test