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.Activity; 20 import android.app.settings.SettingsEnums; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Message; 26 import android.os.PersistableBundle; 27 import android.provider.Settings; 28 import android.telephony.AccessNetworkConstants; 29 import android.telephony.CarrierConfigManager; 30 import android.telephony.CellIdentity; 31 import android.telephony.CellInfo; 32 import android.telephony.NetworkRegistrationInfo; 33 import android.telephony.ServiceState; 34 import android.telephony.SignalStrength; 35 import android.telephony.SubscriptionManager; 36 import android.telephony.TelephonyManager; 37 import android.util.Log; 38 import android.view.View; 39 40 import androidx.annotation.Keep; 41 import androidx.annotation.VisibleForTesting; 42 import androidx.preference.Preference; 43 import androidx.preference.PreferenceCategory; 44 45 import com.android.internal.telephony.OperatorInfo; 46 import com.android.settings.R; 47 import com.android.settings.dashboard.DashboardFragment; 48 import com.android.settings.overlay.FeatureFactory; 49 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 50 import com.android.settingslib.utils.ThreadUtils; 51 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.List; 55 import java.util.Optional; 56 import java.util.concurrent.ExecutorService; 57 import java.util.concurrent.Executors; 58 59 /** 60 * "Choose network" settings UI for the Settings app. 61 */ 62 @Keep 63 public class NetworkSelectSettings extends DashboardFragment { 64 65 private static final String TAG = "NetworkSelectSettings"; 66 67 private static final int EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE = 1; 68 private static final int EVENT_NETWORK_SCAN_RESULTS = 2; 69 private static final int EVENT_NETWORK_SCAN_ERROR = 3; 70 private static final int EVENT_NETWORK_SCAN_COMPLETED = 4; 71 72 private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference"; 73 private static final int MIN_NUMBER_OF_SCAN_REQUIRED = 2; 74 75 private PreferenceCategory mPreferenceCategory; 76 @VisibleForTesting 77 NetworkOperatorPreference mSelectedPreference; 78 private View mProgressHeader; 79 private Preference mStatusMessagePreference; 80 @VisibleForTesting 81 List<CellInfo> mCellInfoList; 82 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 83 private TelephonyManager mTelephonyManager; 84 private List<String> mForbiddenPlmns; 85 private boolean mShow4GForLTE = false; 86 private NetworkScanHelper mNetworkScanHelper; 87 private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1); 88 private MetricsFeatureProvider mMetricsFeatureProvider; 89 private boolean mUseNewApi; 90 private long mRequestIdManualNetworkSelect; 91 private long mRequestIdManualNetworkScan; 92 private long mWaitingForNumberOfScanResults; 93 @VisibleForTesting 94 boolean mIsAggregationEnabled = false; 95 96 @Override onCreate(Bundle icicle)97 public void onCreate(Bundle icicle) { 98 super.onCreate(icicle); 99 onCreateInitialization(); 100 } 101 102 @Keep 103 @VisibleForTesting onCreateInitialization()104 protected void onCreateInitialization() { 105 mUseNewApi = enableNewAutoSelectNetworkUI(getContext()); 106 mSubId = getSubId(); 107 108 mPreferenceCategory = getPreferenceCategory(PREF_KEY_NETWORK_OPERATORS); 109 mStatusMessagePreference = new Preference(getContext()); 110 mStatusMessagePreference.setSelectable(false); 111 mSelectedPreference = null; 112 mTelephonyManager = getTelephonyManager(getContext(), mSubId); 113 mNetworkScanHelper = new NetworkScanHelper( 114 mTelephonyManager, mCallback, mNetworkScanExecutor); 115 PersistableBundle bundle = getCarrierConfigManager(getContext()) 116 .getConfigForSubId(mSubId); 117 if (bundle != null) { 118 mShow4GForLTE = bundle.getBoolean( 119 CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL); 120 } 121 122 mMetricsFeatureProvider = getMetricsFeatureProvider(getContext()); 123 mIsAggregationEnabled = enableAggregation(getContext()); 124 Log.d(TAG, "init: mUseNewApi:" + mUseNewApi 125 + " ,mIsAggregationEnabled:" + mIsAggregationEnabled + " ,mSubId:" + mSubId); 126 } 127 128 @Keep 129 @VisibleForTesting enableNewAutoSelectNetworkUI(Context context)130 protected boolean enableNewAutoSelectNetworkUI(Context context) { 131 return context.getResources().getBoolean( 132 com.android.internal.R.bool.config_enableNewAutoSelectNetworkUI); 133 } 134 135 @Keep 136 @VisibleForTesting enableAggregation(Context context)137 protected boolean enableAggregation(Context context) { 138 return context.getResources().getBoolean( 139 R.bool.config_network_selection_list_aggregation_enabled); 140 } 141 142 @Keep 143 @VisibleForTesting getPreferenceCategory(String preferenceKey)144 protected PreferenceCategory getPreferenceCategory(String preferenceKey) { 145 return findPreference(preferenceKey); 146 } 147 148 @Keep 149 @VisibleForTesting getTelephonyManager(Context context, int subscriptionId)150 protected TelephonyManager getTelephonyManager(Context context, int subscriptionId) { 151 return context.getSystemService(TelephonyManager.class) 152 .createForSubscriptionId(subscriptionId); 153 } 154 155 @Keep 156 @VisibleForTesting getCarrierConfigManager(Context context)157 protected CarrierConfigManager getCarrierConfigManager(Context context) { 158 return context.getSystemService(CarrierConfigManager.class); 159 } 160 161 @Keep 162 @VisibleForTesting getMetricsFeatureProvider(Context context)163 protected MetricsFeatureProvider getMetricsFeatureProvider(Context context) { 164 return FeatureFactory.getFactory(context).getMetricsFeatureProvider(); 165 } 166 167 @Keep 168 @VisibleForTesting isPreferenceScreenEnabled()169 protected boolean isPreferenceScreenEnabled() { 170 return getPreferenceScreen().isEnabled(); 171 } 172 173 @Keep 174 @VisibleForTesting enablePreferenceScreen(boolean enable)175 protected void enablePreferenceScreen(boolean enable) { 176 getPreferenceScreen().setEnabled(enable); 177 } 178 179 @Keep 180 @VisibleForTesting getSubId()181 protected int getSubId() { 182 int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 183 Intent intent = getActivity().getIntent(); 184 if (intent != null) { 185 subId = intent.getIntExtra(Settings.EXTRA_SUB_ID, 186 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 187 } 188 return subId; 189 } 190 191 @Override onViewCreated(View view, Bundle savedInstanceState)192 public void onViewCreated(View view, Bundle savedInstanceState) { 193 super.onViewCreated(view, savedInstanceState); 194 195 final Activity activity = getActivity(); 196 if (activity != null) { 197 mProgressHeader = setPinnedHeaderView(R.layout.progress_header) 198 .findViewById(R.id.progress_bar_animation); 199 setProgressBarVisible(false); 200 } 201 forceUpdateConnectedPreferenceCategory(); 202 } 203 204 @Override onStart()205 public void onStart() { 206 super.onStart(); 207 208 updateForbiddenPlmns(); 209 if (isProgressBarVisible()) { 210 return; 211 } 212 if (mWaitingForNumberOfScanResults <= 0) { 213 startNetworkQuery(); 214 } 215 } 216 217 /** 218 * Update forbidden PLMNs from the USIM App 219 */ 220 @Keep 221 @VisibleForTesting updateForbiddenPlmns()222 protected void updateForbiddenPlmns() { 223 final String[] forbiddenPlmns = mTelephonyManager.getForbiddenPlmns(); 224 mForbiddenPlmns = forbiddenPlmns != null 225 ? Arrays.asList(forbiddenPlmns) 226 : new ArrayList<>(); 227 } 228 229 @Override onStop()230 public void onStop() { 231 super.onStop(); 232 if (mWaitingForNumberOfScanResults <= 0) { 233 stopNetworkQuery(); 234 } 235 } 236 237 @Override onPreferenceTreeClick(Preference preference)238 public boolean onPreferenceTreeClick(Preference preference) { 239 if (preference != mSelectedPreference) { 240 stopNetworkQuery(); 241 242 // Refresh the last selected item in case users reselect network. 243 clearPreferenceSummary(); 244 if (mSelectedPreference != null) { 245 // Set summary as "Disconnected" to the previously connected network 246 mSelectedPreference.setSummary(R.string.network_disconnected); 247 } 248 249 mSelectedPreference = (NetworkOperatorPreference) preference; 250 mSelectedPreference.setSummary(R.string.network_connecting); 251 252 mMetricsFeatureProvider.action(getContext(), 253 SettingsEnums.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK); 254 255 setProgressBarVisible(true); 256 // Disable the screen until network is manually set 257 enablePreferenceScreen(false); 258 259 mRequestIdManualNetworkSelect = getNewRequestId(); 260 mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED; 261 final OperatorInfo operator = mSelectedPreference.getOperatorInfo(); 262 ThreadUtils.postOnBackgroundThread(() -> { 263 final Message msg = mHandler.obtainMessage( 264 EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE); 265 msg.obj = mTelephonyManager.setNetworkSelectionModeManual( 266 operator, true /* persistSelection */); 267 msg.sendToTarget(); 268 }); 269 } 270 271 return true; 272 } 273 274 @Override getPreferenceScreenResId()275 protected int getPreferenceScreenResId() { 276 return R.xml.choose_network; 277 } 278 279 @Override getLogTag()280 protected String getLogTag() { 281 return TAG; 282 } 283 284 @Override getMetricsCategory()285 public int getMetricsCategory() { 286 return SettingsEnums.MOBILE_NETWORK_SELECT; 287 } 288 289 private final Handler mHandler = new Handler() { 290 @Override 291 public void handleMessage(Message msg) { 292 switch (msg.what) { 293 case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE: 294 final boolean isSucceed = (boolean) msg.obj; 295 stopNetworkQuery(); 296 setProgressBarVisible(false); 297 enablePreferenceScreen(true); 298 299 if (mSelectedPreference != null) { 300 mSelectedPreference.setSummary(isSucceed 301 ? R.string.network_connected 302 : R.string.network_could_not_connect); 303 } else { 304 Log.e(TAG, "No preference to update!"); 305 } 306 break; 307 case EVENT_NETWORK_SCAN_RESULTS: 308 scanResultHandler((List<CellInfo>) msg.obj); 309 break; 310 311 case EVENT_NETWORK_SCAN_ERROR: 312 stopNetworkQuery(); 313 Log.i(TAG, "Network scan failure " + msg.arg1 + ":" 314 + " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan) 315 + ", waiting for scan results = " + mWaitingForNumberOfScanResults 316 + ", select request 0x" 317 + Long.toHexString(mRequestIdManualNetworkSelect)); 318 if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) { 319 break; 320 } 321 if (!isPreferenceScreenEnabled()) { 322 clearPreferenceSummary(); 323 enablePreferenceScreen(true); 324 } else { 325 addMessagePreference(R.string.network_query_error); 326 } 327 break; 328 329 case EVENT_NETWORK_SCAN_COMPLETED: 330 stopNetworkQuery(); 331 Log.d(TAG, "Network scan complete:" 332 + " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan) 333 + ", waiting for scan results = " + mWaitingForNumberOfScanResults 334 + ", select request 0x" 335 + Long.toHexString(mRequestIdManualNetworkSelect)); 336 if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) { 337 break; 338 } 339 if (!isPreferenceScreenEnabled()) { 340 clearPreferenceSummary(); 341 enablePreferenceScreen(true); 342 } else if (mCellInfoList == null) { 343 // In case the scan timeout before getting any results 344 addMessagePreference(R.string.empty_networks_list); 345 } 346 break; 347 } 348 return; 349 } 350 }; 351 352 @VisibleForTesting doAggregation(List<CellInfo> cellInfoListInput)353 List<CellInfo> doAggregation(List<CellInfo> cellInfoListInput) { 354 if (!mIsAggregationEnabled) { 355 Log.d(TAG, "no aggregation"); 356 return new ArrayList<>(cellInfoListInput); 357 } 358 ArrayList<CellInfo> aggregatedList = new ArrayList<>(); 359 for (CellInfo cellInfo : cellInfoListInput) { 360 String plmn = CellInfoUtil.getNetworkTitle(cellInfo.getCellIdentity(), 361 CellInfoUtil.getCellIdentityMccMnc(cellInfo.getCellIdentity())); 362 Class className = cellInfo.getClass(); 363 364 Optional<CellInfo> itemInTheList = aggregatedList.stream().filter( 365 item -> { 366 String itemPlmn = CellInfoUtil.getNetworkTitle(item.getCellIdentity(), 367 CellInfoUtil.getCellIdentityMccMnc(item.getCellIdentity())); 368 return itemPlmn.equals(plmn) && item.getClass().equals(className); 369 }) 370 .findFirst(); 371 if (itemInTheList.isPresent()) { 372 if (cellInfo.isRegistered() && !itemInTheList.get().isRegistered()) { 373 // Adding the registered cellinfo item into list. If there are two registered 374 // cellinfo items, then select first one from source list. 375 aggregatedList.set(aggregatedList.indexOf(itemInTheList.get()), cellInfo); 376 } 377 continue; 378 } 379 aggregatedList.add(cellInfo); 380 } 381 return aggregatedList; 382 } 383 384 private final NetworkScanHelper.NetworkScanCallback mCallback = 385 new NetworkScanHelper.NetworkScanCallback() { 386 public void onResults(List<CellInfo> results) { 387 final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results); 388 msg.sendToTarget(); 389 } 390 391 public void onComplete() { 392 final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED); 393 msg.sendToTarget(); 394 } 395 396 public void onError(int error) { 397 final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error, 398 0 /* arg2 */); 399 msg.sendToTarget(); 400 } 401 }; 402 403 @Keep 404 @VisibleForTesting scanResultHandler(List<CellInfo> results)405 protected void scanResultHandler(List<CellInfo> results) { 406 if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) { 407 Log.d(TAG, "CellInfoList (drop): " 408 + CellInfoUtil.cellInfoListToString(new ArrayList<>(results))); 409 return; 410 } 411 mWaitingForNumberOfScanResults--; 412 if ((mWaitingForNumberOfScanResults <= 0) && (!isResumed())) { 413 stopNetworkQuery(); 414 } 415 416 mCellInfoList = doAggregation(results); 417 Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList)); 418 if (mCellInfoList != null && mCellInfoList.size() != 0) { 419 final NetworkOperatorPreference connectedPref = 420 updateAllPreferenceCategory(); 421 if (connectedPref != null) { 422 // update selected preference instance into connected preference 423 if (mSelectedPreference != null) { 424 mSelectedPreference = connectedPref; 425 } 426 } else if (!isPreferenceScreenEnabled()) { 427 if (connectedPref == null) { 428 mSelectedPreference.setSummary(R.string.network_connecting); 429 } 430 } 431 enablePreferenceScreen(true); 432 } else if (isPreferenceScreenEnabled()) { 433 addMessagePreference(R.string.empty_networks_list); 434 // keep showing progress bar, it will be stopped when error or completed 435 setProgressBarVisible(true); 436 } 437 } 438 439 @Keep 440 @VisibleForTesting createNetworkOperatorPreference(CellInfo cellInfo)441 protected NetworkOperatorPreference createNetworkOperatorPreference(CellInfo cellInfo) { 442 return new NetworkOperatorPreference(getPrefContext(), 443 cellInfo, mForbiddenPlmns, mShow4GForLTE); 444 } 445 446 /** 447 * Update the content of network operators list. 448 * 449 * @return preference which shows connected 450 */ updateAllPreferenceCategory()451 private NetworkOperatorPreference updateAllPreferenceCategory() { 452 int numberOfPreferences = mPreferenceCategory.getPreferenceCount(); 453 454 // remove unused preferences 455 while (numberOfPreferences > mCellInfoList.size()) { 456 numberOfPreferences--; 457 mPreferenceCategory.removePreference( 458 mPreferenceCategory.getPreference(numberOfPreferences)); 459 } 460 461 // update the content of preference 462 NetworkOperatorPreference connectedPref = null; 463 for (int index = 0; index < mCellInfoList.size(); index++) { 464 final CellInfo cellInfo = mCellInfoList.get(index); 465 466 NetworkOperatorPreference pref = null; 467 if (index < numberOfPreferences) { 468 final Preference rawPref = mPreferenceCategory.getPreference(index); 469 if (rawPref instanceof NetworkOperatorPreference) { 470 // replace existing preference 471 pref = (NetworkOperatorPreference) rawPref; 472 pref.updateCell(cellInfo); 473 } else { 474 mPreferenceCategory.removePreference(rawPref); 475 } 476 } 477 if (pref == null) { 478 // add new preference 479 pref = createNetworkOperatorPreference(cellInfo); 480 pref.setOrder(index); 481 mPreferenceCategory.addPreference(pref); 482 } 483 pref.setKey(pref.getOperatorName()); 484 485 if (mCellInfoList.get(index).isRegistered()) { 486 pref.setSummary(R.string.network_connected); 487 connectedPref = pref; 488 } else { 489 pref.setSummary(null); 490 } 491 } 492 493 // update selected preference instance by index 494 for (int index = 0; index < mCellInfoList.size(); index++) { 495 final CellInfo cellInfo = mCellInfoList.get(index); 496 497 if ((mSelectedPreference != null) && mSelectedPreference.isSameCell(cellInfo)) { 498 mSelectedPreference = (NetworkOperatorPreference) 499 (mPreferenceCategory.getPreference(index)); 500 } 501 } 502 503 return connectedPref; 504 } 505 506 /** 507 * Config the network operator list when the page was created. When user get 508 * into this page, the device might or might not have data connection. 509 * - If the device has data: 510 * 1. use {@code ServiceState#getNetworkRegistrationInfoList()} to get the currently 511 * registered cellIdentity, wrap it into a CellInfo; 512 * 2. set the signal strength level as strong; 513 * 3. get the title of the previously connected network operator, since the CellIdentity 514 * got from step 1 only has PLMN. 515 * - If the device has no data, we will remove the connected network operators list from the 516 * screen. 517 */ forceUpdateConnectedPreferenceCategory()518 private void forceUpdateConnectedPreferenceCategory() { 519 if (mTelephonyManager.getDataState() == mTelephonyManager.DATA_CONNECTED) { 520 // Try to get the network registration states 521 final ServiceState ss = mTelephonyManager.getServiceState(); 522 if (ss == null) { 523 return; 524 } 525 final List<NetworkRegistrationInfo> networkList = 526 ss.getNetworkRegistrationInfoListForTransportType( 527 AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 528 if (networkList == null || networkList.size() == 0) { 529 return; 530 } 531 // Due to the aggregation of cell between carriers, it's possible to get CellIdentity 532 // containing forbidden PLMN. 533 // Getting current network from ServiceState is no longer a good idea. 534 // Add an additional rule to avoid from showing forbidden PLMN to the user. 535 if (mForbiddenPlmns == null) { 536 updateForbiddenPlmns(); 537 } 538 for (NetworkRegistrationInfo regInfo : networkList) { 539 final CellIdentity cellIdentity = regInfo.getCellIdentity(); 540 if (cellIdentity == null) { 541 continue; 542 } 543 final NetworkOperatorPreference pref = new NetworkOperatorPreference( 544 getPrefContext(), cellIdentity, mForbiddenPlmns, mShow4GForLTE); 545 if (pref.isForbiddenNetwork()) { 546 continue; 547 } 548 pref.setSummary(R.string.network_connected); 549 // Update the signal strength icon, since the default signalStrength value 550 // would be zero 551 // (it would be quite confusing why the connected network has no signal) 552 pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1); 553 mPreferenceCategory.addPreference(pref); 554 break; 555 } 556 } 557 } 558 559 /** 560 * Clear all of the preference summary 561 */ clearPreferenceSummary()562 private void clearPreferenceSummary() { 563 int idxPreference = mPreferenceCategory.getPreferenceCount(); 564 while (idxPreference > 0) { 565 idxPreference--; 566 final NetworkOperatorPreference networkOperator = (NetworkOperatorPreference) 567 (mPreferenceCategory.getPreference(idxPreference)); 568 networkOperator.setSummary(null); 569 } 570 } 571 getNewRequestId()572 private long getNewRequestId() { 573 return Math.max(mRequestIdManualNetworkSelect, 574 mRequestIdManualNetworkScan) + 1; 575 } 576 isProgressBarVisible()577 private boolean isProgressBarVisible() { 578 if (mProgressHeader == null) { 579 return false; 580 } 581 return (mProgressHeader.getVisibility() == View.VISIBLE); 582 } 583 setProgressBarVisible(boolean visible)584 protected void setProgressBarVisible(boolean visible) { 585 if (mProgressHeader != null) { 586 mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); 587 } 588 } 589 addMessagePreference(int messageId)590 private void addMessagePreference(int messageId) { 591 setProgressBarVisible(false); 592 mStatusMessagePreference.setTitle(messageId); 593 mPreferenceCategory.removeAll(); 594 mPreferenceCategory.addPreference(mStatusMessagePreference); 595 } 596 startNetworkQuery()597 private void startNetworkQuery() { 598 setProgressBarVisible(true); 599 if (mNetworkScanHelper != null) { 600 mRequestIdManualNetworkScan = getNewRequestId(); 601 mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED; 602 mNetworkScanHelper.startNetworkScan( 603 mUseNewApi 604 ? NetworkScanHelper.NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS 605 : NetworkScanHelper.NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS); 606 } 607 } 608 stopNetworkQuery()609 private void stopNetworkQuery() { 610 setProgressBarVisible(false); 611 if (mNetworkScanHelper != null) { 612 mWaitingForNumberOfScanResults = 0; 613 mNetworkScanHelper.stopNetworkQuery(); 614 } 615 } 616 617 @Override onDestroy()618 public void onDestroy() { 619 stopNetworkQuery(); 620 mNetworkScanExecutor.shutdown(); 621 super.onDestroy(); 622 } 623 } 624