1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.network.telephony; 18 19 import android.app.settings.SettingsEnums; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.Message; 25 import android.os.PersistableBundle; 26 import android.provider.Settings; 27 import android.telephony.CarrierConfigManager; 28 import android.telephony.CellIdentity; 29 import android.telephony.CellInfo; 30 import android.telephony.NetworkRegistrationInfo; 31 import android.telephony.SignalStrength; 32 import android.telephony.SubscriptionManager; 33 import android.telephony.TelephonyManager; 34 import android.telephony.satellite.SatelliteManager; 35 import android.util.Log; 36 import android.view.View; 37 38 import androidx.annotation.Keep; 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 import androidx.annotation.VisibleForTesting; 42 import androidx.preference.Preference; 43 import androidx.preference.PreferenceCategory; 44 45 import com.android.internal.annotations.Initializer; 46 import com.android.internal.telephony.OperatorInfo; 47 import com.android.settings.R; 48 import com.android.settings.dashboard.DashboardFragment; 49 import com.android.settings.network.telephony.scan.NetworkScanRepository; 50 import com.android.settings.overlay.FeatureFactory; 51 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 52 import com.android.settingslib.utils.ThreadUtils; 53 54 import com.google.common.collect.ImmutableList; 55 56 import kotlin.Unit; 57 58 import kotlinx.coroutines.Job; 59 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.List; 63 import java.util.concurrent.ExecutorService; 64 import java.util.concurrent.Executors; 65 import java.util.concurrent.atomic.AtomicBoolean; 66 import java.util.stream.Collectors; 67 68 /** 69 * "Choose network" settings UI for the Settings app. 70 */ 71 @Keep 72 public class NetworkSelectSettings extends DashboardFragment { 73 74 private static final String TAG = "NetworkSelectSettings"; 75 76 private static final int EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE = 1; 77 78 private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference"; 79 80 private PreferenceCategory mPreferenceCategory; 81 @VisibleForTesting 82 NetworkOperatorPreference mSelectedPreference; 83 private View mProgressHeader; 84 private Preference mStatusMessagePreference; 85 @VisibleForTesting 86 @NonNull 87 List<CellInfo> mCellInfoList = ImmutableList.of(); 88 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 89 private TelephonyManager mTelephonyManager; 90 private SatelliteManager mSatelliteManager; 91 private CarrierConfigManager mCarrierConfigManager; 92 private List<String> mForbiddenPlmns; 93 private boolean mShow4GForLTE = false; 94 private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1); 95 private MetricsFeatureProvider mMetricsFeatureProvider; 96 private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; 97 private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean(); 98 99 private NetworkScanRepository mNetworkScanRepository; 100 @Nullable 101 private Job mNetworkScanJob = null; 102 103 private NetworkSelectRepository mNetworkSelectRepository; 104 105 @Override onCreate(Bundle icicle)106 public void onCreate(Bundle icicle) { 107 super.onCreate(icicle); 108 onCreateInitialization(); 109 } 110 111 @Keep 112 @VisibleForTesting 113 @Initializer onCreateInitialization()114 protected void onCreateInitialization() { 115 Context context = getContext(); 116 mSubId = getSubId(); 117 118 mPreferenceCategory = getPreferenceCategory(PREF_KEY_NETWORK_OPERATORS); 119 mStatusMessagePreference = new Preference(context); 120 mStatusMessagePreference.setSelectable(false); 121 mSelectedPreference = null; 122 mTelephonyManager = getTelephonyManager(context, mSubId); 123 mSatelliteManager = getSatelliteManager(context); 124 mCarrierConfigManager = getCarrierConfigManager(context); 125 PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mSubId, 126 CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, 127 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL); 128 mShow4GForLTE = bundle.getBoolean(CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, 129 false); 130 mShouldFilterOutSatellitePlmn.set(bundle.getBoolean( 131 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, 132 true)); 133 134 mMetricsFeatureProvider = getMetricsFeatureProvider(context); 135 136 mCarrierConfigChangeListener = 137 (slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigChanged( 138 subId); 139 mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor, 140 mCarrierConfigChangeListener); 141 mNetworkScanRepository = new NetworkScanRepository(context, mSubId); 142 mNetworkSelectRepository = new NetworkSelectRepository(context, mSubId); 143 } 144 145 @Keep 146 @VisibleForTesting getPreferenceCategory(String preferenceKey)147 protected PreferenceCategory getPreferenceCategory(String preferenceKey) { 148 return findPreference(preferenceKey); 149 } 150 151 @Keep 152 @VisibleForTesting getTelephonyManager(Context context, int subscriptionId)153 protected TelephonyManager getTelephonyManager(Context context, int subscriptionId) { 154 return context.getSystemService(TelephonyManager.class) 155 .createForSubscriptionId(subscriptionId); 156 } 157 158 @Keep 159 @VisibleForTesting getCarrierConfigManager(Context context)160 protected CarrierConfigManager getCarrierConfigManager(Context context) { 161 return context.getSystemService(CarrierConfigManager.class); 162 } 163 164 @Keep 165 @VisibleForTesting getMetricsFeatureProvider(Context context)166 protected MetricsFeatureProvider getMetricsFeatureProvider(Context context) { 167 return FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 168 } 169 170 @Keep 171 @VisibleForTesting 172 @Nullable getSatelliteManager(Context context)173 protected SatelliteManager getSatelliteManager(Context context) { 174 return context.getSystemService(SatelliteManager.class); 175 } 176 177 @Keep 178 @VisibleForTesting isPreferenceScreenEnabled()179 protected boolean isPreferenceScreenEnabled() { 180 return getPreferenceScreen().isEnabled(); 181 } 182 183 @Keep 184 @VisibleForTesting enablePreferenceScreen(boolean enable)185 protected void enablePreferenceScreen(boolean enable) { 186 getPreferenceScreen().setEnabled(enable); 187 } 188 189 @Keep 190 @VisibleForTesting getSubId()191 protected int getSubId() { 192 int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 193 Intent intent = getActivity().getIntent(); 194 if (intent != null) { 195 subId = intent.getIntExtra(Settings.EXTRA_SUB_ID, 196 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 197 } 198 return subId; 199 } 200 201 @Override onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)202 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 203 super.onViewCreated(view, savedInstanceState); 204 205 mProgressHeader = setPinnedHeaderView( 206 com.android.settingslib.widget.progressbar.R.layout.progress_header 207 ).findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation); 208 mNetworkSelectRepository.launchUpdateNetworkRegistrationInfo( 209 getViewLifecycleOwner(), 210 (info) -> { 211 forceUpdateConnectedPreferenceCategory(info); 212 return Unit.INSTANCE; 213 }); 214 launchNetworkScan(); 215 } 216 launchNetworkScan()217 private void launchNetworkScan() { 218 setProgressBarVisible(true); 219 mNetworkScanJob = mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), 220 (networkScanResult) -> { 221 if (isPreferenceScreenEnabled() && !isFinishingOrDestroyed()) { 222 scanResultHandler(networkScanResult); 223 } 224 225 return Unit.INSTANCE; 226 }); 227 } 228 229 /** 230 * Update forbidden PLMNs from the USIM App 231 */ 232 @Keep 233 @VisibleForTesting updateForbiddenPlmns()234 protected void updateForbiddenPlmns() { 235 final String[] forbiddenPlmns = mTelephonyManager.getForbiddenPlmns(); 236 mForbiddenPlmns = forbiddenPlmns != null 237 ? Arrays.asList(forbiddenPlmns) 238 : new ArrayList<>(); 239 } 240 241 @Override onPreferenceTreeClick(Preference preference)242 public boolean onPreferenceTreeClick(Preference preference) { 243 if (preference == mSelectedPreference) { 244 Log.d(TAG, "onPreferenceTreeClick: preference is mSelectedPreference. Do nothing."); 245 return true; 246 } 247 if (!(preference instanceof NetworkOperatorPreference)) { 248 Log.d(TAG, "onPreferenceTreeClick: preference is not the NetworkOperatorPreference."); 249 return false; 250 } 251 252 // Need stop network scan before manual select network. 253 if (mNetworkScanJob != null) { 254 mNetworkScanJob.cancel(null); 255 mNetworkScanJob = null; 256 } 257 258 // Refresh the last selected item in case users reselect network. 259 clearPreferenceSummary(); 260 if (mSelectedPreference != null) { 261 // Set summary as "Disconnected" to the previously connected network 262 mSelectedPreference.setSummary(R.string.network_disconnected); 263 } 264 265 mSelectedPreference = (NetworkOperatorPreference) preference; 266 mSelectedPreference.setSummary(R.string.network_connecting); 267 268 mMetricsFeatureProvider.action(getContext(), 269 SettingsEnums.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK); 270 271 setProgressBarVisible(true); 272 // Disable the screen until network is manually set 273 enablePreferenceScreen(false); 274 275 final OperatorInfo operator = mSelectedPreference.getOperatorInfo(); 276 ThreadUtils.postOnBackgroundThread(() -> { 277 final Message msg = mHandler.obtainMessage( 278 EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE); 279 msg.obj = mTelephonyManager.setNetworkSelectionModeManual( 280 operator, true /* persistSelection */); 281 msg.sendToTarget(); 282 }); 283 284 return true; 285 } 286 287 @Override getPreferenceScreenResId()288 protected int getPreferenceScreenResId() { 289 return R.xml.choose_network; 290 } 291 292 @Override getLogTag()293 protected String getLogTag() { 294 return TAG; 295 } 296 297 @Override getMetricsCategory()298 public int getMetricsCategory() { 299 return SettingsEnums.MOBILE_NETWORK_SELECT; 300 } 301 302 private final Handler mHandler = new Handler() { 303 @Override 304 public void handleMessage(Message msg) { 305 switch (msg.what) { 306 case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE: 307 final boolean isSucceed = (boolean) msg.obj; 308 setProgressBarVisible(false); 309 enablePreferenceScreen(true); 310 311 if (mSelectedPreference != null) { 312 mSelectedPreference.setSummary(isSucceed 313 ? R.string.network_connected 314 : R.string.network_could_not_connect); 315 } else { 316 Log.e(TAG, "No preference to update!"); 317 } 318 break; 319 } 320 } 321 }; 322 323 /* We do not want to expose carrier satellite plmns to the user when manually scan the 324 cellular network. Therefore, it is needed to filter out satellite plmns from current cell 325 info list */ 326 @VisibleForTesting filterOutSatellitePlmn(List<CellInfo> cellInfoList)327 List<CellInfo> filterOutSatellitePlmn(List<CellInfo> cellInfoList) { 328 List<String> aggregatedSatellitePlmn = getSatellitePlmnsForCarrierWrapper(); 329 if (!mShouldFilterOutSatellitePlmn.get() || aggregatedSatellitePlmn.isEmpty()) { 330 return cellInfoList; 331 } 332 return cellInfoList.stream() 333 .filter(cellInfo -> !aggregatedSatellitePlmn.contains( 334 CellInfoUtil.getOperatorNumeric(cellInfo.getCellIdentity()))) 335 .collect(Collectors.toList()); 336 } 337 338 /** 339 * Serves as a wrapper method for {@link SatelliteManager#getSatellitePlmnsForCarrier(int)}. 340 * Since SatelliteManager is final, this wrapper enables mocking or spying of 341 * {@link SatelliteManager#getSatellitePlmnsForCarrier(int)} for unit testing purposes. 342 */ 343 @VisibleForTesting getSatellitePlmnsForCarrierWrapper()344 protected List<String> getSatellitePlmnsForCarrierWrapper() { 345 if (mSatelliteManager != null) { 346 return mSatelliteManager.getSatellitePlmnsForCarrier(mSubId); 347 } else { 348 Log.e(TAG, "mSatelliteManager is null, return empty list"); 349 return new ArrayList<>(); 350 } 351 } 352 handleCarrierConfigChanged(int subId)353 private void handleCarrierConfigChanged(int subId) { 354 PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId, 355 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL); 356 boolean shouldFilterSatellitePlmn = config.getBoolean( 357 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, 358 true); 359 if (shouldFilterSatellitePlmn != mShouldFilterOutSatellitePlmn.get()) { 360 mShouldFilterOutSatellitePlmn.set(shouldFilterSatellitePlmn); 361 } 362 } 363 364 @VisibleForTesting scanResultHandler(NetworkScanRepository.NetworkScanResult results)365 protected void scanResultHandler(NetworkScanRepository.NetworkScanResult results) { 366 mCellInfoList = filterOutSatellitePlmn(results.getCellInfos()); 367 Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList)); 368 updateAllPreferenceCategory(); 369 NetworkScanRepository.NetworkScanState state = results.getState(); 370 if (state == NetworkScanRepository.NetworkScanState.ERROR) { 371 addMessagePreference(R.string.network_query_error); 372 } else if (mCellInfoList.isEmpty()) { 373 addMessagePreference(R.string.empty_networks_list); 374 } 375 // keep showing progress bar, it will be stopped when error or completed 376 setProgressBarVisible(state == NetworkScanRepository.NetworkScanState.ACTIVE); 377 } 378 379 @Keep 380 @VisibleForTesting createNetworkOperatorPreference(CellInfo cellInfo)381 protected NetworkOperatorPreference createNetworkOperatorPreference(CellInfo cellInfo) { 382 if (mForbiddenPlmns == null) { 383 updateForbiddenPlmns(); 384 } 385 NetworkOperatorPreference preference = 386 new NetworkOperatorPreference(getPrefContext(), mForbiddenPlmns, mShow4GForLTE); 387 preference.updateCell(cellInfo); 388 return preference; 389 } 390 391 /** 392 * Update the content of network operators list. 393 */ updateAllPreferenceCategory()394 private void updateAllPreferenceCategory() { 395 int numberOfPreferences = mPreferenceCategory.getPreferenceCount(); 396 397 // remove unused preferences 398 while (numberOfPreferences > mCellInfoList.size()) { 399 numberOfPreferences--; 400 mPreferenceCategory.removePreference( 401 mPreferenceCategory.getPreference(numberOfPreferences)); 402 } 403 404 // update the content of preference 405 for (int index = 0; index < mCellInfoList.size(); index++) { 406 final CellInfo cellInfo = mCellInfoList.get(index); 407 408 NetworkOperatorPreference pref = null; 409 if (index < numberOfPreferences) { 410 final Preference rawPref = mPreferenceCategory.getPreference(index); 411 if (rawPref instanceof NetworkOperatorPreference) { 412 // replace existing preference 413 pref = (NetworkOperatorPreference) rawPref; 414 pref.updateCell(cellInfo); 415 } else { 416 mPreferenceCategory.removePreference(rawPref); 417 } 418 } 419 if (pref == null) { 420 // add new preference 421 pref = createNetworkOperatorPreference(cellInfo); 422 pref.setOrder(index); 423 mPreferenceCategory.addPreference(pref); 424 } 425 pref.setKey(pref.getOperatorName()); 426 427 if (mCellInfoList.get(index).isRegistered()) { 428 pref.setSummary(R.string.network_connected); 429 } else { 430 pref.setSummary(null); 431 } 432 } 433 } 434 435 /** 436 * Config the network operator list when the page was created. When user get 437 * into this page, the device might or might not have data connection. 438 * - If the device has data: 439 * 1. use {@code ServiceState#getNetworkRegistrationInfoList()} to get the currently 440 * registered cellIdentity, wrap it into a CellInfo; 441 * 2. set the signal strength level as strong; 442 * 3. get the title of the previously connected network operator, since the CellIdentity 443 * got from step 1 only has PLMN. 444 * - If the device has no data, we will remove the connected network operators list from the 445 * screen. 446 */ forceUpdateConnectedPreferenceCategory( NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo info)447 private void forceUpdateConnectedPreferenceCategory( 448 NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo info) { 449 mPreferenceCategory.removeAll(); 450 for (NetworkRegistrationInfo regInfo : info.getNetworkList()) { 451 final CellIdentity cellIdentity = regInfo.getCellIdentity(); 452 if (cellIdentity == null) { 453 continue; 454 } 455 final NetworkOperatorPreference pref = new NetworkOperatorPreference( 456 getPrefContext(), info.getForbiddenPlmns(), mShow4GForLTE); 457 pref.updateCell(null, cellIdentity); 458 if (pref.isForbiddenNetwork()) { 459 continue; 460 } 461 pref.setSummary(R.string.network_connected); 462 // Update the signal strength icon, since the default signalStrength value 463 // would be zero 464 // (it would be quite confusing why the connected network has no signal) 465 pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1); 466 mPreferenceCategory.addPreference(pref); 467 break; 468 } 469 } 470 471 /** 472 * Clear all of the preference summary 473 */ clearPreferenceSummary()474 private void clearPreferenceSummary() { 475 int idxPreference = mPreferenceCategory.getPreferenceCount(); 476 while (idxPreference > 0) { 477 idxPreference--; 478 final Preference networkOperator = mPreferenceCategory.getPreference(idxPreference); 479 networkOperator.setSummary(null); 480 } 481 } 482 setProgressBarVisible(boolean visible)483 protected void setProgressBarVisible(boolean visible) { 484 if (mProgressHeader != null) { 485 mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); 486 } 487 } 488 addMessagePreference(int messageId)489 private void addMessagePreference(int messageId) { 490 mStatusMessagePreference.setTitle(messageId); 491 mPreferenceCategory.removeAll(); 492 mPreferenceCategory.addPreference(mStatusMessagePreference); 493 } 494 495 @Override onDestroy()496 public void onDestroy() { 497 mNetworkScanExecutor.shutdown(); 498 super.onDestroy(); 499 } 500 } 501