diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index 90e3ac43fd8..c2881ac88f1 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -103,8 +103,10 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment implements Indexable, WifiPickerTracker.WifiPickerTrackerCallback, WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener { - private static final String TAG = "NetworkProviderSettings"; + public static final String ACTION_NETWORK_PROVIDER_SETTINGS = + "android.settings.NETWORK_PROVIDER_SETTINGS"; + private static final String TAG = "NetworkProviderSettings"; // IDs of context menu static final int MENU_ID_CONNECT = Menu.FIRST + 1; @VisibleForTesting diff --git a/src/com/android/settings/network/ProviderModelSlice.java b/src/com/android/settings/network/ProviderModelSlice.java new file mode 100644 index 00000000000..f6908c9e6c1 --- /dev/null +++ b/src/com/android/settings/network/ProviderModelSlice.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2020 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; + + +import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; + +import static com.android.settings.slices.CustomSliceRegistry.PROVIDER_MODEL_SLICE_URI; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.telephony.SubscriptionManager; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; + +import com.android.settings.R; +import com.android.settings.SubSettings; +import com.android.settings.network.telephony.MobileNetworkUtils; +import com.android.settings.network.telephony.NetworkProviderWorker; +import com.android.settings.slices.CustomSliceable; +import com.android.settings.slices.SliceBackgroundWorker; +import com.android.settings.slices.SliceBuilderUtils; +import com.android.settings.wifi.slice.WifiSlice; +import com.android.settings.wifi.slice.WifiSliceItem; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * {@link CustomSliceable} for Wi-Fi and mobile data connection, used by generic clients. + */ +// ToDo If the provider model become default design in the future, the code needs to refactor +// the whole structure and use new "data object", and then split provider model out of old design. +public class ProviderModelSlice extends WifiSlice { + + private static final String TAG = "ProviderModelSlice"; + private final ProviderModelSliceHelper mHelper; + + public ProviderModelSlice(Context context) { + super(context); + mHelper = getHelper(); + } + + @Override + public Uri getUri() { + return PROVIDER_MODEL_SLICE_URI; + } + + private static void log(String s) { + Log.d(TAG, s); + } + + protected boolean isApRowCollapsed() { + return false; + } + + @Override + public Slice getSlice() { + // The provider model slice step: + // First section: Add a Wi-Fi item which state is connected. + // Second section: Add a carrier item. + // Third section: Add the Wi-Fi items which are not connected. + // Fourth section: If device has connection problem, this row show the message for user. + + if (mHelper.isAirplaneModeEnabled()) { + log("Airplane mode is enabled."); + // ToDo Next CL will add the Airplane mode Message. + return mHelper.createListBuilder(getUri()).build(); + } + + int maxListSize = 0; + List wifiList = null; + final NetworkProviderWorker worker = getWorker(); + if (worker != null) { + // get Wi-Fi list. + wifiList = worker.getResults(); + maxListSize = worker.getApRowCount(); + } else { + log("network provider worker is null."); + } + + final boolean hasCarrier = mHelper.hasCarrier(); + log("hasCarrier: " + hasCarrier); + + + final ListBuilder listBuilder = mHelper.createListBuilder(getUri()); + + // First section: Add a Wi-Fi item which state is connected. + final WifiSliceItem connectedWifiItem = mHelper.getConnectedWifiItem(wifiList); + if (connectedWifiItem != null) { + log("get Wi-Fi item witch is connected"); + listBuilder.addRow(getWifiSliceItemRow(connectedWifiItem)); + maxListSize--; + } + + // Second section: Add a carrier item. + if (hasCarrier) { + listBuilder.addRow(mHelper.createCarrierRow()); + maxListSize--; + } + + // Third section: Add the Wi-Fi items which are not connected. + if (wifiList != null) { + log("get Wi-Fi items which are not connected"); + final List disconnectedWifiList = wifiList.stream() + .filter(wifiSliceItem -> wifiSliceItem.getConnectedState() + != WifiEntry.CONNECTED_STATE_CONNECTED) + .limit(maxListSize) + .collect(Collectors.toList()); + for (WifiSliceItem item : disconnectedWifiList) { + listBuilder.addRow(getWifiSliceItemRow(item)); + } + } + + // Fourth section: If device has connection problem, this row show the message for user. + // 1) show non_carrier_network_unavailable: + // - while no wifi item + // 2) show all_network_unavailable: + // - while no wifi item + no carrier + // - while no wifi item + no data capability + if (worker == null || wifiList == null) { + log("wifiList is null"); + int resId = R.string.non_carrier_network_unavailable; + if (!hasCarrier || mHelper.isNoCarrierData()) { + log("No carrier item or no carrier data."); + resId = R.string.all_network_unavailable; + } + + if (!hasCarrier) { + // If there is no item in ProviderModelItem, slice needs a header. + listBuilder.setHeader(mHelper.createHeader()); + } + listBuilder.addGridRow(mHelper.createMessageGridRow(resId)); + } + + return listBuilder.build(); + } + + /** + * Update the current carrier's mobile data status. + */ + @Override + public void onNotifyChange(Intent intent) { + final SubscriptionManager subscriptionManager = mHelper.getSubscriptionManager(); + if (subscriptionManager == null) { + return; + } + final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, + mHelper.isMobileDataEnabled()); + final int defaultSubId = subscriptionManager.getDefaultDataSubscriptionId(); + log("defaultSubId:" + defaultSubId); + if (!SubscriptionManager.isUsableSubscriptionId(defaultSubId)) { + return; // No subscription - do nothing. + } + + MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, + false /* disableOtherSubscriptions */); + } + + @Override + public Intent getIntent() { + final String screenTitle = mContext.getText(R.string.provider_internet_settings).toString(); + return SliceBuilderUtils.buildSearchResultPageIntent(mContext, + NetworkProviderSettings.class.getName(), "" /* key */, screenTitle, + SettingsEnums.SLICE) + .setClassName(mContext.getPackageName(), SubSettings.class.getName()) + .setData(getUri()); + } + + @Override + public Class getBackgroundWorkerClass() { + return NetworkProviderWorker.class; + } + + @VisibleForTesting + ProviderModelSliceHelper getHelper() { + return new ProviderModelSliceHelper(mContext, this); + } + + @VisibleForTesting + NetworkProviderWorker getWorker() { + return SliceBackgroundWorker.getInstance(getUri()); + } +} diff --git a/src/com/android/settings/network/telephony/NetworkProviderWorker.java b/src/com/android/settings/network/telephony/NetworkProviderWorker.java new file mode 100644 index 00000000000..bc82901d3e7 --- /dev/null +++ b/src/com/android/settings/network/telephony/NetworkProviderWorker.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2020 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.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.android.settings.network.MobileDataContentObserver; +import com.android.settings.network.MobileDataEnabledListener; +import com.android.settings.network.SubscriptionsChangeListener; +import com.android.settings.wifi.slice.WifiScanWorker; + +import java.util.Collections; +import java.util.concurrent.Executor; + + +/** + * BackgroundWorker for Provider Model slice. + */ +public class NetworkProviderWorker extends WifiScanWorker implements + SignalStrengthListener.Callback, MobileDataEnabledListener.Client, + DataConnectivityListener.Client, + SubscriptionsChangeListener.SubscriptionsChangeListenerClient { + private static final String TAG = "NetworkProviderWorker"; + private static final int PROVIDER_MODEL_DEFAULT_EXPANDED_ROW_COUNT = 4; + private DataContentObserver mMobileDataObserver; + private SignalStrengthListener mSignalStrengthListener; + private SubscriptionsChangeListener mSubscriptionsListener; + private MobileDataEnabledListener mDataEnabledListener; + private DataConnectivityListener mConnectivityListener; + + private final Context mContext; + @VisibleForTesting + final PhoneStateListener mPhoneStateListener; + private final SubscriptionManager mSubscriptionManager; + private final TelephonyManager mTelephonyManager; + + public NetworkProviderWorker(Context context, Uri uri) { + super(context, uri); + // Mobile data worker + final Handler handler = new Handler(Looper.getMainLooper()); + mMobileDataObserver = new DataContentObserver(handler, this); + + mContext = context; + mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + + mPhoneStateListener = new NetworkProviderPhoneStateListener(handler::post); + mSubscriptionsListener = new SubscriptionsChangeListener(context, this); + mDataEnabledListener = new MobileDataEnabledListener(context, this); + mConnectivityListener = new DataConnectivityListener(context, this); + mSignalStrengthListener = new SignalStrengthListener(context, this); + } + + @Override + protected void onSlicePinned() { + mMobileDataObserver.register(mContext, + getDefaultSubscriptionId(mSubscriptionManager)); + + mSubscriptionsListener.start(); + mDataEnabledListener.start(SubscriptionManager.getDefaultDataSubscriptionId()); + mConnectivityListener.start(); + mSignalStrengthListener.resume(); + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE + | PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE + | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED); + + super.onSlicePinned(); + } + + @Override + protected void onSliceUnpinned() { + mMobileDataObserver.unregister(mContext); + mSubscriptionsListener.stop(); + mDataEnabledListener.stop(); + mConnectivityListener.stop(); + mSignalStrengthListener.pause(); + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + super.onSliceUnpinned(); + } + + @Override + public void close() { + mMobileDataObserver = null; + super.close(); + } + + @Override + public int getApRowCount() { + return PROVIDER_MODEL_DEFAULT_EXPANDED_ROW_COUNT; + } + + /** + * To update the Slice. + */ + public void updateSlice() { + notifySliceChange(); + } + + @Override + public void onSubscriptionsChanged() { + int defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId(); + Log.d(TAG, "onSubscriptionsChanged: defaultDataSubId:" + defaultDataSubId); + + mSignalStrengthListener.updateSubscriptionIds( + SubscriptionManager.isUsableSubscriptionId(defaultDataSubId) + ? Collections.singleton(defaultDataSubId) : Collections.emptySet()); + if (defaultDataSubId != mDataEnabledListener.getSubId()) { + mDataEnabledListener.stop(); + mDataEnabledListener.start(defaultDataSubId); + } + updateSlice(); + } + + @Override + public void onSignalStrengthChanged() { + Log.d(TAG, "onSignalStrengthChanged"); + updateSlice(); + } + + @Override + public void onAirplaneModeChanged(boolean airplaneModeEnabled) { + Log.d(TAG, "onAirplaneModeChanged"); + updateSlice(); + } + + @Override + public void onMobileDataEnabledChange() { + Log.d(TAG, "onMobileDataEnabledChange"); + updateSlice(); + } + + @Override + public void onDataConnectivityChange() { + Log.d(TAG, "onDataConnectivityChange"); + updateSlice(); + } + + /** + * Listen to update of mobile data change. + */ + public class DataContentObserver extends ContentObserver { + private final NetworkProviderWorker mNetworkProviderWorker; + + public DataContentObserver(Handler handler, NetworkProviderWorker backgroundWorker) { + super(handler); + mNetworkProviderWorker = backgroundWorker; + } + + @Override + public void onChange(boolean selfChange) { + mNetworkProviderWorker.updateSlice(); + } + + /** + * To register the observer for mobile data changed. + * @param context the Context object. + * @param subId the default data subscription id. + */ + public void register(Context context, int subId) { + final Uri uri = MobileDataContentObserver.getObservableUri(context, subId); + context.getContentResolver().registerContentObserver(uri, false, this); + } + + /** + * To unregister the observer for mobile data changed. + * @param context the Context object. + */ + public void unregister(Context context) { + context.getContentResolver().unregisterContentObserver(this); + } + } + + class NetworkProviderPhoneStateListener extends PhoneStateListener { + NetworkProviderPhoneStateListener(Executor executor) { + super(executor); + } + + @Override + public void onServiceStateChanged(ServiceState state) { + Log.d(TAG, "onServiceStateChanged voiceState=" + state.getState() + + " dataState=" + state.getDataRegistrationState()); + updateSlice(); + } + + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + Log.d(TAG, "onActiveDataSubscriptionIdChanged: subId=" + subId); + updateSlice(); + } + + @Override + public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { + Log.d(TAG, "onDisplayInfoChanged: telephonyDisplayInfo=" + telephonyDisplayInfo); + updateSlice(); + } + } + + protected static int getDefaultSubscriptionId(SubscriptionManager subscriptionManager) { + final SubscriptionInfo defaultSubscription = subscriptionManager.getActiveSubscriptionInfo( + subscriptionManager.getDefaultDataSubscriptionId()); + + if (defaultSubscription == null) { + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; // No default subscription + } + return defaultSubscription.getSubscriptionId(); + } +} diff --git a/src/com/android/settings/panel/InternetConnectivityPanel.java b/src/com/android/settings/panel/InternetConnectivityPanel.java index 6ae70895ee3..312bf75b284 100644 --- a/src/com/android/settings/panel/InternetConnectivityPanel.java +++ b/src/com/android/settings/panel/InternetConnectivityPanel.java @@ -16,6 +16,8 @@ package com.android.settings.panel; +import static com.android.settings.network.NetworkProviderSettings.ACTION_NETWORK_PROVIDER_SETTINGS; + import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; @@ -51,17 +53,19 @@ public class InternetConnectivityPanel implements PanelContent { @Override public CharSequence getTitle() { - return mContext.getText(R.string.internet_connectivity_panel_title); + return mContext.getText(Utils.isProviderModelEnabled(mContext) + ? R.string.provider_internet_settings : R.string.internet_connectivity_panel_title); } @Override public List getSlices() { final List uris = new ArrayList<>(); - uris.add(CustomSliceRegistry.WIFI_SLICE_URI); - uris.add(CustomSliceRegistry.MOBILE_DATA_SLICE_URI); if (Utils.isProviderModelEnabled(mContext)) { + uris.add(CustomSliceRegistry.PROVIDER_MODEL_SLICE_URI); uris.add(CustomSliceRegistry.AIRPLANE_SAFE_NETWORKS_SLICE_URI); } else { + uris.add(CustomSliceRegistry.WIFI_SLICE_URI); + uris.add(CustomSliceRegistry.MOBILE_DATA_SLICE_URI); uris.add(AirplaneModePreferenceController.SLICE_URI); } return uris; @@ -69,7 +73,8 @@ public class InternetConnectivityPanel implements PanelContent { @Override public Intent getSeeMoreIntent() { - return new Intent(Settings.ACTION_WIRELESS_SETTINGS) + return new Intent(Utils.isProviderModelEnabled(mContext) + ? ACTION_NETWORK_PROVIDER_SETTINGS : Settings.ACTION_WIRELESS_SETTINGS) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index cf23cbdf1e0..be71b308a44 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -40,6 +40,7 @@ import com.android.settings.location.LocationSlice; import com.android.settings.media.MediaOutputIndicatorSlice; import com.android.settings.media.RemoteMediaSlice; import com.android.settings.network.AirplaneSafeNetworksSlice; +import com.android.settings.network.ProviderModelSlice; import com.android.settings.network.telephony.MobileDataSlice; import com.android.settings.notification.zen.ZenModeButtonPreferenceController; import com.android.settings.wifi.calling.WifiCallingSliceHelper; @@ -167,6 +168,17 @@ public class CustomSliceRegistry { .appendEncodedPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath("mobile_data") .build(); + + /** + * Full {@link Uri} for the Provider Model Slice. + */ + public static final Uri PROVIDER_MODEL_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendEncodedPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("provider_model") + .build(); + /** * Full {@link Uri} for the Alarm volume Slice. */ @@ -176,6 +188,7 @@ public class CustomSliceRegistry { .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath("alarm_volume") .build(); + /** * Full {@link Uri} for the Call Volume Slice. */ @@ -319,6 +332,7 @@ public class CustomSliceRegistry { sUriToSlice.put(LOW_STORAGE_SLICE_URI, LowStorageSlice.class); sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class); sUriToSlice.put(MOBILE_DATA_SLICE_URI, MobileDataSlice.class); + sUriToSlice.put(PROVIDER_MODEL_SLICE_URI, ProviderModelSlice.class); sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class); sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class); sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class); diff --git a/src/com/android/settings/wifi/slice/WifiScanWorker.java b/src/com/android/settings/wifi/slice/WifiScanWorker.java index 16c4ebc0b5a..6c0f4aabe2b 100644 --- a/src/com/android/settings/wifi/slice/WifiScanWorker.java +++ b/src/com/android/settings/wifi/slice/WifiScanWorker.java @@ -61,7 +61,7 @@ public class WifiScanWorker extends SliceBackgroundWorker impleme @VisibleForTesting final LifecycleRegistry mLifecycleRegistry; @VisibleForTesting - WifiPickerTracker mWifiPickerTracker; + protected WifiPickerTracker mWifiPickerTracker; // Worker thread used for WifiPickerTracker work private final HandlerThread mWorkerThread; diff --git a/tests/unit/src/com/android/settings/network/ProviderModelSliceTest.java b/tests/unit/src/com/android/settings/network/ProviderModelSliceTest.java new file mode 100644 index 00000000000..9c16b8a9c1f --- /dev/null +++ b/tests/unit/src/com/android/settings/network/ProviderModelSliceTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2020 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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.net.ConnectivityManager; +import android.net.Uri; +import android.net.wifi.WifiManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.builders.GridRowBuilder; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; +import androidx.slice.widget.SliceLiveData; +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.Utils; +import com.android.settings.network.telephony.NetworkProviderWorker; +import com.android.settings.testutils.ResourcesUtils; +import com.android.settings.wifi.slice.WifiSliceItem; +import com.android.wifitrackerlib.WifiEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class ProviderModelSliceTest { + private static final Uri PROVIDER_MODEL_SLICE_URI = + Uri.parse("content://com.android.settings.slices/action/provider_model"); + private static final int MOCK_SLICE_LEVEL = 3; + + private Context mContext; + private MockProviderModelSlice mMockProviderModelSlice; + List mWifiList = new ArrayList<>(); + private ListBuilder mListBuilder; + private MockNetworkProviderWorker mMockNetworkProviderWorker; + + @Mock + private SubscriptionManager mSubscriptionManager; + @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private WifiManager mWifiManager; + @Mock + private ProviderModelSliceHelper mProviderModelSliceHelper; + @Mock + private WifiSliceItem mMockWifiSliceItem1; + @Mock + private WifiSliceItem mMockWifiSliceItem2; + @Mock + private WifiSliceItem mMockWifiSliceItem3; + @Mock + ListBuilder.RowBuilder mMockCarrierRowBuild; + @Mock + ListBuilder.HeaderBuilder mMockHeader; + @Mock + GridRowBuilder mMockGridRowBuilderNonCarrierNetworkUnavailable; + @Mock + GridRowBuilder mMockGridRowBuilderAllNetworkUnavailable; + + @Before + @UiThreadTest + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager); + + + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mMockNetworkProviderWorker = new MockNetworkProviderWorker(mContext, + PROVIDER_MODEL_SLICE_URI); + mMockProviderModelSlice = new MockProviderModelSlice(mContext, mMockNetworkProviderWorker); + mListBuilder = spy(new ListBuilder(mContext, PROVIDER_MODEL_SLICE_URI, + ListBuilder.INFINITY).setAccentColor(-1)); + when(mProviderModelSliceHelper.createListBuilder(PROVIDER_MODEL_SLICE_URI)).thenReturn( + mListBuilder); + + mWifiList = new ArrayList<>(); + mMockNetworkProviderWorker.updateSelfResults(mWifiList); + + mockBuilder(); + } + + @Test + @UiThreadTest + public void getSlice_noWorkerAndNoCarrier_getOneHeaderOneGridRowWithAllNetworkUnavailable() { + mWifiList.clear(); + mMockProviderModelSlice = new MockProviderModelSlice(mContext, null); + mockHelperCondition(false, false, false, null); + + final Slice slice = mMockProviderModelSlice.getSlice(); + + assertThat(slice).isNotNull(); + verify(mListBuilder, times(1)).setHeader(mMockHeader); + verify(mListBuilder, times(1)).addGridRow(mMockGridRowBuilderAllNetworkUnavailable); + } + + @Test + @UiThreadTest + public void getSlice_noWifiAndNoCarrier_getOneHeaderOneGridRowWithAllNetworkUnavailable() { + mWifiList.clear(); + mMockNetworkProviderWorker.updateSelfResults(null); + mockHelperCondition(false, false, false, null); + + final Slice slice = mMockProviderModelSlice.getSlice(); + + assertThat(slice).isNotNull(); + verify(mListBuilder, times(1)).setHeader(mMockHeader); + verify(mListBuilder, times(1)).addGridRow(mMockGridRowBuilderAllNetworkUnavailable); + } + + @Test + @UiThreadTest + public void getSlice_noWifiAndHasCarrierNoData_oneCarrierOneGridRowWithAllNetworkUnavailable() { + mWifiList.clear(); + mMockNetworkProviderWorker.updateSelfResults(null); + mockHelperCondition(false, true, true, null); + + final Slice slice = mMockProviderModelSlice.getSlice(); + + assertThat(slice).isNotNull(); + verify(mListBuilder, times(1)).addRow(mMockCarrierRowBuild); + verify(mListBuilder, times(1)).addGridRow(mMockGridRowBuilderAllNetworkUnavailable); + } + + @Test + @UiThreadTest + public void getSlice_noWifiAndNoCarrier_oneCarrierOneGridRowWithNonCarrierNetworkUnavailable() { + mWifiList.clear(); + mMockProviderModelSlice = new MockProviderModelSlice(mContext, null); + mockHelperCondition(false, true, false, null); + + final Slice slice = mMockProviderModelSlice.getSlice(); + + assertThat(slice).isNotNull(); + verify(mListBuilder, times(1)).addRow(mMockCarrierRowBuild); + verify(mListBuilder, times(1)).addGridRow(mMockGridRowBuilderNonCarrierNetworkUnavailable); + } + + @Test + @UiThreadTest + public void getSlice_haveTwoWifiAndOneCarrier_getCarrierAndTwoWiFi() { + mWifiList.clear(); + mockWifiItemCondition(mMockWifiSliceItem1, "wifi1", "wifi1", + WifiEntry.CONNECTED_STATE_CONNECTED, "wifi1_key", true); + mWifiList.add(mMockWifiSliceItem1); + mockWifiItemCondition(mMockWifiSliceItem2, "wifi2", "wifi2", + WifiEntry.CONNECTED_STATE_DISCONNECTED, "wifi2_key", true); + mWifiList.add(mMockWifiSliceItem2); + mMockNetworkProviderWorker.updateSelfResults(mWifiList); + mockHelperCondition(false, true, false, mWifiList.get(0)); + + final Slice slice = mMockProviderModelSlice.getSlice(); + + assertThat(slice).isNotNull(); + verify(mListBuilder, times(1)).addRow(mMockCarrierRowBuild); + verify(mListBuilder, times(3)).addRow(any(ListBuilder.RowBuilder.class)); + } + + @Test + @UiThreadTest + public void getSlice_haveOneConnectedWifiAndTwoDisconnectedWifiAndNoCarrier_getTwoRow() { + mWifiList.clear(); + mockWifiItemCondition(mMockWifiSliceItem1, "wifi1", "wifi1", + WifiEntry.CONNECTED_STATE_CONNECTED, "wifi1_key", true); + mWifiList.add(mMockWifiSliceItem1); + mockWifiItemCondition(mMockWifiSliceItem2, "wifi2", "wifi2", + WifiEntry.CONNECTED_STATE_DISCONNECTED, "wifi2_key", true); + mWifiList.add(mMockWifiSliceItem2); + mockWifiItemCondition(mMockWifiSliceItem3, "wifi3", "wifi3", + WifiEntry.CONNECTED_STATE_DISCONNECTED, "wifi3_key", true); + mWifiList.add(mMockWifiSliceItem3); + mMockNetworkProviderWorker.updateSelfResults(mWifiList); + mockHelperCondition(false, false, false, mWifiList.get(0)); + + final Slice slice = mMockProviderModelSlice.getSlice(); + + assertThat(slice).isNotNull(); + verify(mListBuilder, times(3)).addRow(any(ListBuilder.RowBuilder.class)); + } + + @Test + @UiThreadTest + public void getSlice_haveTwoDisconnectedWifiAndNoCarrier_getTwoRow() { + mWifiList.clear(); + mockWifiItemCondition(mMockWifiSliceItem1, "wifi1", "wifi1", + WifiEntry.CONNECTED_STATE_DISCONNECTED, "wifi1_key", true); + mWifiList.add(mMockWifiSliceItem1); + mockWifiItemCondition(mMockWifiSliceItem2, "wifi2", "wifi2", + WifiEntry.CONNECTED_STATE_DISCONNECTED, "wifi2_key", true); + mWifiList.add(mMockWifiSliceItem2); + mMockNetworkProviderWorker.updateSelfResults(mWifiList); + mockHelperCondition(false, false, false, null); + + final Slice slice = mMockProviderModelSlice.getSlice(); + + assertThat(slice).isNotNull(); + verify(mListBuilder, times(2)).addRow(any(ListBuilder.RowBuilder.class)); + } + + @Test + public void providerModelSlice_hasCorrectUri() { + assertThat(mMockProviderModelSlice.getUri()).isEqualTo(PROVIDER_MODEL_SLICE_URI); + } + + private void mockHelperCondition(boolean airplaneMode, boolean hasCarrier, + boolean isNoCarrierData, WifiSliceItem connectedWifiItem) { + when(mProviderModelSliceHelper.isAirplaneModeEnabled()).thenReturn(airplaneMode); + when(mProviderModelSliceHelper.hasCarrier()).thenReturn(hasCarrier); + when(mProviderModelSliceHelper.isNoCarrierData()).thenReturn(isNoCarrierData); + when(mProviderModelSliceHelper.getConnectedWifiItem(any())).thenReturn(connectedWifiItem); + } + + private void mockWifiItemCondition(WifiSliceItem mockWifiItem, String title, String summary, + int connectedState, String key, boolean shouldEditBeforeConnect) { + when(mockWifiItem.getTitle()).thenReturn(title); + when(mockWifiItem.getSummary()).thenReturn(summary); + when(mockWifiItem.getConnectedState()).thenReturn(connectedState); + when(mockWifiItem.getLevel()).thenReturn(MOCK_SLICE_LEVEL); + when(mockWifiItem.getKey()).thenReturn(key); + when(mockWifiItem.shouldEditBeforeConnect()).thenReturn(shouldEditBeforeConnect); + } + + private void mockBuilder() { + SliceAction mockSliceAction = getPrimarySliceAction(); + when(mMockHeader.getTitle()).thenReturn("mockHeader"); + when(mMockHeader.getPrimaryAction()).thenReturn(mockSliceAction); + when(mProviderModelSliceHelper.createHeader()).thenReturn(mMockHeader); + + int resId = ResourcesUtils.getResourcesId(mContext, "string", + "non_carrier_network_unavailable"); + when(mProviderModelSliceHelper.createMessageGridRow(resId)).thenReturn( + mMockGridRowBuilderNonCarrierNetworkUnavailable); + resId = ResourcesUtils.getResourcesId(mContext, "string", + "all_network_unavailable"); + when(mProviderModelSliceHelper.createMessageGridRow(resId)).thenReturn( + mMockGridRowBuilderAllNetworkUnavailable); + + when(mMockCarrierRowBuild.getTitle()).thenReturn("mockRow"); + when(mMockCarrierRowBuild.getPrimaryAction()).thenReturn(mockSliceAction); + when(mProviderModelSliceHelper.createCarrierRow()).thenReturn(mMockCarrierRowBuild); + } + + private SliceAction getPrimarySliceAction() { + return SliceAction.createDeeplink( + getPrimaryAction(), + Utils.createIconWithDrawable(new ColorDrawable(Color.TRANSPARENT)), + ListBuilder.ICON_IMAGE, + ResourcesUtils.getResourcesString(mContext, "summary_placeholder")); + } + + private PendingIntent getPrimaryAction() { + final Intent intent = new Intent("android.settings.NETWORK_PROVIDER_SETTINGS") + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivity(mContext, 0 /* requestCode */, + intent, PendingIntent.FLAG_IMMUTABLE /* flags */); + } + + public class MockNetworkProviderWorker extends NetworkProviderWorker { + MockNetworkProviderWorker(Context context, Uri uri) { + super(context, uri); + } + + public void updateSelfResults(List results) { + this.updateResults(results); + } + } + + public class MockProviderModelSlice extends ProviderModelSlice { + private MockNetworkProviderWorker mNetworkProviderWorker; + + MockProviderModelSlice(Context context, MockNetworkProviderWorker networkProviderWorker) { + super(context); + mNetworkProviderWorker = networkProviderWorker; + } + + @Override + ProviderModelSliceHelper getHelper() { + return mProviderModelSliceHelper; + } + + @Override + NetworkProviderWorker getWorker() { + return mNetworkProviderWorker; + } + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/NetworkProviderWorkerTest.java b/tests/unit/src/com/android/settings/network/telephony/NetworkProviderWorkerTest.java new file mode 100644 index 00000000000..a4fc74558cc --- /dev/null +++ b/tests/unit/src/com/android/settings/network/telephony/NetworkProviderWorkerTest.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2020 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.android.settings.slices.CustomSliceRegistry.PROVIDER_MODEL_SLICE_URI; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Uri; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; + +import androidx.lifecycle.Lifecycle; +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.wifitrackerlib.WifiEntry; +import com.android.wifitrackerlib.WifiPickerTracker; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +public class NetworkProviderWorkerTest { + private Context mContext; + private MockNetworkProviderWorker mMockNetworkProviderWorker; + + @Mock + WifiPickerTracker mMockWifiPickerTracker; + @Mock + private SubscriptionManager mSubscriptionManager; + @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private TelephonyManager mTelephonyManager; + + @Before + @UiThreadTest + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + + mMockNetworkProviderWorker = new MockNetworkProviderWorker(mContext, + PROVIDER_MODEL_SLICE_URI); + mMockNetworkProviderWorker.setWifiPickerTracker(mMockWifiPickerTracker); + } + + @Test + @UiThreadTest + public void onConstructor_shouldBeInCreatedState() { + assertThat(mMockNetworkProviderWorker.getLifecycle().getCurrentState()) + .isEqualTo(Lifecycle.State.CREATED); + } + + @Test + @UiThreadTest + public void onSlicePinned_shouldBeInResumedState() { + mMockNetworkProviderWorker.onSlicePinned(); + + assertThat(mMockNetworkProviderWorker.getLifecycle().getCurrentState()) + .isEqualTo(Lifecycle.State.RESUMED); + } + + @Test + @UiThreadTest + public void onSliceUnpinned_shouldBeInCreatedState() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.onSliceUnpinned(); + + assertThat(mMockNetworkProviderWorker.getLifecycle().getCurrentState()) + .isEqualTo(Lifecycle.State.CREATED); + } + + @Test + @UiThreadTest + public void close_shouldBeInDestroyedState() { + mMockNetworkProviderWorker.close(); + + assertThat(mMockNetworkProviderWorker.getLifecycle().getCurrentState()) + .isEqualTo(Lifecycle.State.DESTROYED); + } + + @Test + @UiThreadTest + public void getWifiEntry_connectedWifiKey_shouldGetConnectedWifi() { + final String key = "key"; + final WifiEntry connectedWifiEntry = mock(WifiEntry.class); + when(connectedWifiEntry.getKey()).thenReturn(key); + when(mMockWifiPickerTracker.getConnectedWifiEntry()).thenReturn(connectedWifiEntry); + + assertThat(mMockNetworkProviderWorker.getWifiEntry(key)).isEqualTo(connectedWifiEntry); + } + + @Test + @UiThreadTest + public void getWifiEntry_reachableWifiKey_shouldGetReachableWifi() { + final String key = "key"; + final WifiEntry reachableWifiEntry = mock(WifiEntry.class); + when(reachableWifiEntry.getKey()).thenReturn(key); + when(mMockWifiPickerTracker.getWifiEntries()).thenReturn(Arrays.asList(reachableWifiEntry)); + + assertThat(mMockNetworkProviderWorker.getWifiEntry(key)).isEqualTo(reachableWifiEntry); + } + + @Test + @UiThreadTest + public void onSubscriptionsChanged_notifySubscriptionChanged_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.onSubscriptionsChanged(); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onAirplaneModeChanged_airplaneModeOn_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.onAirplaneModeChanged(false); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onAirplaneModeChanged_airplaneModeOff_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.onAirplaneModeChanged(true); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onSignalStrengthChanged_notifySignalStrengthChanged_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.onSignalStrengthChanged(); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onMobileDataEnabledChange_notifyMobileDataEnabledChanged_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.onMobileDataEnabledChange(); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onDataConnectivityChange_notifyDataConnectivityChanged_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.onDataConnectivityChange(); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onServiceStateChanged_notifyPhoneStateListener_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.mPhoneStateListener.onServiceStateChanged(new ServiceState()); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onActiveDataSubscriptionIdChanged_notifyPhoneStateListener_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.mPhoneStateListener.onActiveDataSubscriptionIdChanged(1); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + @Test + @UiThreadTest + public void onDisplayInfoChanged_notifyPhoneStateListener_callUpdateSlice() { + mMockNetworkProviderWorker.onSlicePinned(); + mMockNetworkProviderWorker.receiveNotification(false); + + mMockNetworkProviderWorker.mPhoneStateListener.onDisplayInfoChanged( + new TelephonyDisplayInfo(14, 0)); + + assertThat(mMockNetworkProviderWorker.hasNotification()).isTrue(); + } + + public class MockNetworkProviderWorker extends NetworkProviderWorker { + private boolean mHasNotification = false; + + MockNetworkProviderWorker(Context context, Uri uri) { + super(context, uri); + } + + public void receiveNotification(boolean inputValue) { + mHasNotification = inputValue; + } + + public boolean hasNotification() { + return mHasNotification; + } + + @Override + public void updateSlice() { + super.updateSlice(); + receiveNotification(true); + } + + public void setWifiPickerTracker(WifiPickerTracker wifiPickerTracker) { + mWifiPickerTracker = wifiPickerTracker; + } + } +}