From e548fc6334ed407ce48615112c304fd0416020eb Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Mon, 10 Jun 2019 14:41:08 -0700 Subject: [PATCH 1/4] Remove on/off toggle for physical SIMs Our original design for mobile network subscription management included having an on/off toggle for both eSIMs and physical SIMs. However, it turns out that our current telephony stack has some problems with disabling physical SIMs, so for now we're removing the on/off toggle. Because of this, we've added a footer to the SIM details page for physical SIMS letting users know that to disable them you need to remove them from the device. Even though we're removing the on/off toggle for pSIMs, there are still a few edge cases where you could end up with a disabled one (eg having two SIMs in single-SIM mode where the eSIM is active and then you erased the eSIM). In order to have a way to re-enable the pSIM in these cases, this CL changes the relevant pref's summary to "Tap to activate " and makes the tap action begin the re-enabling. This can affect either the Mobile network pref on the Network & internet main page (if this disabled pSIM is the only SIM), or an entry in the Mobile networks list page (if there are still multiple SIMs present). Finally, this also fixes a problem where we weren't showing the on/off toggle for eSIMs if you only had one SIM total; we actually always want to show it for eSIMs. Bug: 132921553 Test: make RunSettingsRoboTests Change-Id: Id0750ebd5bed46dc2450b65b53cc81847ef09b82 --- res/xml/mobile_network_settings_v2.xml | 10 +++ .../network/MobileNetworkListController.java | 13 +++- .../MobileNetworkSummaryController.java | 21 ++++- .../settings/network/SubscriptionUtil.java | 10 ++- .../DisableSimFooterPreferenceController.java | 54 +++++++++++++ .../telephony/MobileNetworkSettings.java | 1 + .../MobileNetworkSwitchController.java | 26 +++---- .../MobileNetworkListControllerTest.java | 39 +++++++++- .../MobileNetworkSummaryControllerTest.java | 23 ++++++ ...ableSimFooterPreferenceControllerTest.java | 77 +++++++++++++++++++ .../MobileNetworkSwitchControllerTest.java | 16 +++- 11 files changed, 261 insertions(+), 29 deletions(-) create mode 100644 src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java diff --git a/res/xml/mobile_network_settings_v2.xml b/res/xml/mobile_network_settings_v2.xml index 20ed1c75a22..506b354ba42 100644 --- a/res/xml/mobile_network_settings_v2.xml +++ b/res/xml/mobile_network_settings_v2.xml @@ -203,4 +203,14 @@ android:title="@string/mobile_network_erase_sim" settings:controller="com.android.settings.network.telephony.DeleteSimProfilePreferenceController" /> + + + + diff --git a/src/com/android/settings/network/MobileNetworkListController.java b/src/com/android/settings/network/MobileNetworkListController.java index d0e14ce2168..ab41fad6236 100644 --- a/src/com/android/settings/network/MobileNetworkListController.java +++ b/src/com/android/settings/network/MobileNetworkListController.java @@ -118,14 +118,19 @@ public class MobileNetworkListController extends AbstractPreferenceController im if (mSubscriptionManager.isActiveSubscriptionId(subId)) { pref.setSummary(R.string.mobile_network_active_sim); } else { - pref.setSummary(R.string.mobile_network_inactive_sim); + pref.setSummary(mContext.getString(R.string.mobile_network_tap_to_activate, + SubscriptionUtil.getDisplayName(info))); } } pref.setOnPreferenceClickListener(clickedPref -> { - final Intent intent = new Intent(mContext, MobileNetworkActivity.class); - intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId()); - mContext.startActivity(intent); + if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)) { + mSubscriptionManager.setSubscriptionEnabled(subId, true); + } else { + final Intent intent = new Intent(mContext, MobileNetworkActivity.class); + intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId()); + mContext.startActivity(intent); + } return true; }); mPreferences.put(subId, pref); diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java index 9498c4e61e0..0f76f24de56 100644 --- a/src/com/android/settings/network/MobileNetworkSummaryController.java +++ b/src/com/android/settings/network/MobileNetworkSummaryController.java @@ -108,7 +108,14 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController } return null; } else if (subs.size() == 1) { - return subs.get(0).getDisplayName(); + final SubscriptionInfo info = subs.get(0); + final int subId = info.getSubscriptionId(); + if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)) { + return mContext.getString(R.string.mobile_network_tap_to_activate, + SubscriptionUtil.getDisplayName(info)); + } else { + return subs.get(0).getDisplayName(); + } } else { final int count = subs.size(); return mContext.getResources().getQuantityString(R.plurals.mobile_network_summary_count, @@ -154,9 +161,15 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController if (subs.size() == 1) { mPreference.setOnPreferenceClickListener((Preference pref) -> { - final Intent intent = new Intent(mContext, MobileNetworkActivity.class); - intent.putExtra(Settings.EXTRA_SUB_ID, subs.get(0).getSubscriptionId()); - mContext.startActivity(intent); + final SubscriptionInfo info = subs.get(0); + final int subId = info.getSubscriptionId(); + if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)) { + mSubscriptionManager.setSubscriptionEnabled(subId, true); + } else { + final Intent intent = new Intent(mContext, MobileNetworkActivity.class); + intent.putExtra(Settings.EXTRA_SUB_ID, subs.get(0).getSubscriptionId()); + mContext.startActivity(intent); + } return true; }); } else { diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index ebac51f3f67..f7235754f6c 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -25,12 +25,10 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccSlotInfo; -import android.text.TextUtils; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; public class SubscriptionUtil { @@ -107,4 +105,12 @@ public class SubscriptionUtil { } return subscriptions; } + + public static String getDisplayName(SubscriptionInfo info) { + final CharSequence name = info.getDisplayName(); + if (name != null) { + return name.toString(); + } + return ""; + } } diff --git a/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java b/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java new file mode 100644 index 00000000000..ab01b9d5a86 --- /dev/null +++ b/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony; + +import android.content.Context; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.network.SubscriptionUtil; + +public class DisableSimFooterPreferenceController extends BasePreferenceController { + private int mSubId; + + public DisableSimFooterPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + + public void init(int subId) { + mSubId = subId; + } + + @Override + public int getAvailabilityStatus() { + if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + return CONDITIONALLY_UNAVAILABLE; + } + for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) { + if (info.getSubscriptionId() == mSubId) { + if (info.isEmbedded()) { + return CONDITIONALLY_UNAVAILABLE; + } + break; + } + } + return AVAILABLE; + } +} diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index aa63b747ad9..c8e2247db15 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -142,6 +142,7 @@ public class MobileNetworkSettings extends RestrictedDashboardFragment { use(DisabledSubscriptionController.class).init(getLifecycle(), mSubId); use(DeleteSimProfilePreferenceController.class).init(mSubId, this, REQUEST_CODE_DELETE_SUBSCRIPTION); + use(DisableSimFooterPreferenceController.class).init(mSubId); } use(MobileDataPreferenceController.class).init(getFragmentManager(), mSubId); use(RoamingPreferenceController.class).init(getFragmentManager(), mSubId); diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java index 2037adce4b5..e6422f00d60 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java @@ -93,23 +93,23 @@ public class MobileNetworkSwitchController extends BasePreferenceController impl if (mSwitchBar == null) { return; } - final List subs = SubscriptionUtil.getAvailableSubscriptions( - mContext); - if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID || - mSubscriptionManager.isSubscriptionEnabled(mSubId) && subs.size() < 2) { - mSwitchBar.hide(); - return; - } - for (SubscriptionInfo info : subs) { + SubscriptionInfo subInfo = null; + for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) { if (info.getSubscriptionId() == mSubId) { - mSwitchBar.show(); - mSwitchBar.setChecked(mSubscriptionManager.isSubscriptionEnabled(mSubId)); - return; + subInfo = info; + break; } } - // This subscription was not found in the available list. - mSwitchBar.hide(); + + // For eSIM, we always want the toggle. The telephony stack doesn't currently support + // disabling a pSIM directly (b/133379187), so we for now we don't include this on pSIM. + if (subInfo == null || !subInfo.isEmbedded()) { + mSwitchBar.hide(); + } else { + mSwitchBar.show(); + mSwitchBar.setChecked(mSubscriptionManager.isSubscriptionEnabled(mSubId)); + } } @Override diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java index 342fed50f15..4a98753e2b0 100644 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java @@ -21,6 +21,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -32,6 +34,7 @@ import android.content.Context; import android.content.Intent; import android.provider.Settings; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccManager; @@ -54,9 +57,11 @@ import androidx.preference.PreferenceScreen; @RunWith(RobolectricTestRunner.class) public class MobileNetworkListControllerTest { @Mock - TelephonyManager mTelephonyManager; + private TelephonyManager mTelephonyManager; @Mock - EuiccManager mEuiccManager; + private EuiccManager mEuiccManager; + @Mock + private SubscriptionManager mSubscriptionManager; @Mock private Lifecycle mLifecycle; @@ -74,6 +79,7 @@ public class MobileNetworkListControllerTest { mContext = spy(Robolectric.setupActivity(Activity.class)); when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); when(mContext.getSystemService(EuiccManager.class)).thenReturn(mEuiccManager); + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1); when(mPreferenceScreen.getContext()).thenReturn(mContext); mAddMorePreference = new Preference(mContext); @@ -114,6 +120,8 @@ public class MobileNetworkListControllerTest { public void displayPreference_twoSubscriptions_correctlySetup() { final SubscriptionInfo sub1 = createMockSubscription(1, "sub1"); final SubscriptionInfo sub2 = createMockSubscription(2, "sub2"); + doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(1)); + doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(2)); SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2)); mController.displayPreference(mPreferenceScreen); mController.onResume(); @@ -138,6 +146,33 @@ public class MobileNetworkListControllerTest { assertThat(intent2.getIntExtra(EXTRA_SUB_ID, INVALID_SUBSCRIPTION_ID)).isEqualTo(2); } + @Test + public void displayPreference_oneActiveESimOneInactivePSim_correctlySetup() { + final SubscriptionInfo sub1 = createMockSubscription(1, "sub1"); + final SubscriptionInfo sub2 = createMockSubscription(2, "sub2"); + when(sub1.isEmbedded()).thenReturn(true); + doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(1)); + doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(eq(2)); + + when(sub2.isEmbedded()).thenReturn(false); + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2)); + + mController.displayPreference(mPreferenceScreen); + mController.onResume(); + + // Check that the preferences get created with the correct summaries. + final ArgumentCaptor preferenceCaptor = ArgumentCaptor.forClass( + Preference.class); + verify(mPreferenceScreen, times(2)).addPreference(preferenceCaptor.capture()); + final Preference pref1 = preferenceCaptor.getAllValues().get(0); + final Preference pref2 = preferenceCaptor.getAllValues().get(1); + assertThat(pref1.getSummary()).isEqualTo("Active / Downloaded SIM"); + assertThat(pref2.getSummary()).isEqualTo("Tap to activate sub2"); + + pref2.getOnPreferenceClickListener().onPreferenceClick(pref2); + verify(mSubscriptionManager).setSubscriptionEnabled(eq(2), eq(true)); + } + @Test public void onSubscriptionsChanged_twoSubscriptionsOneChangesName_preferenceUpdated() { final SubscriptionInfo sub1 = createMockSubscription(1, "sub1"); diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java index a6238505756..99c9134c77d 100644 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java @@ -18,6 +18,7 @@ package com.android.settings.network; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.atLeastOnce; @@ -64,6 +65,8 @@ public class MobileNetworkSummaryControllerTest { @Mock private TelephonyManager mTelephonyManager; @Mock + private SubscriptionManager mSubscriptionManager; + @Mock private EuiccManager mEuiccManager; @Mock private PreferenceScreen mPreferenceScreen; @@ -79,9 +82,11 @@ public class MobileNetworkSummaryControllerTest { MockitoAnnotations.initMocks(this); mContext = spy(Robolectric.setupActivity(Activity.class)); when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); when(mContext.getSystemService(EuiccManager.class)).thenReturn(mEuiccManager); when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); when(mTelephonyManager.getNetworkCountryIso()).thenReturn(""); + when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true); when(mEuiccManager.isEnabled()).thenReturn(true); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1); @@ -159,6 +164,24 @@ public class MobileNetworkSummaryControllerTest { SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId()); } + @Test + public void getSummary_oneInactivePSim_correctSummaryAndClickHandler() { + final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); + when(sub1.getSubscriptionId()).thenReturn(1); + when(sub1.getDisplayName()).thenReturn("sub1"); + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); + when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false); + + mController.displayPreference(mPreferenceScreen); + mController.onResume(); + + assertThat(mController.getSummary()).isEqualTo("Tap to activate sub1"); + + assertThat(mPreference.getFragment()).isNull(); + mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); + verify(mSubscriptionManager).setSubscriptionEnabled(eq(sub1.getSubscriptionId()), eq(true)); + } + @Test public void getSummary_twoSubscriptions_correctSummaryAndFragment() { final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); diff --git a/tests/robotests/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java new file mode 100644 index 00000000000..a0a4f009832 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.telephony.SubscriptionInfo; + +import com.android.settings.network.SubscriptionUtil; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.Arrays; + +@RunWith(RobolectricTestRunner.class) +public class DisableSimFooterPreferenceControllerTest { + private static final String PREF_KEY = "pref_key"; + private static final int SUB_ID = 111; + + @Mock + private SubscriptionInfo mInfo; + + private Context mContext; + private DisableSimFooterPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + when(mInfo.getSubscriptionId()).thenReturn(SUB_ID); + SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mInfo)); + mController = new DisableSimFooterPreferenceController(mContext, PREF_KEY); + } + + @Test + public void isAvailable_noInit_notAvailable() { + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_eSIM_notAvailable() { + when(mInfo.isEmbedded()).thenReturn(true); + mController.init(SUB_ID); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_pSIM_available() { + when(mInfo.isEmbedded()).thenReturn(false); + mController.init(SUB_ID); + assertThat(mController.isAvailable()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java index a10227f5924..86c380e006b 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java @@ -79,13 +79,14 @@ public class MobileNetworkSwitchControllerTest { mLifecycleOwner = () -> mLifecycle; mLifecycle = new Lifecycle(mLifecycleOwner); + when(mSubscription.isEmbedded()).thenReturn(true); when(mSubscription.getSubscriptionId()).thenReturn(mSubId); // Most tests want to have 2 available subscriptions so that the switch bar will show. - SubscriptionInfo sub2 = mock(SubscriptionInfo.class); + final SubscriptionInfo sub2 = mock(SubscriptionInfo.class); when(sub2.getSubscriptionId()).thenReturn(456); SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription, sub2)); - String key = "prefKey"; + final String key = "prefKey"; mController = new MobileNetworkSwitchController(mContext, key); mController.init(mLifecycle, mSubscription.getSubscriptionId()); @@ -100,11 +101,18 @@ public class MobileNetworkSwitchControllerTest { } @Test - public void displayPreference_oneEnabledSubscription_switchBarHidden() { + public void isAvailable_pSIM_isNotAvailable() { + when(mSubscription.isEmbedded()).thenReturn(false); + mController.displayPreference(mScreen); + assertThat(mSwitchBar.isShowing()).isFalse(); + } + + @Test + public void displayPreference_oneEnabledSubscription_switchBarNotHidden() { doReturn(true).when(mSubscriptionManager).isSubscriptionEnabled(mSubId); SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription)); mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isFalse(); + assertThat(mSwitchBar.isShowing()).isTrue(); } @Test From c26745207dccc3a93559cec38d43aa93e1ed255f Mon Sep 17 00:00:00 2001 From: Yi Jiang Date: Wed, 5 Jun 2019 13:20:42 -0700 Subject: [PATCH 2/4] Show a 'permission missing' message in Screen Attention settings. Bug: 129284701 Test: Manually verified. Test: atest AdaptiveSleepSettingsTest Change-Id: I6375929d59479a0aeb7e4aec184bd36e7f3ab33b --- res/values/strings.xml | 4 + ...aptiveSleepDetailPreferenceController.java | 8 +- .../AdaptiveSleepPreferenceController.java | 4 +- .../display/AdaptiveSleepSettings.java | 60 +++++++++++++- .../display/AdaptiveSleepSettingsTest.java | 80 +++++++++++++++++++ 5 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/display/AdaptiveSleepSettingsTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 2636e6e6573..6b7f04d5643 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2832,6 +2832,10 @@ On / Screen won\u2019t turn off if you\u2019re looking at it Off + + Camera access needed + + Tap to manage permissions for Device Personalization Services Prevents your screen from turning off if you\u2019re looking at it diff --git a/src/com/android/settings/display/AdaptiveSleepDetailPreferenceController.java b/src/com/android/settings/display/AdaptiveSleepDetailPreferenceController.java index 5ce7be5b43e..ab7465cfd3f 100644 --- a/src/com/android/settings/display/AdaptiveSleepDetailPreferenceController.java +++ b/src/com/android/settings/display/AdaptiveSleepDetailPreferenceController.java @@ -21,8 +21,11 @@ import android.content.Context; import androidx.preference.Preference; public class AdaptiveSleepDetailPreferenceController extends AdaptiveSleepPreferenceController { + private final Context mContext; + public AdaptiveSleepDetailPreferenceController(Context context, String key) { super(context, key); + mContext = context; } @Override @@ -42,6 +45,7 @@ public class AdaptiveSleepDetailPreferenceController extends AdaptiveSleepPrefer @Override public void updateState(Preference preference) { super.updateState(preference); - preference.setEnabled(super.hasSufficientPermissions); + preference.setEnabled(AdaptiveSleepPreferenceController.hasSufficientPermission( + mContext.getPackageManager())); } -} +} \ No newline at end of file diff --git a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java index e83410d3d7a..f9828d77fcd 100644 --- a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java +++ b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java @@ -29,7 +29,7 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle private static final String SYSTEM_KEY = ADAPTIVE_SLEEP; private static final int DEFAULT_VALUE = 0; - final boolean hasSufficientPermissions; + private final boolean hasSufficientPermissions; public AdaptiveSleepPreferenceController(Context context, String key) { super(context, key); @@ -71,7 +71,7 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle : UNSUPPORTED_ON_DEVICE; } - private static boolean hasSufficientPermission(PackageManager packageManager) { + static boolean hasSufficientPermission(PackageManager packageManager) { final String attentionPackage = packageManager.getAttentionServicePackageName(); return attentionPackage != null && packageManager.checkPermission( Manifest.permission.CAMERA, attentionPackage) == PackageManager.PERMISSION_GRANTED; diff --git a/src/com/android/settings/display/AdaptiveSleepSettings.java b/src/com/android/settings/display/AdaptiveSleepSettings.java index d0f2c9aa0b1..94abe1aba0d 100644 --- a/src/com/android/settings/display/AdaptiveSleepSettings.java +++ b/src/com/android/settings/display/AdaptiveSleepSettings.java @@ -16,16 +16,21 @@ package com.android.settings.display; +import static com.android.settings.display.AdaptiveSleepPreferenceController.hasSufficientPermission; import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF; import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF_KEY_INTERACTED; import android.app.settings.SettingsEnums; import android.content.Context; -import android.content.SharedPreferences; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.provider.SearchIndexableResource; -import android.util.Log; +import androidx.preference.Preference; + +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; @@ -39,23 +44,45 @@ import java.util.List; public class AdaptiveSleepSettings extends DashboardFragment { private static final String TAG = "AdaptiveSleepSettings"; + private Context mContext; + private String mPackageName; + private PackageManager mPackageManager; + + @VisibleForTesting + Preference mPermissionRequiredPreference; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); final FooterPreference footerPreference = mFooterPreferenceMixin.createFooterPreference(); - final Context context = getContext(); + mContext = getContext(); + mPermissionRequiredPreference = createPermissionMissionPreference(); footerPreference.setIcon(R.drawable.ic_privacy_shield_24dp); footerPreference.setTitle(R.string.adaptive_sleep_privacy); - context.getSharedPreferences(PREF, Context.MODE_PRIVATE) + getPreferenceScreen().addPreference(mPermissionRequiredPreference); + mPermissionRequiredPreference.setVisible(false); + mPackageManager = mContext.getPackageManager(); + mPackageName = mPackageManager.getAttentionServicePackageName(); + mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE) .edit() .putBoolean(PREF_KEY_INTERACTED, true) .apply(); } + @Override + public void onResume() { + super.onResume(); + if (!hasSufficientPermission(mPackageManager)) { + mPermissionRequiredPreference.setVisible(true); + } + else { + mPermissionRequiredPreference.setVisible(false); + } + } + @Override protected int getPreferenceScreenResId() { return R.xml.adaptive_sleep_detail; @@ -86,4 +113,29 @@ public class AdaptiveSleepSettings extends DashboardFragment { return Arrays.asList(sir); } }; + + private Preference createPermissionMissionPreference() { + Preference preference = new Preference(mContext, null); + preference.setIcon(R.drawable.ic_info_outline_24); + // Makes sure it's above the toggle. + preference.setOrder(1); + preference.setPersistent(true); + preference.setTitle(R.string.adaptive_sleep_title_no_permission); + preference.setSummary(R.string.adaptive_sleep_summary_no_permission); + preference.setOnPreferenceClickListener(p -> { + final Intent intent = new Intent( + android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + mPackageName)); + mContext.startActivity(intent); + return true; + }); + return preference; + } + + @VisibleForTesting + void setupForTesting(PackageManager packageManager, Context context) { + mContext = context; + mPackageManager = packageManager; + mPermissionRequiredPreference = createPermissionMissionPreference(); + } } diff --git a/tests/robotests/src/com/android/settings/display/AdaptiveSleepSettingsTest.java b/tests/robotests/src/com/android/settings/display/AdaptiveSleepSettingsTest.java new file mode 100644 index 00000000000..971ebc52d67 --- /dev/null +++ b/tests/robotests/src/com/android/settings/display/AdaptiveSleepSettingsTest.java @@ -0,0 +1,80 @@ +/* + * 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.display; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; + +import androidx.preference.PreferenceScreen; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AdaptiveSleepSettingsTest { + private AdaptiveSleepSettings mSettings; + private static final String PACKAGE_NAME = "package_name"; + @Mock + private PackageManager mPackageManager; + @Mock + private PreferenceScreen mScreen; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Context context = RuntimeEnvironment.application; + mSettings = spy(new AdaptiveSleepSettings()); + + doReturn(PACKAGE_NAME).when(mPackageManager).getAttentionServicePackageName(); + doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission( + Manifest.permission.CAMERA, PACKAGE_NAME); + doReturn(mScreen).when(mSettings).getPreferenceScreen(); + + mSettings.setupForTesting(mPackageManager, context); + mSettings.onAttach(context); + + } + + @Test + public void onResume_hasPermission_preferenceInvisible() { + mSettings.onResume(); + + assertThat(mSettings.mPermissionRequiredPreference.isVisible()).isFalse(); + } + + @Test + public void onResume_noPermission_preferenceVisible() { + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + Manifest.permission.CAMERA, PACKAGE_NAME); + + mSettings.onResume(); + + assertThat(mSettings.mPermissionRequiredPreference.isVisible()).isTrue(); + } +} From 308a53285245d9f70e96aa24bf0df575865cc1b8 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 10 Jun 2019 14:40:32 -0700 Subject: [PATCH 3/4] Update mainline module versions string. We are putting this particular Google string into AOSP because: 1. This feature is part of platform mainline and has a hard dependency to Google Play. 2. Settings code has no way to target gms devices, or mainline devices. It's either AOSP or OEM. And this change is beyond OEM. Fixes: 134101529 Test: manual Change-Id: Ic4e7a7539230db0390bb9230931132e5500a1eb4 --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index b2306deb380..e0a490705e7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3075,8 +3075,8 @@ Kernel version Build number - - Mainline module versions + + Google Play system update Not available From 564fcb90ab6c27aaa125de95a442254318342e30 Mon Sep 17 00:00:00 2001 From: tmfang Date: Tue, 11 Jun 2019 13:56:56 +0800 Subject: [PATCH 4/4] Fix overlapping problem on notification screen If we didn't set view and lifecycle for EntityHeaderController, it only sets "z-order" as 0 for action bar. So, user sees overlapped problem while user is scrolling view. For now, we set view and lifecycle as parameter for EntityHeaderController, then EntityHeaderController can change "z-order" while scrolling view. Test: visual, robotest Fixes: 132819126 Change-Id: Ieb01b26e4d6ca4d82f72371620938665de8149a0 --- .../HeaderPreferenceController.java | 23 +++++++++++-------- .../HeaderPreferenceControllerTest.java | 5 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/com/android/settings/notification/HeaderPreferenceController.java b/src/com/android/settings/notification/HeaderPreferenceController.java index d94211384d7..be5f45eacca 100644 --- a/src/com/android/settings/notification/HeaderPreferenceController.java +++ b/src/com/android/settings/notification/HeaderPreferenceController.java @@ -25,13 +25,14 @@ import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.view.View; +import androidx.annotation.VisibleForTesting; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.dashboard.DashboardFragment; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.widget.LayoutPreference; @@ -39,11 +40,11 @@ import com.android.settingslib.widget.LayoutPreference; public class HeaderPreferenceController extends NotificationPreferenceController implements PreferenceControllerMixin, LifecycleObserver { - private final PreferenceFragmentCompat mFragment; + private final DashboardFragment mFragment; private EntityHeaderController mHeaderController; private boolean mStarted = false; - public HeaderPreferenceController(Context context, PreferenceFragmentCompat fragment) { + public HeaderPreferenceController(Context context, DashboardFragment fragment) { super(context, null); mFragment = fragment; } @@ -83,18 +84,12 @@ public class HeaderPreferenceController extends NotificationPreferenceController .setButtonActions(EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE, EntityHeaderController.ActionType.ACTION_NONE) .setHasAppInfoLink(true) + .setRecyclerView(mFragment.getListView(), mFragment.getSettingsLifecycle()) .done(activity, mContext); pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE); } } - CharSequence getLabel() { - return (mChannel != null && !isDefaultChannel()) ? mChannel.getName() - : mChannelGroup != null - ? mChannelGroup.getName() - : mAppRow.label; - } - @Override public CharSequence getSummary() { if (mChannel != null && !isDefaultChannel()) { @@ -124,4 +119,12 @@ public class HeaderPreferenceController extends NotificationPreferenceController mHeaderController.styleActionBar(mFragment.getActivity()); } } + + @VisibleForTesting + CharSequence getLabel() { + return (mChannel != null && !isDefaultChannel()) ? mChannel.getName() + : mChannelGroup != null + ? mChannelGroup.getName() + : mAppRow.label; + } } diff --git a/tests/robotests/src/com/android/settings/notification/HeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/HeaderPreferenceControllerTest.java index de84f0755c3..7f6ecaefc67 100644 --- a/tests/robotests/src/com/android/settings/notification/HeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/HeaderPreferenceControllerTest.java @@ -21,7 +21,6 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; - import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -35,8 +34,8 @@ import android.os.UserManager; import android.view.View; import androidx.fragment.app.FragmentActivity; -import androidx.preference.PreferenceFragmentCompat; +import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.widget.LayoutPreference; import org.junit.Before; @@ -70,7 +69,7 @@ public class HeaderPreferenceControllerTest { shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; - PreferenceFragmentCompat fragment = mock(PreferenceFragmentCompat.class); + DashboardFragment fragment = mock(DashboardFragment.class); when(fragment.getContext()).thenReturn(mContext); FragmentActivity activity = mock(FragmentActivity.class); when(activity.getApplicationContext()).thenReturn(mContext);