From 820e22d3240b9d2c881a08ec78b9502e7a66ad00 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 2 Oct 2018 14:19:27 -0700 Subject: [PATCH 1/8] Add entitlement check for usb tethering Before this CL, usb tethering in detail page doesn't have entitlement check. This CL fix it by using the correct API Also polish it by updating the UI before entitlement check to align hotspot page. So in UI it will first check "usb tethering". If entitlement check fail, it will revoke and go back to previous selection. Bug: 115707279 Test: RunSettingsRoboTests Merged-In: I3d2ebad2879479a870bcdfe596bb88b83c424389 Change-Id: I3d2ebad2879479a870bcdfe596bb88b83c424389 --- .../usb/UsbDefaultFragment.java | 42 +++++++++- .../usb/UsbDetailsFunctionsController.java | 48 ++++++++++- .../usb/UsbDefaultFragmentTest.java | 55 +++++++++++-- .../UsbDetailsFunctionsControllerTest.java | 81 +++++++++++++++---- 4 files changed, 201 insertions(+), 25 deletions(-) diff --git a/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java b/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java index e1b3d70049d..6e7c6a77496 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java +++ b/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java @@ -16,9 +16,14 @@ package com.android.settings.connecteddevice.usb; +import static android.net.ConnectivityManager.TETHERING_USB; + import android.content.Context; import android.graphics.drawable.Drawable; +import android.hardware.usb.UsbManager; +import android.net.ConnectivityManager; import android.os.Bundle; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; @@ -39,11 +44,18 @@ import java.util.List; public class UsbDefaultFragment extends RadioButtonPickerFragment { @VisibleForTesting UsbBackend mUsbBackend; + @VisibleForTesting + ConnectivityManager mConnectivityManager; + @VisibleForTesting + OnStartTetheringCallback mOnStartTetheringCallback = new OnStartTetheringCallback(); + @VisibleForTesting + long mPreviousFunctions; @Override public void onAttach(Context context) { super.onAttach(context); mUsbBackend = new UsbBackend(context); + mConnectivityManager = context.getSystemService(ConnectivityManager.class); } @Override @@ -103,9 +115,37 @@ public class UsbDefaultFragment extends RadioButtonPickerFragment { @Override protected boolean setDefaultKey(String key) { long functions = UsbBackend.usbFunctionsFromString(key); + mPreviousFunctions = mUsbBackend.getCurrentFunctions(); if (!Utils.isMonkeyRunning()) { - mUsbBackend.setDefaultUsbFunctions(functions); + if (functions == UsbManager.FUNCTION_RNDIS) { + // We need to have entitlement check for usb tethering, so use API in + // ConnectivityManager. + mConnectivityManager.startTethering(TETHERING_USB, true /* showProvisioningUi */, + mOnStartTetheringCallback); + } else { + mUsbBackend.setDefaultUsbFunctions(functions); + } + } return true; } + + @VisibleForTesting + final class OnStartTetheringCallback extends + ConnectivityManager.OnStartTetheringCallback { + + @Override + public void onTetheringStarted() { + super.onTetheringStarted(); + // Set default usb functions again to make internal data persistent + mUsbBackend.setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS); + } + + @Override + public void onTetheringFailed() { + super.onTetheringFailed(); + mUsbBackend.setDefaultUsbFunctions(mPreviousFunctions); + updateCandidates(); + } + } } \ No newline at end of file diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java index 47545ca17fd..c5fec421af3 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java +++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java @@ -16,11 +16,15 @@ package com.android.settings.connecteddevice.usb; +import static android.net.ConnectivityManager.TETHERING_USB; + import android.content.Context; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; +import android.support.annotation.VisibleForTesting; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; +import android.net.ConnectivityManager; import com.android.settings.R; import com.android.settings.Utils; @@ -46,10 +50,18 @@ public class UsbDetailsFunctionsController extends UsbDetailsController } private PreferenceCategory mProfilesContainer; + private ConnectivityManager mConnectivityManager; + @VisibleForTesting + OnStartTetheringCallback mOnStartTetheringCallback; + @VisibleForTesting + long mPreviousFunction; public UsbDetailsFunctionsController(Context context, UsbDetailsFragment fragment, UsbBackend backend) { super(context, fragment, backend); + mConnectivityManager = context.getSystemService(ConnectivityManager.class); + mOnStartTetheringCallback = new OnStartTetheringCallback(); + mPreviousFunction = mUsbBackend.getCurrentFunctions(); } @Override @@ -96,9 +108,28 @@ public class UsbDetailsFunctionsController extends UsbDetailsController @Override public void onRadioButtonClicked(RadioButtonPreference preference) { - long function = UsbBackend.usbFunctionsFromString(preference.getKey()); - if (function != mUsbBackend.getCurrentFunctions() && !Utils.isMonkeyRunning()) { - mUsbBackend.setCurrentFunctions(function); + final long function = UsbBackend.usbFunctionsFromString(preference.getKey()); + final long previousFunction = mUsbBackend.getCurrentFunctions(); + if (function != previousFunction && !Utils.isMonkeyRunning()) { + mPreviousFunction = previousFunction; + + if (function == UsbManager.FUNCTION_RNDIS) { + //Update the UI in advance to make it looks smooth + final RadioButtonPreference prevPref = + (RadioButtonPreference) mProfilesContainer.findPreference( + UsbBackend.usbFunctionsToString(mPreviousFunction)); + if (prevPref != null) { + prevPref.setChecked(false); + preference.setChecked(true); + } + + // We need to have entitlement check for usb tethering, so use API in + // ConnectivityManager. + mConnectivityManager.startTethering(TETHERING_USB, true /* showProvisioningUi */, + mOnStartTetheringCallback); + } else { + mUsbBackend.setCurrentFunctions(function); + } } } @@ -111,4 +142,15 @@ public class UsbDetailsFunctionsController extends UsbDetailsController public String getPreferenceKey() { return "usb_details_functions"; } + + @VisibleForTesting + final class OnStartTetheringCallback extends + ConnectivityManager.OnStartTetheringCallback { + + @Override + public void onTetheringFailed() { + super.onTetheringFailed(); + mUsbBackend.setCurrentFunctions(mPreviousFunction); + } + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java index dca10d73390..2c619dc4056 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java @@ -16,12 +16,21 @@ package com.android.settings.connecteddevice.usb; +import static android.net.ConnectivityManager.TETHERING_USB; + import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.anyLong; +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.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.hardware.usb.UsbManager; +import android.net.ConnectivityManager; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowUtils; @@ -31,13 +40,17 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.annotation.Config; +import org.robolectric.util.FragmentTestUtil; @RunWith(SettingsRobolectricTestRunner.class) public class UsbDefaultFragmentTest { @Mock private UsbBackend mUsbBackend; + @Mock + private ConnectivityManager mConnectivityManager; private UsbDefaultFragment mFragment; @@ -46,6 +59,7 @@ public class UsbDefaultFragmentTest { MockitoAnnotations.initMocks(this); mFragment = new UsbDefaultFragment(); mFragment.mUsbBackend = mUsbBackend; + mFragment.mConnectivityManager = mConnectivityManager; } @Test @@ -101,12 +115,6 @@ public class UsbDefaultFragmentTest { verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_PTP); } - @Test - public void setDefaultKey_isRndis_shouldSetRndis() { - mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS)); - verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS); - } - @Test public void setDefaultKey_isMidi_shouldSetMidi() { mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_MIDI)); @@ -118,6 +126,39 @@ public class UsbDefaultFragmentTest { public void setDefaultKey_isMonkey_shouldDoNothing() { ShadowUtils.setIsUserAMonkey(true); mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_MTP)); - verifyZeroInteractions(mUsbBackend); + + verify(mUsbBackend, never()).setDefaultUsbFunctions(anyLong()); + } + + @Test + public void setDefaultKey_functionRndis_startTetheringInvoked() { + doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); + + mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS)); + + verify(mConnectivityManager).startTethering(TETHERING_USB, true, + mFragment.mOnStartTetheringCallback); + assertThat(mFragment.mPreviousFunctions).isEqualTo( + UsbManager.FUNCTION_MTP); + } + + @Test + public void setDefaultKey_functionOther_setCurrentFunctionInvoked() { + doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); + + mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP)); + + verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_PTP); + assertThat(mFragment.mPreviousFunctions).isEqualTo( + UsbManager.FUNCTION_MTP); + } + + @Test + public void onTetheringStarted_setDefaultUsbFunctions() { + mFragment.mPreviousFunctions = UsbManager.FUNCTION_PTP; + + mFragment.mOnStartTetheringCallback.onTetheringStarted(); + + verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java index 713fd2471d5..2da89447ef6 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java @@ -16,9 +16,14 @@ package com.android.settings.connecteddevice.usb; +import static android.net.ConnectivityManager.TETHERING_USB; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyLong; +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; @@ -26,6 +31,7 @@ import android.app.Activity; import android.content.Context; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; +import android.net.ConnectivityManager; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceScreen; @@ -53,9 +59,10 @@ public class UsbDetailsFunctionsControllerTest { private UsbDetailsFunctionsController mDetailsFunctionsController; private Context mContext; private Lifecycle mLifecycle; - private PreferenceCategory mPreference; + private PreferenceCategory mPreferenceCategory; private PreferenceManager mPreferenceManager; private PreferenceScreen mScreen; + private RadioButtonPreference mRadioButtonPreference; @Mock private UsbBackend mUsbBackend; @@ -63,12 +70,14 @@ public class UsbDetailsFunctionsControllerTest { private UsbDetailsFragment mFragment; @Mock private Activity mActivity; + @Mock + private ConnectivityManager mConnectivityManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; + mContext = spy(RuntimeEnvironment.application); mLifecycle = new Lifecycle(() -> mLifecycle); mPreferenceManager = new PreferenceManager(mContext); mScreen = mPreferenceManager.createPreferenceScreen(mContext); @@ -78,12 +87,16 @@ public class UsbDetailsFunctionsControllerTest { when(mFragment.getContext()).thenReturn(mContext); when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager); when(mFragment.getPreferenceScreen()).thenReturn(mScreen); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); mDetailsFunctionsController = new UsbDetailsFunctionsController(mContext, mFragment, mUsbBackend); - mPreference = new PreferenceCategory(mContext); - mPreference.setKey(mDetailsFunctionsController.getPreferenceKey()); - mScreen.addPreference(mPreference); + mPreferenceCategory = new PreferenceCategory(mContext); + mPreferenceCategory.setKey(mDetailsFunctionsController.getPreferenceKey()); + mScreen.addPreference(mPreferenceCategory); + mDetailsFunctionsController.displayPreference(mScreen); + + mRadioButtonPreference = new RadioButtonPreference(mContext); } @Test @@ -105,10 +118,9 @@ public class UsbDetailsFunctionsControllerTest { public void displayRefresh_disconnected_shouldDisable() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); - mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(false, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); - assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreferenceCategory.isEnabled()).isFalse(); } @Test @@ -118,7 +130,6 @@ public class UsbDetailsFunctionsControllerTest { when(mUsbBackend.areFunctionsSupported(UsbManager.FUNCTION_PTP)).thenReturn(false); when(mUsbBackend.areFunctionsSupported(UsbManager.FUNCTION_RNDIS)).thenReturn(false); - mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); List prefs = getRadioPreferences(); @@ -131,7 +142,6 @@ public class UsbDetailsFunctionsControllerTest { public void displayRefresh_mtpEnabled_shouldCheckSwitches() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); - mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_MTP, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); List prefs = getRadioPreferences(); @@ -145,7 +155,6 @@ public class UsbDetailsFunctionsControllerTest { public void onClickMtp_noneEnabled_shouldEnableMtp() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); - mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_NONE); @@ -164,7 +173,6 @@ public class UsbDetailsFunctionsControllerTest { public void onClickMtp_ptpEnabled_shouldEnableMtp() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); - mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_PTP, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_PTP); @@ -186,7 +194,6 @@ public class UsbDetailsFunctionsControllerTest { public void onClickNone_mtpEnabled_shouldDisableMtp() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); - mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_MTP, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_MTP); @@ -210,9 +217,55 @@ public class UsbDetailsFunctionsControllerTest { private List getRadioPreferences() { ArrayList result = new ArrayList<>(); - for (int i = 0; i < mPreference.getPreferenceCount(); i++) { - result.add((RadioButtonPreference) mPreference.getPreference(i)); + for (int i = 0; i < mPreferenceCategory.getPreferenceCount(); i++) { + result.add((RadioButtonPreference) mPreferenceCategory.getPreference(i)); } return result; } + + @Test + public void onRadioButtonClicked_functionRndis_startTetheringInvoked() { + mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS)); + doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); + + mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference); + + verify(mConnectivityManager).startTethering(TETHERING_USB, true, + mDetailsFunctionsController.mOnStartTetheringCallback); + assertThat(mDetailsFunctionsController.mPreviousFunction).isEqualTo( + UsbManager.FUNCTION_MTP); + } + + @Test + public void onRadioButtonClicked_functionOther_setCurrentFunctionInvoked() { + mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP)); + doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); + + mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference); + + verify(mUsbBackend).setCurrentFunctions(UsbManager.FUNCTION_PTP); + assertThat(mDetailsFunctionsController.mPreviousFunction).isEqualTo( + UsbManager.FUNCTION_MTP); + } + + @Test + public void onRadioButtonClicked_clickSameButton_doNothing() { + mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP)); + doReturn(UsbManager.FUNCTION_PTP).when(mUsbBackend).getCurrentFunctions(); + + mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference); + + verify(mUsbBackend, never()).setCurrentFunctions(UsbManager.FUNCTION_PTP); + verify(mConnectivityManager, never()).startTethering(TETHERING_USB, true, + mDetailsFunctionsController.mOnStartTetheringCallback); + } + + @Test + public void onTetheringFailed_resetPreviousFunctions() { + mDetailsFunctionsController.mPreviousFunction = UsbManager.FUNCTION_PTP; + + mDetailsFunctionsController.mOnStartTetheringCallback.onTetheringFailed(); + + verify(mUsbBackend).setCurrentFunctions(UsbManager.FUNCTION_PTP); + } } From 0c7c59f1b933e3e1a0060611d4cd6a7febc73460 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Fri, 26 Oct 2018 13:40:38 -0700 Subject: [PATCH 2/8] Make android.settings.SETTINGS intent go into homepage. Change-Id: Ib7453a79a473410c44bdcf06019446cedd34f837 Fixes: 117281845 Test: robotests --- AndroidManifest.xml | 19 ++++---- .../android/settings/SettingsActivity.java | 8 +--- .../homepage/SettingsHomepageActivity.java | 11 ++--- .../settings/SettingsActivityTest.java | 14 +++--- .../SettingsHomepageActivityTest.java | 47 +++++++++++++++++++ 5 files changed, 67 insertions(+), 32 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index bc832efcd87..1ffdba16ac2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -111,13 +111,9 @@ - - - - - @@ -126,6 +122,12 @@ android:label="@string/settings_label_launcher" android:theme="@style/Theme.Settings.Home" android:launchMode="singleTask"> + + + + + - + android:exported="false" /> - - + android:exported="true" /> diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 8881fc2f56c..8ed368ca490 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -16,8 +16,6 @@ package com.android.settings; -import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO; - import android.app.ActionBar; import android.app.ActivityManager; import android.content.BroadcastReceiver; @@ -248,10 +246,8 @@ public class SettingsActivity extends SettingsBaseActivity // Getting Intent properties can only be done after the super.onCreate(...) final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); - final ComponentName cn = intent.getComponent(); - final String className = cn.getClassName(); - - mIsShowingDashboard = className.equals(Settings.class.getName()); + mIsShowingDashboard = TextUtils.equals( + SettingsActivity.class.getName(), intent.getComponent().getClassName()); // This is a "Sub Settings" when: // - this is a real SubSettings diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index b3d1aa43ff5..c10543dab6d 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -16,7 +16,6 @@ package com.android.settings.homepage; -import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.FeatureFlagUtils; @@ -27,6 +26,7 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; +import com.android.settings.SettingsActivity; import com.android.settings.core.FeatureFlags; import com.android.settings.core.SettingsBaseActivity; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; @@ -38,9 +38,8 @@ public class SettingsHomepageActivity extends SettingsBaseActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (!isDynamicHomepageEnabled(this)) { - final Intent settings = new Intent(); - settings.setAction("android.settings.SETTINGS"); + if (!FeatureFlagUtils.isEnabled(this, FeatureFlags.DYNAMIC_HOMEPAGE)) { + final Intent settings = new Intent(this, SettingsActivity.class); startActivity(settings); finish(); return; @@ -56,10 +55,6 @@ public class SettingsHomepageActivity extends SettingsBaseActivity { showFragment(new TopLevelSettings(), R.id.main_content); } - public static boolean isDynamicHomepageEnabled(Context context) { - return FeatureFlagUtils.isEnabled(context, FeatureFlags.DYNAMIC_HOMEPAGE); - } - private void showFragment(Fragment fragment, int id) { final FragmentManager fragmentManager = getSupportFragmentManager(); final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); diff --git a/tests/robotests/src/com/android/settings/SettingsActivityTest.java b/tests/robotests/src/com/android/settings/SettingsActivityTest.java index 409512eb140..af25f4835df 100644 --- a/tests/robotests/src/com/android/settings/SettingsActivityTest.java +++ b/tests/robotests/src/com/android/settings/SettingsActivityTest.java @@ -71,10 +71,9 @@ public class SettingsActivityTest { @Test public void onCreate_deviceNotProvisioned_shouldDisableSearch() { Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 0); - final Intent intent = new Intent(mContext, Settings.class); - final SettingsActivity activity = - Robolectric.buildActivity(SettingsActivity.class, intent).create( - Bundle.EMPTY).get(); + final SettingsActivity activity = Robolectric.buildActivity(SettingsActivity.class) + .create(Bundle.EMPTY) + .get(); assertThat(activity.findViewById(R.id.search_bar).getVisibility()) .isEqualTo(View.INVISIBLE); @@ -83,10 +82,9 @@ public class SettingsActivityTest { @Test public void onCreate_deviceProvisioned_shouldEnableSearch() { Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); - final Intent intent = new Intent(mContext, Settings.class); - final SettingsActivity activity = - Robolectric.buildActivity(SettingsActivity.class, intent).create( - Bundle.EMPTY).get(); + final SettingsActivity activity = Robolectric.buildActivity(SettingsActivity.class) + .create(Bundle.EMPTY) + .get(); assertThat(activity.findViewById(R.id.search_bar).getVisibility()).isEqualTo(View.VISIBLE); } diff --git a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java new file mode 100644 index 00000000000..f1228194d49 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java @@ -0,0 +1,47 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import android.util.FeatureFlagUtils; + +import com.android.settings.SettingsActivity; +import com.android.settings.core.FeatureFlags; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowActivity; + +@RunWith(SettingsRobolectricTestRunner.class) +public class SettingsHomepageActivityTest { + + @Test + public void launch_featureFlagOff_shouldRedirectToSettingsActivity() { + FeatureFlagUtils.setEnabled(RuntimeEnvironment.application, FeatureFlags.DYNAMIC_HOMEPAGE, + false); + + final ShadowActivity shadowActivity = Shadows.shadowOf( + Robolectric.setupActivity(SettingsHomepageActivity.class)); + assertThat(shadowActivity.getNextStartedActivity().getComponent().getClassName()) + .isEqualTo(SettingsActivity.class.getName()); + } +} From 293883b2fe991df43bfa7b75ed05761ca17652df Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Fri, 26 Oct 2018 14:53:12 -0700 Subject: [PATCH 3/8] Report fully drawn when all settings are loaded. We do not want to wait for contextual cards in app start instrumentation, as user can already do everything in settings without them. Change-Id: I57d59aa3d623bf6911972ec310d558c25e44bf14 Fixes: 117494071 Fixes: 118495692 Test: perftest --- src/com/android/settings/dashboard/DashboardFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index be59ca11c70..0cda9eef411 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -321,6 +321,12 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment displayResourceTiles(); refreshDashboardTiles(TAG); + + final Activity activity = getActivity(); + if (activity != null) { + Log.d(TAG, "All preferences added, reporting fully drawn"); + activity.reportFullyDrawn(); + } } /** From f61bc19f6b56bfabd71e7516e8467a2c24b3e0b3 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Fri, 5 Oct 2018 19:03:55 +0800 Subject: [PATCH 4/8] Add Connected Device slice to Contextual Settings Homepage - Support Bluetooth device information. - Not yet integrate slice background worker. Bug: 114807655 Test: robotests, visual Change-Id: I23f902137b0468349ee627bed6a394d42ea4a00d --- res/values/strings.xml | 8 + .../SettingsContextualCardProvider.java | 7 + .../slices/ConnectedDeviceSlice.java | 286 ++++++++++++++++++ .../settings/slices/CustomSliceManager.java | 2 + .../slices/ConnectedDeviceSliceTest.java | 99 ++++++ 5 files changed, 402 insertions(+) create mode 100644 src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java create mode 100644 tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSliceTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index f18ec41e4c3..f2661bda11b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10264,4 +10264,12 @@ See more See less + + + + %1$d device connected + %1$d devices connected + + + No connected devices diff --git a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java index 572c36dc15b..36c0a118023 100644 --- a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java +++ b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java @@ -25,6 +25,7 @@ import com.android.settings.homepage.contextualcards.deviceinfo.DataUsageSlice; import com.android.settings.homepage.contextualcards.deviceinfo.DeviceInfoSlice; import com.android.settings.homepage.contextualcards.deviceinfo.EmergencyInfoSlice; import com.android.settings.homepage.contextualcards.deviceinfo.StorageSlice; +import com.android.settings.homepage.contextualcards.slices.ConnectedDeviceSlice; import com.android.settings.intelligence.ContextualCardProto.ContextualCard; import com.android.settings.intelligence.ContextualCardProto.ContextualCardList; import com.android.settings.wifi.WifiSlice; @@ -69,6 +70,11 @@ public class SettingsContextualCardProvider extends ContextualCardProvider { .setSliceUri(BatterySlice.BATTERY_CARD_URI.toSafeString()) .setCardName(BatterySlice.PATH_BATTERY_INFO) .build(); + final ContextualCard connectedDeviceCard = + ContextualCard.newBuilder() + .setSliceUri(ConnectedDeviceSlice.CONNECTED_DEVICE_URI.toString()) + .setCardName(ConnectedDeviceSlice.PATH_CONNECTED_DEVICE) + .build(); final ContextualCardList cards = ContextualCardList.newBuilder() .addCard(wifiCard) .addCard(dataUsageCard) @@ -76,6 +82,7 @@ public class SettingsContextualCardProvider extends ContextualCardProvider { .addCard(storageInfoCard) .addCard(emergencyInfoCard) .addCard(batteryInfoCard) + .addCard(connectedDeviceCard) .build(); return cards; diff --git a/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java b/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java new file mode 100644 index 00000000000..83a6af5d972 --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSlice.java @@ -0,0 +1,286 @@ +/* + * 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.slices; + +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pair; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.SubSettings; +import com.android.settings.Utils; +import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment; +import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.slices.CustomSliceable; +import com.android.settings.slices.SettingsSliceProvider; +import com.android.settings.slices.SliceBuilderUtils; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.core.instrumentation.Instrumentable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * TODO(b/114807655): Contextual Home Page - Connected Device + * + * Show connected device info if one is currently connected. UI for connected device should + * match Connected Devices > Currently Connected Devices + * + * This Slice will show multiple currently connected devices, which includes: + * 1) Bluetooth. + * 2) Docks. + * ... + * TODO Other device types are under checking to support, will update later. + */ +public class ConnectedDeviceSlice implements CustomSliceable { + + /** + * The path denotes the unique name of Connected device Slice. + */ + public static final String PATH_CONNECTED_DEVICE = "connected_device"; + + /** + * Backing Uri for Connected device Slice. + */ + public static final Uri CONNECTED_DEVICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(PATH_CONNECTED_DEVICE) + .build(); + + /** + * To sort the Bluetooth devices by {@link CachedBluetoothDevice}. + * Refer compareTo method from {@link com.android.settings.bluetooth.BluetoothDevicePreference}. + */ + private static final Comparator COMPARATOR + = Comparator.naturalOrder(); + + private static final int DEFAULT_EXPANDED_ROW_COUNT = 4; + + private static final String TAG = "ConnectedDeviceSlice"; + + private final Context mContext; + + public ConnectedDeviceSlice(Context context) { + mContext = context; + } + + private static Bitmap getBitmapFromVectorDrawable(Drawable VectorDrawable) { + final Bitmap bitmap = Bitmap.createBitmap(VectorDrawable.getIntrinsicWidth(), + VectorDrawable.getIntrinsicHeight(), Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + + VectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + VectorDrawable.draw(canvas); + + return bitmap; + } + + @Override + public Uri getUri() { + return CONNECTED_DEVICE_URI; + } + + /** + * Return a Connected Device Slice bound to {@link #CONNECTED_DEVICE_URI}. + */ + @Override + public Slice getSlice() { + final IconCompat icon = IconCompat.createWithResource(mContext, + R.drawable.ic_homepage_connected_device); + final CharSequence title = mContext.getText(R.string.connected_devices_dashboard_title); + final CharSequence titleNoConnectedDevices = mContext.getText( + R.string.no_connected_devices); + final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext, 0, + getIntent(), 0); + final SliceAction primarySliceAction = new SliceAction(primaryActionIntent, icon, + title); + final ListBuilder listBuilder = + new ListBuilder(mContext, CONNECTED_DEVICE_URI, ListBuilder.INFINITY) + .setAccentColor(Utils.getColorAccentDefaultColor(mContext)); + + // Get row builders by connected devices, e.g. Bluetooth. + // TODO Add other type connected devices, e.g. Docks. + final List rows = getBluetoothRowBuilder(primarySliceAction); + + // Return a header with IsError flag, if no connected devices. + if (rows.isEmpty()) { + return listBuilder.setHeader(new ListBuilder.HeaderBuilder() + .setTitle(titleNoConnectedDevices) + .setPrimaryAction(primarySliceAction)) + .setIsError(true) + .build(); + } + + // According the number of connected devices to set sub title of header. + listBuilder.setHeader(new ListBuilder.HeaderBuilder() + .setTitle(title) + .setSubtitle(getSubTitle(rows.size())) + .setPrimaryAction(primarySliceAction)); + + // Add rows. + for (ListBuilder.RowBuilder rowBuilder : rows) { + listBuilder.addRow(rowBuilder); + } + + // Only show "see more" button when the number of data row is more than or equal to 4. + // TODO(b/118465996): SHOW MORE button won't work properly when having two data rows + if (rows.size() >= DEFAULT_EXPANDED_ROW_COUNT) { + listBuilder.setSeeMoreAction(primaryActionIntent); + } + + return listBuilder.build(); + } + + @Override + public Intent getIntent() { + final String screenTitle = mContext.getText(R.string.connected_devices_dashboard_title) + .toString(); + final Uri contentUri = new Uri.Builder().appendPath(PATH_CONNECTED_DEVICE).build(); + + return SliceBuilderUtils.buildSearchResultPageIntent(mContext, + ConnectedDeviceDashboardFragment.class.getName(), PATH_CONNECTED_DEVICE, + screenTitle, + MetricsProto.MetricsEvent.SLICE) + .setClassName(mContext.getPackageName(), SubSettings.class.getName()) + .setData(contentUri); + } + + @Override + public void onNotifyChange(Intent intent) { + } + + @VisibleForTesting + List getBluetoothConnectedDevices() { + final List connectedBluetoothList = new ArrayList<>(); + + // If Bluetooth is disable, skip to get the bluetooth devices. + if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) { + Log.d(TAG, "Cannot get Bluetooth connected devices, Bluetooth is disabled."); + return connectedBluetoothList; + } + + // Get the Bluetooth devices from LocalBluetoothManager. + final LocalBluetoothManager bluetoothManager = + com.android.settings.bluetooth.Utils.getLocalBtManager(mContext); + if (bluetoothManager == null) { + Log.d(TAG, "Cannot get Bluetooth connected devices, Bluetooth is not supported."); + return connectedBluetoothList; + } + final Collection cachedDevices = + bluetoothManager.getCachedDeviceManager().getCachedDevicesCopy(); + + // Get all connected Bluetooth devices and use Map to filter duplicated Bluetooth. + final Map connectedBluetoothMap = new ArrayMap<>(); + for (CachedBluetoothDevice device : cachedDevices) { + if (device.isConnected() && !connectedBluetoothMap.containsKey(device.getDevice())) { + connectedBluetoothMap.put(device.getDevice(), device); + } + } + + // Sort connected Bluetooth devices. + connectedBluetoothList.addAll(connectedBluetoothMap.values()); + Collections.sort(connectedBluetoothList, COMPARATOR); + + return connectedBluetoothList; + } + + @VisibleForTesting + PendingIntent getBluetoothDetailIntent(CachedBluetoothDevice device) { + final Bundle args = new Bundle(); + args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, + device.getDevice().getAddress()); + final SubSettingLauncher subSettingLauncher = new SubSettingLauncher(mContext); + subSettingLauncher.setDestination(BluetoothDeviceDetailsFragment.class.getName()) + .setArguments(args) + .setTitleRes(R.string.device_details_title) + .setSourceMetricsCategory(Instrumentable.METRICS_CATEGORY_UNKNOWN); + + // The requestCode should be unique, use the hashcode of device as request code. + return PendingIntent + .getActivity(mContext, device.hashCode() /* requestCode */, + subSettingLauncher.toIntent(), + 0 /* flags */); + } + + @VisibleForTesting + IconCompat getConnectedDeviceIcon(CachedBluetoothDevice device) { + final Pair pair = BluetoothUtils + .getBtClassDrawableWithDescription(mContext, device); + + if (pair.first != null) { + return IconCompat.createWithBitmap(getBitmapFromVectorDrawable(pair.first)); + } else { + return IconCompat.createWithResource(mContext, R.drawable.ic_homepage_connected_device); + } + } + + private List getBluetoothRowBuilder(SliceAction primarySliceAction) { + final List bluetoothRows = new ArrayList<>(); + + // According Bluetooth connected device to create row builders. + final List bluetoothDevices = getBluetoothConnectedDevices(); + for (CachedBluetoothDevice bluetoothDevice : bluetoothDevices) { + bluetoothRows.add(new ListBuilder.RowBuilder() + .setTitleItem(getConnectedDeviceIcon(bluetoothDevice), ListBuilder.ICON_IMAGE) + .setTitle(bluetoothDevice.getName()) + .setSubtitle(bluetoothDevice.getConnectionSummary()) + .setPrimaryAction(primarySliceAction) + .addEndItem(buildBluetoothDetailDeepLinkAction(bluetoothDevice))); + } + + return bluetoothRows; + } + + private SliceAction buildBluetoothDetailDeepLinkAction(CachedBluetoothDevice bluetoothDevice) { + return new SliceAction( + getBluetoothDetailIntent(bluetoothDevice), + IconCompat.createWithResource(mContext, R.drawable.ic_settings), + bluetoothDevice.getName()); + } + + private CharSequence getSubTitle(int deviceCount) { + return mContext.getResources().getQuantityString(R.plurals.show_connected_devices, + deviceCount, deviceCount); + } +} \ No newline at end of file diff --git a/src/com/android/settings/slices/CustomSliceManager.java b/src/com/android/settings/slices/CustomSliceManager.java index 4658d2a0b98..556c69817b3 100644 --- a/src/com/android/settings/slices/CustomSliceManager.java +++ b/src/com/android/settings/slices/CustomSliceManager.java @@ -24,6 +24,7 @@ import com.android.settings.homepage.contextualcards.deviceinfo.BatterySlice; import com.android.settings.homepage.contextualcards.deviceinfo.DataUsageSlice; import com.android.settings.homepage.contextualcards.deviceinfo.DeviceInfoSlice; import com.android.settings.homepage.contextualcards.deviceinfo.StorageSlice; +import com.android.settings.homepage.contextualcards.slices.ConnectedDeviceSlice; import com.android.settings.wifi.WifiSlice; import java.util.Map; @@ -103,5 +104,6 @@ public class CustomSliceManager { mUriMap.put(DeviceInfoSlice.DEVICE_INFO_CARD_URI, DeviceInfoSlice.class); mUriMap.put(StorageSlice.STORAGE_CARD_URI, StorageSlice.class); mUriMap.put(BatterySlice.BATTERY_CARD_URI, BatterySlice.class); + mUriMap.put(ConnectedDeviceSlice.CONNECTED_DEVICE_URI, ConnectedDeviceSlice.class); } } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSliceTest.java new file mode 100644 index 00000000000..23da1272b5c --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ConnectedDeviceSliceTest.java @@ -0,0 +1,99 @@ +/* + * 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.slices; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.SliceProvider; +import androidx.slice.widget.SliceLiveData; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.SliceTester; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ConnectedDeviceSliceTest { + + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + + private List mCachedDevices = new ArrayList(); + private Context mContext; + private ConnectedDeviceSlice mConnectedDeviceSlice; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + + mConnectedDeviceSlice = spy(new ConnectedDeviceSlice(mContext)); + } + + @Test + public void getSlice_hasConnectedDevices_shouldBeCorrectSliceContent() { + final String title = "BluetoothTitle"; + final String summary = "BluetoothSummary"; + final IconCompat icon = IconCompat.createWithResource(mContext, + R.drawable.ic_homepage_connected_device); + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, + new Intent("test action"), 0); + doReturn(title).when(mCachedBluetoothDevice).getName(); + doReturn(summary).when(mCachedBluetoothDevice).getConnectionSummary(); + mCachedDevices.add(mCachedBluetoothDevice); + doReturn(mCachedDevices).when(mConnectedDeviceSlice).getBluetoothConnectedDevices(); + doReturn(icon).when(mConnectedDeviceSlice).getConnectedDeviceIcon(any()); + doReturn(pendingIntent).when(mConnectedDeviceSlice).getBluetoothDetailIntent(any()); + final Slice slice = mConnectedDeviceSlice.getSlice(); + + final List sliceItems = slice.getItems(); + SliceTester.assertTitle(sliceItems, title); + } + + @Test + public void getSlice_hasNoConnectedDevices_shouldReturnCorrectHeader() { + final List connectedBluetoothList = new ArrayList<>(); + doReturn(connectedBluetoothList).when(mConnectedDeviceSlice).getBluetoothConnectedDevices(); + final Slice slice = mConnectedDeviceSlice.getSlice(); + + final List sliceItems = slice.getItems(); + SliceTester.assertTitle(sliceItems, mContext.getString(R.string.no_connected_devices)); + } +} \ No newline at end of file From 52fd31c2f3dbdee58ee57b08336445df8ce80ae9 Mon Sep 17 00:00:00 2001 From: Rakesh Iyer Date: Fri, 26 Oct 2018 12:45:38 -0700 Subject: [PATCH 5/8] Misc fixes to TTS settings. 1. Remove an extra PreferenceGroup that was causing some spacing issues. 2. Remove the animations when elements resize because this causes items to slide around unnecessarily. 3. While we're in the file, lambda-ify some of the older code. Bug: 77981482 Bug: 79897262 Test: Manually verified. Change-Id: I9c79beea3a231ba62aeef3543802faebd775e128 --- res/xml/tts_settings.xml | 24 ++++++-------- .../settings/tts/TextToSpeechSettings.java | 31 +++++-------------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/res/xml/tts_settings.xml b/res/xml/tts_settings.xml index 4a2415477ee..e76cdb78046 100644 --- a/res/xml/tts_settings.xml +++ b/res/xml/tts_settings.xml @@ -20,21 +20,17 @@ android:key="tts_settings_screen" android:title="@string/tts_settings_title"> - + - - - - - + diff --git a/src/com/android/settings/tts/TextToSpeechSettings.java b/src/com/android/settings/tts/TextToSpeechSettings.java index 0f10d28572c..866e5a8000b 100644 --- a/src/com/android/settings/tts/TextToSpeechSettings.java +++ b/src/com/android/settings/tts/TextToSpeechSettings.java @@ -147,12 +147,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment * screen for the first time (as opposed to when a user changes his choice * of engine). */ - private final TextToSpeech.OnInitListener mInitListener = new TextToSpeech.OnInitListener() { - @Override - public void onInit(int status) { - onInitEngine(status); - } - }; + private final TextToSpeech.OnInitListener mInitListener = this::onInitEngine; @Override public int getMetricsCategory() { @@ -215,6 +210,11 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment @Override public void onResume() { super.onResume(); + // We tend to change the summary contents of our widgets, which at higher text sizes causes + // them to resize, which results in the recyclerview smoothly animating them at inopportune + // times. Disable the animation so widgets snap to their positions rather than sliding + // around while the user is interacting with it. + getListView().getItemAnimator().setMoveDuration(0); if (mTts == null || mCurrentDefaultLocale == null) { return; @@ -323,7 +323,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment if (mCurrentEngine != null) { EngineInfo info = mEnginesHelper.getEngineInfo(mCurrentEngine); - Preference mEnginePreference = findPreference(KEY_TTS_ENGINE_PREFERENCE); ((GearPreference) mEnginePreference).setOnGearClickListener(this); mEnginePreference.setSummary(info.label); @@ -365,14 +364,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment if (status == TextToSpeech.SUCCESS) { if (DBG) Log.d(TAG, "TTS engine for settings screen initialized."); checkDefaultLocale(); - getActivity() - .runOnUiThread( - new Runnable() { - @Override - public void run() { - mLocalePreference.setEnabled(true); - } - }); + getActivity().runOnUiThread(() -> mLocalePreference.setEnabled(true)); } else { if (DBG) { Log.d(TAG, @@ -516,14 +508,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment } // Sort it - Collections.sort( - entryPairs, - new Comparator>() { - @Override - public int compare(Pair lhs, Pair rhs) { - return lhs.first.compareToIgnoreCase(rhs.first); - } - }); + Collections.sort(entryPairs, (lhs, rhs) -> lhs.first.compareToIgnoreCase(rhs.first)); // Get two arrays out of one of pairs mSelectedLocaleIndex = 0; // Will point to the R.string.tts_lang_use_system value From af4426bb65dde5aab4913a19a99143b9737242cb Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 29 Oct 2018 13:09:12 -0700 Subject: [PATCH 6/8] Use ImeAwareEditText in FingerprintSettings Like we did the same thing in the Settings app [1][2][3], FingerprintSetting is another instance that should use ImeAwareEditText to make sure InputMethodManager#showSoftInput() is called after the EditText gains IME focus. This approach should be more robust and easier to maintain than trying to control the software keyboard visibility with SOFT_INPUT_STATE_ALWAYS_VISIBLE, which is more complicated that people think. This CL also removes RenameDialog#mTextHadFocus since RenameDialog#mDialogTextField is always focused when the dialog is shown [4]. This CL also removes the following fields since they can just be a local final variables that will be captured into callback. * RenameDialog#mFingerName * RenameDialog#mTextSelectionStart * RenameDialog#mTextSelectionEnd [1]: I182b05d3ff59fb3b4732d60d0d5a464f0e0e0235 f6af093e2dfd156768a3937e94d405c04c541fea [2]: I892d639f3cc5d43db553b682d5278b8ce2fe72da 480326710681b1ef1c1a55cf38b3de10ed75f27e [3]: Ib75ba0f361b8b46c3b717cc1ffb864726958ed82 b0bcbed3722bcd16ba16f49f36c84ed8f8825ae3 [4]: I5bb1b7227c1323595bf7f483e11e87e2c3550093 7a1d52eb061a7b936f15aa99b997a87f5bb0b16f Fix: 118473687 Test: Manually verified as follows. 1. Build and flash aosp_taimen-userdebug to Taimen 2. Set up a fingerprint unlock. 3. Rotate the device to landscape mode. 4. Open Settings -> Security & location -> Fingerprint 5. Re-enter password if necessary 6. Tap "Finger 1" 7. Make sure that AOSP Keyboard shows up Change-Id: I2a137aa8f6a1ee2b67923bcf40e82320a56c7b59 --- res/layout/fingerprint_rename_dialog.xml | 2 +- .../fingerprint/FingerprintSettings.java | 39 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/res/layout/fingerprint_rename_dialog.xml b/res/layout/fingerprint_rename_dialog.xml index 99cfa718d0e..2da84ca9876 100644 --- a/res/layout/fingerprint_rename_dialog.xml +++ b/res/layout/fingerprint_rename_dialog.xml @@ -35,7 +35,7 @@ android:clipChildren="false" android:clipToPadding="false" /> - Date: Mon, 29 Oct 2018 20:33:19 +0000 Subject: [PATCH 7/8] Revert "resolve merge conflicts of 7849607a7ca9518f57fb22cca5665b88e99134c2 to pi-dev-plus-aosp" This reverts commit e81224d80c3af797003a40f06fc7563728a0da96. Reason for revert: broke pi-dev-plus-aosp Change-Id: Ib45b96216017d01b6301a558a0cd27bf1071779c Merged-In: I3d2ebad2879479a870bcdfe596bb88b83c424389 --- .../usb/UsbDefaultFragment.java | 42 +--------- .../usb/UsbDetailsFunctionsController.java | 49 +---------- .../usb/UsbDefaultFragmentTest.java | 55 ++----------- .../UsbDetailsFunctionsControllerTest.java | 81 ++++--------------- 4 files changed, 25 insertions(+), 202 deletions(-) diff --git a/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java b/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java index 6e7c6a77496..e1b3d70049d 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java +++ b/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java @@ -16,14 +16,9 @@ package com.android.settings.connecteddevice.usb; -import static android.net.ConnectivityManager.TETHERING_USB; - import android.content.Context; import android.graphics.drawable.Drawable; -import android.hardware.usb.UsbManager; -import android.net.ConnectivityManager; import android.os.Bundle; -import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; @@ -44,18 +39,11 @@ import java.util.List; public class UsbDefaultFragment extends RadioButtonPickerFragment { @VisibleForTesting UsbBackend mUsbBackend; - @VisibleForTesting - ConnectivityManager mConnectivityManager; - @VisibleForTesting - OnStartTetheringCallback mOnStartTetheringCallback = new OnStartTetheringCallback(); - @VisibleForTesting - long mPreviousFunctions; @Override public void onAttach(Context context) { super.onAttach(context); mUsbBackend = new UsbBackend(context); - mConnectivityManager = context.getSystemService(ConnectivityManager.class); } @Override @@ -115,37 +103,9 @@ public class UsbDefaultFragment extends RadioButtonPickerFragment { @Override protected boolean setDefaultKey(String key) { long functions = UsbBackend.usbFunctionsFromString(key); - mPreviousFunctions = mUsbBackend.getCurrentFunctions(); if (!Utils.isMonkeyRunning()) { - if (functions == UsbManager.FUNCTION_RNDIS) { - // We need to have entitlement check for usb tethering, so use API in - // ConnectivityManager. - mConnectivityManager.startTethering(TETHERING_USB, true /* showProvisioningUi */, - mOnStartTetheringCallback); - } else { - mUsbBackend.setDefaultUsbFunctions(functions); - } - + mUsbBackend.setDefaultUsbFunctions(functions); } return true; } - - @VisibleForTesting - final class OnStartTetheringCallback extends - ConnectivityManager.OnStartTetheringCallback { - - @Override - public void onTetheringStarted() { - super.onTetheringStarted(); - // Set default usb functions again to make internal data persistent - mUsbBackend.setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS); - } - - @Override - public void onTetheringFailed() { - super.onTetheringFailed(); - mUsbBackend.setDefaultUsbFunctions(mPreviousFunctions); - updateCandidates(); - } - } } \ No newline at end of file diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java index f74dc0f5c5f..8ae334c3a8f 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java +++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java @@ -16,14 +16,9 @@ package com.android.settings.connecteddevice.usb; -import static android.net.ConnectivityManager.TETHERING_USB; - import android.content.Context; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; -import android.net.ConnectivityManager; - -import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; @@ -51,18 +46,10 @@ public class UsbDetailsFunctionsController extends UsbDetailsController } private PreferenceCategory mProfilesContainer; - private ConnectivityManager mConnectivityManager; - @VisibleForTesting - OnStartTetheringCallback mOnStartTetheringCallback; - @VisibleForTesting - long mPreviousFunction; public UsbDetailsFunctionsController(Context context, UsbDetailsFragment fragment, UsbBackend backend) { super(context, fragment, backend); - mConnectivityManager = context.getSystemService(ConnectivityManager.class); - mOnStartTetheringCallback = new OnStartTetheringCallback(); - mPreviousFunction = mUsbBackend.getCurrentFunctions(); } @Override @@ -109,28 +96,9 @@ public class UsbDetailsFunctionsController extends UsbDetailsController @Override public void onRadioButtonClicked(RadioButtonPreference preference) { - final long function = UsbBackend.usbFunctionsFromString(preference.getKey()); - final long previousFunction = mUsbBackend.getCurrentFunctions(); - if (function != previousFunction && !Utils.isMonkeyRunning()) { - mPreviousFunction = previousFunction; - - if (function == UsbManager.FUNCTION_RNDIS) { - //Update the UI in advance to make it looks smooth - final RadioButtonPreference prevPref = - (RadioButtonPreference) mProfilesContainer.findPreference( - UsbBackend.usbFunctionsToString(mPreviousFunction)); - if (prevPref != null) { - prevPref.setChecked(false); - preference.setChecked(true); - } - - // We need to have entitlement check for usb tethering, so use API in - // ConnectivityManager. - mConnectivityManager.startTethering(TETHERING_USB, true /* showProvisioningUi */, - mOnStartTetheringCallback); - } else { - mUsbBackend.setCurrentFunctions(function); - } + long function = UsbBackend.usbFunctionsFromString(preference.getKey()); + if (function != mUsbBackend.getCurrentFunctions() && !Utils.isMonkeyRunning()) { + mUsbBackend.setCurrentFunctions(function); } } @@ -143,15 +111,4 @@ public class UsbDetailsFunctionsController extends UsbDetailsController public String getPreferenceKey() { return "usb_details_functions"; } - - @VisibleForTesting - final class OnStartTetheringCallback extends - ConnectivityManager.OnStartTetheringCallback { - - @Override - public void onTetheringFailed() { - super.onTetheringFailed(); - mUsbBackend.setCurrentFunctions(mPreviousFunction); - } - } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java index 2c619dc4056..dca10d73390 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java @@ -16,21 +16,12 @@ package com.android.settings.connecteddevice.usb; -import static android.net.ConnectivityManager.TETHERING_USB; - import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Matchers.anyLong; -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.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.hardware.usb.UsbManager; -import android.net.ConnectivityManager; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowUtils; @@ -40,17 +31,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.Robolectric; import org.robolectric.annotation.Config; -import org.robolectric.util.FragmentTestUtil; @RunWith(SettingsRobolectricTestRunner.class) public class UsbDefaultFragmentTest { @Mock private UsbBackend mUsbBackend; - @Mock - private ConnectivityManager mConnectivityManager; private UsbDefaultFragment mFragment; @@ -59,7 +46,6 @@ public class UsbDefaultFragmentTest { MockitoAnnotations.initMocks(this); mFragment = new UsbDefaultFragment(); mFragment.mUsbBackend = mUsbBackend; - mFragment.mConnectivityManager = mConnectivityManager; } @Test @@ -115,6 +101,12 @@ public class UsbDefaultFragmentTest { verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_PTP); } + @Test + public void setDefaultKey_isRndis_shouldSetRndis() { + mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS)); + verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS); + } + @Test public void setDefaultKey_isMidi_shouldSetMidi() { mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_MIDI)); @@ -126,39 +118,6 @@ public class UsbDefaultFragmentTest { public void setDefaultKey_isMonkey_shouldDoNothing() { ShadowUtils.setIsUserAMonkey(true); mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_MTP)); - - verify(mUsbBackend, never()).setDefaultUsbFunctions(anyLong()); - } - - @Test - public void setDefaultKey_functionRndis_startTetheringInvoked() { - doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); - - mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS)); - - verify(mConnectivityManager).startTethering(TETHERING_USB, true, - mFragment.mOnStartTetheringCallback); - assertThat(mFragment.mPreviousFunctions).isEqualTo( - UsbManager.FUNCTION_MTP); - } - - @Test - public void setDefaultKey_functionOther_setCurrentFunctionInvoked() { - doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); - - mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP)); - - verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_PTP); - assertThat(mFragment.mPreviousFunctions).isEqualTo( - UsbManager.FUNCTION_MTP); - } - - @Test - public void onTetheringStarted_setDefaultUsbFunctions() { - mFragment.mPreviousFunctions = UsbManager.FUNCTION_PTP; - - mFragment.mOnStartTetheringCallback.onTetheringStarted(); - - verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS); + verifyZeroInteractions(mUsbBackend); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java index 6e43d48e040..989a293fc1d 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java @@ -16,14 +16,9 @@ package com.android.settings.connecteddevice.usb; -import static android.net.ConnectivityManager.TETHERING_USB; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyLong; -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; @@ -31,7 +26,6 @@ import android.app.Activity; import android.content.Context; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; - import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; @@ -59,10 +53,9 @@ public class UsbDetailsFunctionsControllerTest { private UsbDetailsFunctionsController mDetailsFunctionsController; private Context mContext; private Lifecycle mLifecycle; - private PreferenceCategory mPreferenceCategory; + private PreferenceCategory mPreference; private PreferenceManager mPreferenceManager; private PreferenceScreen mScreen; - private RadioButtonPreference mRadioButtonPreference; @Mock private UsbBackend mUsbBackend; @@ -70,14 +63,12 @@ public class UsbDetailsFunctionsControllerTest { private UsbDetailsFragment mFragment; @Mock private Activity mActivity; - @Mock - private ConnectivityManager mConnectivityManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); + mContext = RuntimeEnvironment.application; mLifecycle = new Lifecycle(() -> mLifecycle); mPreferenceManager = new PreferenceManager(mContext); mScreen = mPreferenceManager.createPreferenceScreen(mContext); @@ -87,16 +78,12 @@ public class UsbDetailsFunctionsControllerTest { when(mFragment.getContext()).thenReturn(mContext); when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager); when(mFragment.getPreferenceScreen()).thenReturn(mScreen); - when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); mDetailsFunctionsController = new UsbDetailsFunctionsController(mContext, mFragment, mUsbBackend); - mPreferenceCategory = new PreferenceCategory(mContext); - mPreferenceCategory.setKey(mDetailsFunctionsController.getPreferenceKey()); - mScreen.addPreference(mPreferenceCategory); - mDetailsFunctionsController.displayPreference(mScreen); - - mRadioButtonPreference = new RadioButtonPreference(mContext); + mPreference = new PreferenceCategory(mContext); + mPreference.setKey(mDetailsFunctionsController.getPreferenceKey()); + mScreen.addPreference(mPreference); } @Test @@ -118,9 +105,10 @@ public class UsbDetailsFunctionsControllerTest { public void displayRefresh_disconnected_shouldDisable() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); + mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(false, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); - assertThat(mPreferenceCategory.isEnabled()).isFalse(); + assertThat(mPreference.isEnabled()).isFalse(); } @Test @@ -130,6 +118,7 @@ public class UsbDetailsFunctionsControllerTest { when(mUsbBackend.areFunctionsSupported(UsbManager.FUNCTION_PTP)).thenReturn(false); when(mUsbBackend.areFunctionsSupported(UsbManager.FUNCTION_RNDIS)).thenReturn(false); + mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); List prefs = getRadioPreferences(); @@ -142,6 +131,7 @@ public class UsbDetailsFunctionsControllerTest { public void displayRefresh_mtpEnabled_shouldCheckSwitches() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); + mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_MTP, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); List prefs = getRadioPreferences(); @@ -155,6 +145,7 @@ public class UsbDetailsFunctionsControllerTest { public void onClickMtp_noneEnabled_shouldEnableMtp() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); + mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_NONE); @@ -173,6 +164,7 @@ public class UsbDetailsFunctionsControllerTest { public void onClickMtp_ptpEnabled_shouldEnableMtp() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); + mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_PTP, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_PTP); @@ -194,6 +186,7 @@ public class UsbDetailsFunctionsControllerTest { public void onClickNone_mtpEnabled_shouldDisableMtp() { when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true); + mDetailsFunctionsController.displayPreference(mScreen); mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_MTP, UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_MTP); @@ -217,55 +210,9 @@ public class UsbDetailsFunctionsControllerTest { private List getRadioPreferences() { ArrayList result = new ArrayList<>(); - for (int i = 0; i < mPreferenceCategory.getPreferenceCount(); i++) { - result.add((RadioButtonPreference) mPreferenceCategory.getPreference(i)); + for (int i = 0; i < mPreference.getPreferenceCount(); i++) { + result.add((RadioButtonPreference) mPreference.getPreference(i)); } return result; } - - @Test - public void onRadioButtonClicked_functionRndis_startTetheringInvoked() { - mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS)); - doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); - - mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference); - - verify(mConnectivityManager).startTethering(TETHERING_USB, true, - mDetailsFunctionsController.mOnStartTetheringCallback); - assertThat(mDetailsFunctionsController.mPreviousFunction).isEqualTo( - UsbManager.FUNCTION_MTP); - } - - @Test - public void onRadioButtonClicked_functionOther_setCurrentFunctionInvoked() { - mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP)); - doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions(); - - mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference); - - verify(mUsbBackend).setCurrentFunctions(UsbManager.FUNCTION_PTP); - assertThat(mDetailsFunctionsController.mPreviousFunction).isEqualTo( - UsbManager.FUNCTION_MTP); - } - - @Test - public void onRadioButtonClicked_clickSameButton_doNothing() { - mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP)); - doReturn(UsbManager.FUNCTION_PTP).when(mUsbBackend).getCurrentFunctions(); - - mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference); - - verify(mUsbBackend, never()).setCurrentFunctions(UsbManager.FUNCTION_PTP); - verify(mConnectivityManager, never()).startTethering(TETHERING_USB, true, - mDetailsFunctionsController.mOnStartTetheringCallback); - } - - @Test - public void onTetheringFailed_resetPreviousFunctions() { - mDetailsFunctionsController.mPreviousFunction = UsbManager.FUNCTION_PTP; - - mDetailsFunctionsController.mOnStartTetheringCallback.onTetheringFailed(); - - verify(mUsbBackend).setCurrentFunctions(UsbManager.FUNCTION_PTP); - } } From 008c7781e8888edc4550f741ba16ca0043b86b43 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Fri, 26 Oct 2018 14:43:31 -0700 Subject: [PATCH 8/8] Refine the logic to show/hide cdma preference 1. Add special check for "system select" 2. For cdma, need to check whether to hide carrier settings Bug: 114749736 Test: RunSettingsRoboTests Change-Id: I0a20816912500725894208a60bba7a5c74912561 --- .../network/telephony/MobileNetworkUtils.java | 19 +++--- .../CdmaSystemSelectPreferenceController.java | 8 +++ .../telephony/MobileNetworkUtilsTest.java | 58 +++++++++++++------ ...aSubscriptionPreferenceControllerTest.java | 10 ++++ ...aSystemSelectPreferenceControllerTest.java | 26 +++++++++ 5 files changed, 95 insertions(+), 26 deletions(-) diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java index b43cd3a1a24..f3973829cc2 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java +++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java @@ -30,7 +30,6 @@ import android.provider.Settings; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; -import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -213,11 +212,6 @@ public class MobileNetworkUtils { || (!esimIgnoredDevice && enabledEsimUiByDefault && inEsimSupportedCountries)); } - public static PersistableBundle getCarrierConfigBySubId(int mSubId) { - //TODO(b/114749736): get carrier config from subId - return new PersistableBundle(); - } - /** * Set whether to enable data for {@code subId}, also whether to disable data for other * subscription @@ -253,9 +247,17 @@ public class MobileNetworkUtils { } final TelephonyManager telephonyManager = TelephonyManager.from(context) .createForSubscriptionId(subId); + final PersistableBundle carrierConfig = context.getSystemService( + CarrierConfigManager.class).getConfigForSubId(subId); + if (telephonyManager.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { return true; + } else if (carrierConfig != null + && !carrierConfig.getBoolean( + CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) + && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) { + return true; } if (isWorldMode(context, subId)) { @@ -312,7 +314,10 @@ public class MobileNetworkUtils { if (telephonyManager.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { return true; - } else if (carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) { + } else if (carrierConfig != null + && !carrierConfig.getBoolean( + CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) + && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) { return true; } diff --git a/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java b/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java index 4119c64e59c..5f259f85982 100644 --- a/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java +++ b/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java @@ -23,6 +23,8 @@ import android.telephony.TelephonyManager; import androidx.preference.ListPreference; import androidx.preference.Preference; +import com.android.internal.telephony.Phone; + /** * Preference controller for "System Select" */ @@ -47,6 +49,12 @@ public class CdmaSystemSelectPreferenceController extends CdmaBasePreferenceCont resetCdmaRoamingModeToDefault(); } } + final int settingsNetworkMode = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.PREFERRED_NETWORK_MODE + mSubId, + Phone.PREFERRED_NT_MODE); + listPreference.setEnabled( + settingsNetworkMode != TelephonyManager.NETWORK_MODE_LTE_GSM_WCDMA); } @Override diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java index c03fb71d466..676c9f4f23d 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java @@ -21,18 +21,20 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.nullable; -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.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.PersistableBundle; import android.provider.Settings; import android.telecom.PhoneAccountHandle; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -75,27 +77,35 @@ public class MobileNetworkUtilsTest { private ComponentName mComponentName; @Mock private ResolveInfo mResolveInfo; + @Mock + private CarrierConfigManager mCarrierConfigManager; private Context mContext; + private PersistableBundle mCarrierConfig; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class); - doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE); - doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID_1); - doReturn(mTelephonyManager2).when(mTelephonyManager).createForSubscriptionId(SUB_ID_2); - doReturn(mPackageManager).when(mContext).getPackageManager(); - doReturn(mComponentName).when(mPhoneAccountHandle).getComponentName(); - doReturn(PACKAGE_NAME).when(mComponentName).getPackageName(); + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + when(mTelephonyManager.createForSubscriptionId(SUB_ID_1)).thenReturn(mTelephonyManager); + when(mTelephonyManager.createForSubscriptionId(SUB_ID_2)).thenReturn(mTelephonyManager2); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPhoneAccountHandle.getComponentName()).thenReturn(mComponentName); + when(mComponentName.getPackageName()).thenReturn(PACKAGE_NAME); + when(mContext.getSystemService(CarrierConfigManager.class)).thenReturn( + mCarrierConfigManager); - doReturn(SUB_ID_1).when(mSubscriptionInfo1).getSubscriptionId(); - doReturn(SUB_ID_2).when(mSubscriptionInfo2).getSubscriptionId(); + mCarrierConfig = new PersistableBundle(); + when(mCarrierConfigManager.getConfigForSubId(SUB_ID_1)).thenReturn(mCarrierConfig); - doReturn(Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2)).when( - mSubscriptionManager).getActiveSubscriptionInfoList(); + when(mSubscriptionInfo1.getSubscriptionId()).thenReturn(SUB_ID_1); + when(mSubscriptionInfo2.getSubscriptionId()).thenReturn(SUB_ID_2); + + when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn( + Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2)); } @Test @@ -129,8 +139,8 @@ public class MobileNetworkUtilsTest { @Test public void buildConfigureIntent_noActivityHandleIntent_returnNull() { - doReturn(new ArrayList()).when(mPackageManager).queryIntentActivities( - nullable(Intent.class), anyInt()); + when(mPackageManager.queryIntentActivities(nullable(Intent.class), anyInt())) + .thenReturn(new ArrayList<>()); assertThat(MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext, mPhoneAccountHandle)).isNull(); @@ -138,8 +148,8 @@ public class MobileNetworkUtilsTest { @Test public void buildConfigureIntent_hasActivityHandleIntent_returnIntent() { - doReturn(Arrays.asList(mResolveInfo)).when(mPackageManager).queryIntentActivities( - nullable(Intent.class), anyInt()); + when(mPackageManager.queryIntentActivities(nullable(Intent.class), anyInt())) + .thenReturn(Arrays.asList(mResolveInfo)); assertThat(MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext, mPhoneAccountHandle)).isNotNull(); @@ -147,19 +157,29 @@ public class MobileNetworkUtilsTest { @Test public void isCdmaOptions_phoneTypeCdma_returnTrue() { - doReturn(PhoneConstants.PHONE_TYPE_CDMA).when(mTelephonyManager).getPhoneType(); + when(mTelephonyManager.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_CDMA); assertThat(MobileNetworkUtils.isCdmaOptions(mContext, SUB_ID_1)).isTrue(); } @Test public void isCdmaOptions_worldModeWithGsmWcdma_returnTrue() { - doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mTelephonyManager).getPhoneType(); - doReturn("true").when(mContext).getString(R.string.config_world_mode); + when(mTelephonyManager.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); + when(mContext.getString(R.string.config_world_mode)).thenReturn("true"); Settings.Global.putInt(mContext.getContentResolver(), android.provider.Settings.Global.PREFERRED_NETWORK_MODE + SUB_ID_1, TelephonyManager.NETWORK_MODE_LTE_GSM_WCDMA); assertThat(MobileNetworkUtils.isCdmaOptions(mContext, SUB_ID_1)).isTrue(); } + + @Test + public void isCdmaOptions_carrierWorldModeWithoutHideCarrier_returnTrue() { + when(mTelephonyManager.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, + false); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true); + + assertThat(MobileNetworkUtils.isCdmaOptions(mContext, SUB_ID_1)).isTrue(); + } } diff --git a/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSubscriptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSubscriptionPreferenceControllerTest.java index c938948a403..4044b6caf98 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSubscriptionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSubscriptionPreferenceControllerTest.java @@ -21,10 +21,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.Context; +import android.os.PersistableBundle; import android.os.SystemProperties; import android.provider.Settings; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -53,9 +56,12 @@ public class CdmaSubscriptionPreferenceControllerTest { private TelephonyManager mInvalidTelephonyManager; @Mock private SubscriptionManager mSubscriptionManager; + @Mock + private CarrierConfigManager mCarrierConfigManager; private CdmaSubscriptionPreferenceController mController; private ListPreference mPreference; + private PersistableBundle mCarrierConfig; private Context mContext; @Before @@ -68,6 +74,10 @@ public class CdmaSubscriptionPreferenceControllerTest { doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID); doReturn(mInvalidTelephonyManager).when(mTelephonyManager).createForSubscriptionId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); + doReturn(mCarrierConfigManager).when(mContext).getSystemService(CarrierConfigManager.class); + + mCarrierConfig = new PersistableBundle(); + when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfig); mPreference = new ListPreference(mContext); mController = new CdmaSubscriptionPreferenceController(mContext, "mobile_data"); diff --git a/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java index 204623728ca..f00afc97bbc 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java @@ -21,9 +21,12 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.Context; +import android.os.PersistableBundle; import android.provider.Settings; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -51,9 +54,12 @@ public class CdmaSystemSelectPreferenceControllerTest { private TelephonyManager mInvalidTelephonyManager; @Mock private SubscriptionManager mSubscriptionManager; + @Mock + private CarrierConfigManager mCarrierConfigManager; private CdmaSystemSelectPreferenceController mController; private ListPreference mPreference; + private PersistableBundle mCarrierConfig; private Context mContext; @Before @@ -67,6 +73,12 @@ public class CdmaSystemSelectPreferenceControllerTest { doReturn(mInvalidTelephonyManager).when(mTelephonyManager).createForSubscriptionId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); + doReturn(mCarrierConfigManager).when(mContext).getSystemService(CarrierConfigManager.class); + + mCarrierConfig = new PersistableBundle(); + when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfig); + + mPreference = new ListPreference(mContext); mController = new CdmaSystemSelectPreferenceController(mContext, "mobile_data"); mController.init(mPreferenceManager, SUB_ID); @@ -101,6 +113,20 @@ public class CdmaSystemSelectPreferenceControllerTest { Integer.toString(TelephonyManager.CDMA_ROAMING_MODE_HOME)); } + @Test + public void updateState_LteGSMWcdma_disabled() { + doReturn(TelephonyManager.CDMA_ROAMING_MODE_HOME).when( + mTelephonyManager).getCdmaRoamingMode(); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.PREFERRED_NETWORK_MODE + SUB_ID, + TelephonyManager.NETWORK_MODE_LTE_GSM_WCDMA); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test public void updateState_stateOther_resetToDefault() { Settings.Global.putInt(mContext.getContentResolver(),