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