From 59adc3177303e71dbc44a9a6f33a5bd920755e99 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Mon, 28 Jan 2019 16:10:58 -0800 Subject: [PATCH] Add a plus button to the mobile pref on Network & internet page On the Network & internet page, we have a "Mobile network" pref that in single-SIM mode leads to a detail page for the current SIM. In multi-SIM mode it has more complicated behavior (leading either to details about the current subscription if there is only one, or a list of subscriptions if more than one). One of the things we wanted to add was a shortcut to add another eSIM subscription. So this CL adds a plus button control on the right of the preference which leads to a flow to add another mobile subscription via the eSIM manager. Bug: 116349402 Test: make RunSettingsRoboTests Change-Id: I38e0031e3bd603e93c45dcb4557750e7bc1b8b5a --- res/layout/preference_widget_add.xml | 30 ++++ res/values/strings.xml | 2 + res/xml/mobile_network_list.xml | 4 +- res/xml/network_and_internet_v2.xml | 2 +- .../MobileNetworkSummaryController.java | 86 ++++++---- .../settings/widget/AddPreference.java | 85 ++++++++++ .../MobileNetworkSummaryControllerTest.java | 98 ++++++++++-- .../settings/widget/AddPreferenceTest.java | 149 ++++++++++++++++++ 8 files changed, 411 insertions(+), 45 deletions(-) create mode 100644 res/layout/preference_widget_add.xml create mode 100644 src/com/android/settings/widget/AddPreference.java create mode 100644 tests/robotests/src/com/android/settings/widget/AddPreferenceTest.java diff --git a/res/layout/preference_widget_add.xml b/res/layout/preference_widget_add.xml new file mode 100644 index 00000000000..6e33a4d0ec8 --- /dev/null +++ b/res/layout/preference_widget_add.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 36fba35d2ee..29cdffa763a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -658,6 +658,8 @@ Apply Share + + Add Settings diff --git a/res/xml/mobile_network_list.xml b/res/xml/mobile_network_list.xml index 81704e5592b..5bffa2ff60f 100644 --- a/res/xml/mobile_network_list.xml +++ b/res/xml/mobile_network_list.xml @@ -23,6 +23,8 @@ android:key="add_more" android:title="@string/mobile_network_list_add_more" android:icon="@drawable/ic_menu_add" - android:order="100" /> + android:order="100" > + + diff --git a/res/xml/network_and_internet_v2.xml b/res/xml/network_and_internet_v2.xml index 83499d7206a..fd29c4141c9 100644 --- a/res/xml/network_and_internet_v2.xml +++ b/res/xml/network_and_internet_v2.xml @@ -39,7 +39,7 @@ android:targetClass="Settings$WifiSettingsActivity" /> - subs = SubscriptionUtil.getAvailableSubscriptions( - mSubscriptionManager); + private void startAddSimFlow() { + final Intent intent = new Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION); + mContext.startActivity(intent); + } - preference.setOnPreferenceClickListener(null); - preference.setFragment(null); - if (subs.size() == 0) { - preference.setOnPreferenceClickListener((Preference pref) -> { - // TODO(asargent) - need to get correct intent to fire here - return true; - }); - } else if (subs.size() == 1) { - preference.setOnPreferenceClickListener((Preference pref) -> { - final Intent intent = new Intent(mContext, MobileNetworkActivity.class); - mContext.startActivity(intent); - return true; - }); + private boolean shouldEnableAddButton() { + // The add button should only show up if the device is in multi-sim mode. + return mTelephonyMgr.getMultiSimConfiguration() != UNKNOWN; + } + + private void update() { + if (mPreference == null) { + return; + } + final boolean enableAddButton = shouldEnableAddButton(); + refreshSummary(mPreference); + if (!enableAddButton) { + mPreference.setOnAddClickListener(null); + } else { + mPreference.setOnAddClickListener(p -> { + startAddSimFlow(); + }); + } + final List subs = SubscriptionUtil.getAvailableSubscriptions( + mSubscriptionManager); + mPreference.setOnPreferenceClickListener(null); + mPreference.setFragment(null); + mPreference.setEnabled(true); + if (subs.isEmpty()) { + if (enableAddButton) { + mPreference.setEnabled(false); } else { - preference.setFragment(MobileNetworkListFragment.class.getCanonicalName()); + mPreference.setOnPreferenceClickListener((Preference pref) -> { + startAddSimFlow(); + return true; + }); } + } else if (subs.size() == 1) { + mPreference.setOnPreferenceClickListener((Preference pref) -> { + final Intent intent = new Intent(mContext, MobileNetworkActivity.class); + mContext.startActivity(intent); + return true; + }); + } else { + mPreference.setFragment(MobileNetworkListFragment.class.getCanonicalName()); } } @@ -142,6 +171,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController @Override public void onSubscriptionsChanged() { + refreshSummary(mPreference); update(); } } diff --git a/src/com/android/settings/widget/AddPreference.java b/src/com/android/settings/widget/AddPreference.java new file mode 100644 index 00000000000..ce36ab35f48 --- /dev/null +++ b/src/com/android/settings/widget/AddPreference.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import com.android.settings.R; +import com.android.settingslib.RestrictedPreference; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceViewHolder; + +/** + * A preference with a plus button on the side representing an "add" action. The plus button will + * only be visible when a non-null click listener is registered. + */ +public class AddPreference extends RestrictedPreference implements View.OnClickListener { + + private OnAddClickListener mListener; + private View mWidgetFrame; + + public AddPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @VisibleForTesting + int getAddWidgetResId() { + return R.id.add_preference_widget; + } + + /** Sets a listener for clicks on the plus button. Passing null will cause the button to be + * hidden. */ + public void setOnAddClickListener(OnAddClickListener listener) { + mListener = listener; + if (mWidgetFrame != null) { + mWidgetFrame.setVisibility(shouldHideSecondTarget() ? View.GONE : View.VISIBLE); + } + } + + @Override + protected int getSecondTargetResId() { + return R.layout.preference_widget_add; + } + + @Override + protected boolean shouldHideSecondTarget() { + return mListener == null; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + mWidgetFrame = holder.findViewById(android.R.id.widget_frame); + final View addWidget = holder.findViewById(getAddWidgetResId()); + addWidget.setEnabled(true); + addWidget.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + if (view.getId() == getAddWidgetResId() && mListener != null) { + mListener.onAddClick(this); + } + } + + public interface OnAddClickListener { + void onAddClick(AddPreference p); + } +} diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java index f0c012de52e..6a1abd1d46b 100644 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java @@ -16,10 +16,16 @@ package com.android.settings.network; +import static android.telephony.TelephonyManager.MultiSimVariants.DSDS; +import static android.telephony.TelephonyManager.MultiSimVariants.UNKNOWN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.notNull; 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; @@ -28,8 +34,11 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyManager; +import android.telephony.euicc.EuiccManager; import com.android.settings.network.telephony.MobileNetworkActivity; +import com.android.settings.widget.AddPreference; import org.junit.After; import org.junit.Before; @@ -44,7 +53,6 @@ import org.robolectric.RobolectricTestRunner; import java.util.Arrays; import androidx.lifecycle.Lifecycle; -import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @RunWith(RobolectricTestRunner.class) @@ -52,10 +60,12 @@ public class MobileNetworkSummaryControllerTest { @Mock private Lifecycle mLifecycle; @Mock - PreferenceScreen mPreferenceScreen; + private TelephonyManager mTelephonyManager; - Preference mPreference; + @Mock + private PreferenceScreen mPreferenceScreen; + private AddPreference mPreference; private Context mContext; private MobileNetworkSummaryController mController; @@ -63,8 +73,11 @@ public class MobileNetworkSummaryControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(Robolectric.setupActivity(Activity.class)); + when(mContext.getSystemService(eq(TelephonyManager.class))).thenReturn(mTelephonyManager); + when(mTelephonyManager.getMultiSimConfiguration()).thenReturn(UNKNOWN); + mController = new MobileNetworkSummaryController(mContext, mLifecycle); - mPreference = new Preference(mContext); + mPreference = spy(new AddPreference(mContext, null)); mPreference.setKey(mController.getPreferenceKey()); when(mPreferenceScreen.findPreference(eq(mController.getPreferenceKey()))).thenReturn( mPreference); @@ -76,15 +89,21 @@ public class MobileNetworkSummaryControllerTest { } @Test - public void getSummary_noSubscriptions_correctSummary() { + public void getSummary_noSubscriptions_correctSummaryAndClickHandler() { mController.displayPreference(mPreferenceScreen); mController.onResume(); assertThat(mController.getSummary()).isEqualTo("Add a network"); + + mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).startActivity(intentCaptor.capture()); + assertThat(intentCaptor.getValue().getAction()).isEqualTo( + EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION); } @Test public void getSummary_oneSubscription_correctSummaryAndClickHandler() { - SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); when(sub1.getSubscriptionId()).thenReturn(1); when(sub1.getDisplayName()).thenReturn("sub1"); SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); @@ -93,7 +112,7 @@ public class MobileNetworkSummaryControllerTest { assertThat(mController.getSummary()).isEqualTo("sub1"); assertThat(mPreference.getFragment()).isNull(); mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext).startActivity(intentCaptor.capture()); assertThat(intentCaptor.getValue().getComponent().getClassName()).isEqualTo( MobileNetworkActivity.class.getName()); @@ -101,8 +120,8 @@ public class MobileNetworkSummaryControllerTest { @Test public void getSummary_twoSubscriptions_correctSummaryAndFragment() { - SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionInfo sub2 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub2 = mock(SubscriptionInfo.class); when(sub1.getSubscriptionId()).thenReturn(1); when(sub2.getSubscriptionId()).thenReturn(2); @@ -115,8 +134,8 @@ public class MobileNetworkSummaryControllerTest { @Test public void getSummaryAfterUpdate_twoSubscriptionsBecomesOne_correctSummaryAndFragment() { - SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionInfo sub2 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub2 = mock(SubscriptionInfo.class); when(sub1.getSubscriptionId()).thenReturn(1); when(sub2.getSubscriptionId()).thenReturn(2); when(sub1.getDisplayName()).thenReturn("sub1"); @@ -135,7 +154,7 @@ public class MobileNetworkSummaryControllerTest { assertThat(mController.getSummary()).isEqualTo("sub1"); assertThat(mPreference.getFragment()).isNull(); mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext).startActivity(intentCaptor.capture()); assertThat(intentCaptor.getValue().getComponent().getClassName()).isEqualTo( MobileNetworkActivity.class.getName()); @@ -143,8 +162,8 @@ public class MobileNetworkSummaryControllerTest { @Test public void getSummaryAfterUpdate_oneSubscriptionBecomesTwo_correctSummaryAndFragment() { - SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionInfo sub2 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub2 = mock(SubscriptionInfo.class); when(sub1.getSubscriptionId()).thenReturn(1); when(sub2.getSubscriptionId()).thenReturn(2); when(sub1.getDisplayName()).thenReturn("sub1"); @@ -156,7 +175,7 @@ public class MobileNetworkSummaryControllerTest { assertThat(mController.getSummary()).isEqualTo("sub1"); assertThat(mPreference.getFragment()).isNull(); mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext).startActivity(intentCaptor.capture()); assertThat(intentCaptor.getValue().getComponent().getClassName()).isEqualTo( MobileNetworkActivity.class.getName()); @@ -168,4 +187,53 @@ public class MobileNetworkSummaryControllerTest { assertThat(mController.getSummary()).isEqualTo("2 SIMs"); assertThat(mPreference.getFragment()).isEqualTo(MobileNetworkListFragment.class.getName()); } + + @Test + public void addButton_noSubscriptionsSingleSimMode_noAddClickListener() { + mController.displayPreference(mPreferenceScreen); + mController.onResume(); + verify(mPreference, never()).setOnAddClickListener(notNull()); + } + + @Test + public void addButton_oneSubscriptionSingleSimMode_noAddClickListener() { + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); + mController.displayPreference(mPreferenceScreen); + mController.onResume(); + verify(mPreference, never()).setOnAddClickListener(notNull()); + } + + @Test + public void addButton_noSubscriptionsMultiSimMode_hasAddClickListenerAndPrefDisabled() { + when(mTelephonyManager.getMultiSimConfiguration()).thenReturn(DSDS); + mController.displayPreference(mPreferenceScreen); + mController.onResume(); + assertThat(mPreference.isEnabled()).isFalse(); + verify(mPreference, never()).setOnAddClickListener(isNull()); + verify(mPreference).setOnAddClickListener(notNull()); + } + + @Test + public void addButton_oneSubscriptionMultiSimMode_hasAddClickListener() { + when(mTelephonyManager.getMultiSimConfiguration()).thenReturn(DSDS); + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); + mController.displayPreference(mPreferenceScreen); + mController.onResume(); + verify(mPreference, never()).setOnAddClickListener(isNull()); + verify(mPreference).setOnAddClickListener(notNull()); + } + + @Test + public void addButton_twoSubscriptionsMultiSimMode_hasAddClickListener() { + when(mTelephonyManager.getMultiSimConfiguration()).thenReturn(DSDS); + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub2 = mock(SubscriptionInfo.class); + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2)); + mController.displayPreference(mPreferenceScreen); + mController.onResume(); + verify(mPreference, never()).setOnAddClickListener(isNull()); + verify(mPreference).setOnAddClickListener(notNull()); + } } diff --git a/tests/robotests/src/com/android/settings/widget/AddPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/AddPreferenceTest.java new file mode 100644 index 00000000000..3fccb3406f5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/widget/AddPreferenceTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.view.View; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import androidx.preference.PreferenceViewHolder; + +@RunWith(RobolectricTestRunner.class) +public class AddPreferenceTest { + + private Context mContext; + private PreferenceViewHolder mViewHolder; + private View mWidgetFrame; + private View mAddWidget; + private AddPreference mPreference; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mPreference = new AddPreference(mContext, null); + + final View view = spy(View.inflate(mContext, mPreference.getLayoutResource(), null)); + mViewHolder = PreferenceViewHolder.createInstanceForTests(view); + mWidgetFrame = view.findViewById(android.R.id.widget_frame); + mAddWidget = spy(View.inflate(mContext, mPreference.getSecondTargetResId(), null)); + when(mViewHolder.findViewById(mPreference.getAddWidgetResId())).thenReturn(mAddWidget); + } + + @Test + public void onBindViewHolder_noListener_addButtonNotVisible() { + mPreference.onBindViewHolder(mViewHolder); + assertThat(mPreference.shouldHideSecondTarget()).isTrue(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_hasListener_addButtonVisible() { + mPreference.setOnAddClickListener(p -> {}); + mPreference.onBindViewHolder(mViewHolder); + assertThat(mPreference.shouldHideSecondTarget()).isFalse(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void setOnAddClickListener_listenerAddedAfterBinding_addButtonBecomesVisible() { + mPreference.onBindViewHolder(mViewHolder); + assertThat(mPreference.shouldHideSecondTarget()).isTrue(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE); + + mPreference.setOnAddClickListener(p -> {}); + assertThat(mPreference.shouldHideSecondTarget()).isFalse(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void setOnAddClickListener_listenerRemovedAfterBinding_addButtonNotVisible() { + mPreference.setOnAddClickListener(p -> {}); + + mPreference.onBindViewHolder(mViewHolder); + assertThat(mPreference.shouldHideSecondTarget()).isFalse(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE); + + mPreference.setOnAddClickListener(null); + assertThat(mPreference.shouldHideSecondTarget()).isTrue(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void setOnAddClickListener_listenerAddedAndRemovedAfterBinding_addButtonNotVisible() { + mPreference.onBindViewHolder(mViewHolder); + assertThat(mPreference.shouldHideSecondTarget()).isTrue(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE); + + mPreference.setOnAddClickListener(p -> {}); + assertThat(mPreference.shouldHideSecondTarget()).isFalse(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE); + + mPreference.setOnAddClickListener(null); + assertThat(mPreference.shouldHideSecondTarget()).isTrue(); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onClick_noListener_noCrash() { + mPreference.onBindViewHolder(mViewHolder); + // should be no crash here + mPreference.onClick(mAddWidget); + } + + @Test + public void onClick_hasListenerBeforeBind_firesCorrectly() { + final AddPreference.OnAddClickListener listener = mock( + AddPreference.OnAddClickListener.class); + mPreference.setOnAddClickListener(listener); + + mPreference.onBindViewHolder(mViewHolder); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE); + + mPreference.onClick(mAddWidget); + verify(listener).onAddClick(eq(mPreference)); + } + + @Test + public void onClick_listenerAddedAfterBind_firesCorrectly() { + mPreference.onBindViewHolder(mViewHolder); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE); + + final AddPreference.OnAddClickListener listener = mock( + AddPreference.OnAddClickListener.class); + mPreference.setOnAddClickListener(listener); + assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE); + + mPreference.onClick(mAddWidget); + verify(listener).onAddClick(eq(mPreference)); + } +}