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