NetworkControllerImpl.java revision 403aa2684e0e93b4792aabc0bbe1f32ac5e417af
1/* 2 * Copyright (C) 2010 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 17package com.android.systemui.statusbar.policy; 18 19import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 20import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; 21import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 22import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; 23import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 24 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.res.Resources; 30import android.net.ConnectivityManager; 31import android.net.NetworkCapabilities; 32import android.net.NetworkInfo; 33import android.net.wifi.WifiConfiguration; 34import android.net.wifi.WifiInfo; 35import android.net.wifi.WifiManager; 36import android.os.AsyncTask; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.Message; 40import android.os.Messenger; 41import android.provider.Settings; 42import android.telephony.PhoneStateListener; 43import android.telephony.ServiceState; 44import android.telephony.SignalStrength; 45import android.telephony.SubscriptionInfo; 46import android.telephony.SubscriptionManager; 47import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; 48import android.telephony.TelephonyManager; 49import android.text.TextUtils; 50import android.text.format.DateFormat; 51import android.util.Log; 52 53import com.android.internal.annotations.VisibleForTesting; 54import com.android.internal.telephony.IccCardConstants; 55import com.android.internal.telephony.PhoneConstants; 56import com.android.internal.telephony.TelephonyIntents; 57import com.android.internal.telephony.cdma.EriInfo; 58import com.android.internal.util.AsyncChannel; 59import com.android.systemui.DemoMode; 60import com.android.systemui.R; 61 62import java.io.FileDescriptor; 63import java.io.PrintWriter; 64import java.util.ArrayList; 65import java.util.BitSet; 66import java.util.Collections; 67import java.util.Comparator; 68import java.util.HashMap; 69import java.util.List; 70import java.util.Locale; 71import java.util.Map; 72import java.util.Objects; 73 74/** Platform implementation of the network controller. **/ 75public class NetworkControllerImpl extends BroadcastReceiver 76 implements NetworkController, DemoMode { 77 // debug 78 static final String TAG = "NetworkController"; 79 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 80 // additional diagnostics, but not logspew 81 static final boolean CHATTY = Log.isLoggable(TAG + ".Chat", Log.DEBUG); 82 // Save the previous SignalController.States of all SignalControllers for dumps. 83 static final boolean RECORD_HISTORY = true; 84 // If RECORD_HISTORY how many to save, must be a power of 2. 85 static final int HISTORY_SIZE = 16; 86 87 private static final int INET_CONDITION_THRESHOLD = 50; 88 89 private final Context mContext; 90 private final TelephonyManager mPhone; 91 private final WifiManager mWifiManager; 92 private final ConnectivityManager mConnectivityManager; 93 private final SubscriptionManager mSubscriptionManager; 94 private final boolean mHasMobileDataFeature; 95 private final Config mConfig; 96 97 // Subcontrollers. 98 @VisibleForTesting 99 final WifiSignalController mWifiSignalController; 100 @VisibleForTesting 101 final Map<Integer, MobileSignalController> mMobileSignalControllers = 102 new HashMap<Integer, MobileSignalController>(); 103 // When no SIMs are around at setup, and one is added later, it seems to default to the first 104 // SIM for most actions. This may be null if there aren't any SIMs around. 105 private MobileSignalController mDefaultSignalController; 106 private final AccessPointControllerImpl mAccessPoints; 107 private final MobileDataControllerImpl mMobileDataController; 108 109 // Network types that replace the carrier label if the device does not support mobile data. 110 private boolean mBluetoothTethered = false; 111 private boolean mEthernetConnected = false; 112 113 // state of inet connection 114 private boolean mConnected = false; 115 private boolean mInetCondition; // Used for Logging and demo. 116 117 // BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are 118 // connected and validated, respectively. 119 private final BitSet mConnectedTransports = new BitSet(); 120 private final BitSet mValidatedTransports = new BitSet(); 121 122 // States that don't belong to a subcontroller. 123 private boolean mAirplaneMode = false; 124 private boolean mHasNoSims; 125 private Locale mLocale = null; 126 // This list holds our ordering. 127 private List<SubscriptionInfo> mCurrentSubscriptions 128 = new ArrayList<SubscriptionInfo>(); 129 130 // All the callbacks. 131 private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>(); 132 private ArrayList<CarrierLabelListener> mCarrierListeners = 133 new ArrayList<CarrierLabelListener>(); 134 private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>(); 135 private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks = 136 new ArrayList<NetworkSignalChangedCallback>(); 137 private boolean mListening; 138 139 // The current user ID. 140 private int mCurrentUserId; 141 142 /** 143 * Construct this controller object and register for updates. 144 */ 145 public NetworkControllerImpl(Context context) { 146 this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE), 147 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE), 148 (WifiManager) context.getSystemService(Context.WIFI_SERVICE), 149 SubscriptionManager.from(context), Config.readConfig(context), 150 new AccessPointControllerImpl(context), new MobileDataControllerImpl(context)); 151 registerListeners(); 152 } 153 154 @VisibleForTesting 155 NetworkControllerImpl(Context context, ConnectivityManager connectivityManager, 156 TelephonyManager telephonyManager, WifiManager wifiManager, 157 SubscriptionManager subManager, Config config, 158 AccessPointControllerImpl accessPointController, 159 MobileDataControllerImpl mobileDataController) { 160 mContext = context; 161 mConfig = config; 162 163 mSubscriptionManager = subManager; 164 mConnectivityManager = connectivityManager; 165 mHasMobileDataFeature = 166 mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); 167 168 // telephony 169 mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 170 171 // wifi 172 mWifiManager = wifiManager; 173 174 mLocale = mContext.getResources().getConfiguration().locale; 175 mAccessPoints = accessPointController; 176 mMobileDataController = mobileDataController; 177 mMobileDataController.setNetworkController(this); 178 // TODO: Find a way to move this into MobileDataController. 179 mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() { 180 @Override 181 public void onMobileDataEnabled(boolean enabled) { 182 notifyMobileDataEnabled(enabled); 183 } 184 }); 185 mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature, 186 mSignalsChangedCallbacks, mSignalClusters, this); 187 188 // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it 189 updateAirplaneMode(true /* force callback */); 190 mAccessPoints.setNetworkController(this); 191 } 192 193 private void registerListeners() { 194 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 195 mobileSignalController.registerListener(); 196 } 197 mSubscriptionManager.registerOnSubscriptionsChangedListener(mSubscriptionListener); 198 199 // broadcasts 200 IntentFilter filter = new IntentFilter(); 201 filter.addAction(WifiManager.RSSI_CHANGED_ACTION); 202 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 203 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 204 filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); 205 filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); 206 filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); 207 filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); 208 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE); 209 filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); 210 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 211 filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); 212 mContext.registerReceiver(this, filter); 213 mListening = true; 214 215 updateMobileControllers(); 216 } 217 218 private void unregisterListeners() { 219 mListening = false; 220 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 221 mobileSignalController.unregisterListener(); 222 } 223 mSubscriptionManager.unregisterOnSubscriptionsChangedListener(mSubscriptionListener); 224 mContext.unregisterReceiver(this); 225 } 226 227 public int getConnectedWifiLevel() { 228 return mWifiSignalController.getState().level; 229 } 230 231 @Override 232 public AccessPointController getAccessPointController() { 233 return mAccessPoints; 234 } 235 236 @Override 237 public MobileDataController getMobileDataController() { 238 return mMobileDataController; 239 } 240 241 public void addEmergencyListener(EmergencyListener listener) { 242 mEmergencyListeners.add(listener); 243 listener.setEmergencyCallsOnly(isEmergencyOnly()); 244 } 245 246 public void addCarrierLabel(CarrierLabelListener listener) { 247 mCarrierListeners.add(listener); 248 refreshCarrierLabel(); 249 } 250 251 private void notifyMobileDataEnabled(boolean enabled) { 252 final int length = mSignalsChangedCallbacks.size(); 253 for (int i = 0; i < length; i++) { 254 mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled); 255 } 256 } 257 258 public boolean hasMobileDataFeature() { 259 return mHasMobileDataFeature; 260 } 261 262 public boolean hasVoiceCallingFeature() { 263 return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; 264 } 265 266 private MobileSignalController getDataController() { 267 int dataSubId = SubscriptionManager.getDefaultDataSubId(); 268 if (dataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 269 if (DEBUG) Log.e(TAG, "No data sim selected"); 270 return mDefaultSignalController; 271 } 272 if (mMobileSignalControllers.containsKey(dataSubId)) { 273 return mMobileSignalControllers.get(dataSubId); 274 } 275 Log.e(TAG, "Cannot find controller for data sub: " + dataSubId); 276 return mDefaultSignalController; 277 } 278 279 public String getMobileNetworkName() { 280 MobileSignalController controller = getDataController(); 281 return controller != null ? controller.getState().networkName : ""; 282 } 283 284 public boolean isEmergencyOnly() { 285 int voiceSubId = SubscriptionManager.getDefaultVoiceSubId(); 286 if (voiceSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 287 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 288 if (!mobileSignalController.isEmergencyOnly()) { 289 return false; 290 } 291 } 292 } 293 if (mMobileSignalControllers.containsKey(voiceSubId)) { 294 return mMobileSignalControllers.get(voiceSubId).isEmergencyOnly(); 295 } 296 Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId); 297 // Something is wrong, better assume we can't make calls... 298 return true; 299 } 300 301 /** 302 * Emergency status may have changed (triggered by MobileSignalController), 303 * so we should recheck and send out the state to listeners. 304 */ 305 void recalculateEmergency() { 306 final boolean emergencyOnly = isEmergencyOnly(); 307 final int length = mEmergencyListeners.size(); 308 for (int i = 0; i < length; i++) { 309 mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly); 310 } 311 // If the emergency has a chance to change, then so does the carrier 312 // label. 313 refreshCarrierLabel(); 314 } 315 316 public void addSignalCluster(SignalCluster cluster) { 317 mSignalClusters.add(cluster); 318 cluster.setSubs(mCurrentSubscriptions); 319 cluster.setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON, 320 R.string.accessibility_airplane_mode); 321 cluster.setNoSims(mHasNoSims); 322 mWifiSignalController.notifyListeners(); 323 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 324 mobileSignalController.notifyListeners(); 325 } 326 } 327 328 public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) { 329 mSignalsChangedCallbacks.add(cb); 330 cb.onAirplaneModeChanged(mAirplaneMode); 331 cb.onNoSimVisibleChanged(mHasNoSims); 332 mWifiSignalController.notifyListeners(); 333 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 334 mobileSignalController.notifyListeners(); 335 } 336 } 337 338 public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) { 339 mSignalsChangedCallbacks.remove(cb); 340 } 341 342 @Override 343 public void setWifiEnabled(final boolean enabled) { 344 new AsyncTask<Void, Void, Void>() { 345 @Override 346 protected Void doInBackground(Void... args) { 347 // Disable tethering if enabling Wifi 348 final int wifiApState = mWifiManager.getWifiApState(); 349 if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || 350 (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { 351 mWifiManager.setWifiApEnabled(null, false); 352 } 353 354 mWifiManager.setWifiEnabled(enabled); 355 return null; 356 } 357 }.execute(); 358 } 359 360 @Override 361 public void onUserSwitched(int newUserId) { 362 mCurrentUserId = newUserId; 363 mAccessPoints.onUserSwitched(newUserId); 364 updateConnectivity(); 365 refreshCarrierLabel(); 366 } 367 368 @Override 369 public void onReceive(Context context, Intent intent) { 370 if (CHATTY) { 371 Log.d(TAG, "onReceive: intent=" + intent); 372 } 373 final String action = intent.getAction(); 374 if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) || 375 action.equals(ConnectivityManager.INET_CONDITION_ACTION)) { 376 updateConnectivity(); 377 refreshCarrierLabel(); 378 } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { 379 refreshLocale(); 380 refreshCarrierLabel(); 381 } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { 382 refreshLocale(); 383 updateAirplaneMode(false); 384 refreshCarrierLabel(); 385 } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) { 386 // We are using different subs now, we might be able to make calls. 387 recalculateEmergency(); 388 } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { 389 // Notify every MobileSignalController so they can know whether they are the 390 // data sim or not. 391 for (MobileSignalController controller : mMobileSignalControllers.values()) { 392 controller.handleBroadcast(intent); 393 } 394 } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) { 395 // Might have different subscriptions now. 396 updateMobileControllers(); 397 } else { 398 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, 399 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 400 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 401 if (mMobileSignalControllers.containsKey(subId)) { 402 mMobileSignalControllers.get(subId).handleBroadcast(intent); 403 } else { 404 // Can't find this subscription... We must be out of date. 405 updateMobileControllers(); 406 } 407 } else { 408 // No sub id, must be for the wifi. 409 mWifiSignalController.handleBroadcast(intent); 410 } 411 } 412 } 413 414 private void updateMobileControllers() { 415 if (!mListening) { 416 return; 417 } 418 List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList(); 419 // If there have been no relevant changes to any of the subscriptions, we can leave as is. 420 if (hasCorrectMobileControllers(subscriptions)) { 421 // Even if the controllers are correct, make sure we have the right no sims state. 422 // Such as on boot, don't need any controllers, because there are no sims, 423 // but we still need to update the no sim state. 424 updateNoSims(); 425 return; 426 } 427 setCurrentSubscriptions(subscriptions); 428 updateNoSims(); 429 } 430 431 private void updateNoSims() { 432 boolean hasNoSims = mPhone.getPhoneCount() != 0 && mMobileSignalControllers.size() == 0; 433 if (hasNoSims != mHasNoSims) { 434 mHasNoSims = hasNoSims; 435 notifyListeners(); 436 } 437 } 438 439 @VisibleForTesting 440 void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) { 441 Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() { 442 @Override 443 public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) { 444 return lhs.getSimSlotIndex() == rhs.getSimSlotIndex() 445 ? lhs.getSubscriptionId() - rhs.getSubscriptionId() 446 : lhs.getSimSlotIndex() - rhs.getSimSlotIndex(); 447 } 448 }); 449 final int length = mSignalClusters.size(); 450 for (int i = 0; i < length; i++) { 451 mSignalClusters.get(i).setSubs(subscriptions); 452 } 453 mCurrentSubscriptions = subscriptions; 454 455 HashMap<Integer, MobileSignalController> cachedControllers = 456 new HashMap<Integer, MobileSignalController>(mMobileSignalControllers); 457 mMobileSignalControllers.clear(); 458 final int num = subscriptions.size(); 459 for (int i = 0; i < num; i++) { 460 int subId = subscriptions.get(i).getSubscriptionId(); 461 // If we have a copy of this controller already reuse it, otherwise make a new one. 462 if (cachedControllers.containsKey(subId)) { 463 mMobileSignalControllers.put(subId, cachedControllers.get(subId)); 464 } else { 465 MobileSignalController controller = new MobileSignalController(mContext, mConfig, 466 mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters, 467 this, subscriptions.get(i)); 468 mMobileSignalControllers.put(subId, controller); 469 if (subscriptions.get(i).getSimSlotIndex() == 0) { 470 mDefaultSignalController = controller; 471 } 472 if (mListening) { 473 controller.registerListener(); 474 } 475 } 476 } 477 if (mListening) { 478 for (Integer key : cachedControllers.keySet()) { 479 if (cachedControllers.get(key) == mDefaultSignalController) { 480 mDefaultSignalController = null; 481 } 482 cachedControllers.get(key).unregisterListener(); 483 } 484 } 485 } 486 487 private boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) { 488 if (allSubscriptions == null) { 489 // If null then the system doesn't know the subscriptions yet, instead just wait 490 // to update the MobileControllers until it knows the state. 491 return true; 492 } 493 for (SubscriptionInfo info : allSubscriptions) { 494 if (!mMobileSignalControllers.containsKey(info.getSubscriptionId())) { 495 return false; 496 } 497 } 498 return true; 499 } 500 501 private void updateAirplaneMode(boolean force) { 502 boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(), 503 Settings.Global.AIRPLANE_MODE_ON, 0) == 1); 504 if (airplaneMode != mAirplaneMode || force) { 505 mAirplaneMode = airplaneMode; 506 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 507 mobileSignalController.setAirplaneMode(mAirplaneMode); 508 } 509 notifyListeners(); 510 refreshCarrierLabel(); 511 } 512 } 513 514 private void refreshLocale() { 515 Locale current = mContext.getResources().getConfiguration().locale; 516 if (!current.equals(mLocale)) { 517 mLocale = current; 518 notifyAllListeners(); 519 } 520 } 521 522 /** 523 * Forces update of all callbacks on both SignalClusters and 524 * NetworkSignalChangedCallbacks. 525 */ 526 private void notifyAllListeners() { 527 notifyListeners(); 528 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 529 mobileSignalController.notifyListeners(); 530 } 531 mWifiSignalController.notifyListeners(); 532 } 533 534 /** 535 * Notifies listeners of changes in state of to the NetworkController, but 536 * does not notify for any info on SignalControllers, for that call 537 * notifyAllListeners. 538 */ 539 private void notifyListeners() { 540 int length = mSignalClusters.size(); 541 for (int i = 0; i < length; i++) { 542 mSignalClusters.get(i).setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON, 543 R.string.accessibility_airplane_mode); 544 mSignalClusters.get(i).setNoSims(mHasNoSims); 545 } 546 int signalsChangedLength = mSignalsChangedCallbacks.size(); 547 for (int i = 0; i < signalsChangedLength; i++) { 548 mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode); 549 mSignalsChangedCallbacks.get(i).onNoSimVisibleChanged(mHasNoSims); 550 } 551 } 552 553 /** 554 * Update the Inet conditions and what network we are connected to. 555 */ 556 private void updateConnectivity() { 557 mConnectedTransports.clear(); 558 mValidatedTransports.clear(); 559 for (NetworkCapabilities nc : 560 mConnectivityManager.getDefaultNetworkCapabilitiesForUser(mCurrentUserId)) { 561 for (int transportType : nc.getTransportTypes()) { 562 mConnectedTransports.set(transportType); 563 if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) { 564 mValidatedTransports.set(transportType); 565 } 566 } 567 } 568 569 if (CHATTY) { 570 Log.d(TAG, "updateConnectivity: mConnectedTransports=" + mConnectedTransports); 571 Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports); 572 } 573 574 mConnected = !mConnectedTransports.isEmpty(); 575 mInetCondition = !mValidatedTransports.isEmpty(); 576 mBluetoothTethered = mConnectedTransports.get(TRANSPORT_BLUETOOTH); 577 mEthernetConnected = mConnectedTransports.get(TRANSPORT_ETHERNET); 578 579 // We want to update all the icons, all at once, for any condition change 580 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 581 mobileSignalController.setInetCondition( 582 mInetCondition ? 1 : 0, 583 mValidatedTransports.get(mobileSignalController.getTransportType()) ? 1 : 0); 584 } 585 mWifiSignalController.setInetCondition( 586 mValidatedTransports.get(mWifiSignalController.getTransportType()) ? 1 : 0); 587 } 588 589 /** 590 * Recalculate and update the carrier label. 591 */ 592 void refreshCarrierLabel() { 593 Context context = mContext; 594 595 WifiSignalController.WifiState wifiState = mWifiSignalController.getState(); 596 String label = ""; 597 for (MobileSignalController controller : mMobileSignalControllers.values()) { 598 label = controller.getLabel(label, mConnected, mHasMobileDataFeature); 599 } 600 601 // TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore 602 // but stay for the sake of history. 603 if (mBluetoothTethered && !mHasMobileDataFeature) { 604 label = mContext.getString(R.string.bluetooth_tethered); 605 } 606 607 if (mEthernetConnected && !mHasMobileDataFeature) { 608 label = context.getString(R.string.ethernet_label); 609 } 610 611 if (mAirplaneMode && !isEmergencyOnly()) { 612 // combined values from connected wifi take precedence over airplane mode 613 if (wifiState.connected && mHasMobileDataFeature) { 614 // Suppress "No internet connection." from mobile if wifi connected. 615 label = ""; 616 } else { 617 if (!mHasMobileDataFeature) { 618 label = context.getString( 619 R.string.status_bar_settings_signal_meter_disconnected); 620 } 621 } 622 } else if (!isMobileDataConnected() && !wifiState.connected && !mBluetoothTethered && 623 !mEthernetConnected && !mHasMobileDataFeature) { 624 // Pretty much no connection. 625 label = context.getString(R.string.status_bar_settings_signal_meter_disconnected); 626 } 627 628 // for mobile devices, we always show mobile connection info here (SPN/PLMN) 629 // for other devices, we show whatever network is connected 630 // This is determined above by references to mHasMobileDataFeature. 631 int length = mCarrierListeners.size(); 632 for (int i = 0; i < length; i++) { 633 mCarrierListeners.get(i).setCarrierLabel(label); 634 } 635 } 636 637 private boolean isMobileDataConnected() { 638 MobileSignalController controller = getDataController(); 639 return controller != null ? controller.getState().dataConnected : false; 640 } 641 642 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 643 pw.println("NetworkController state:"); 644 645 pw.println(" - telephony ------"); 646 pw.print(" hasVoiceCallingFeature()="); 647 pw.println(hasVoiceCallingFeature()); 648 649 pw.println(" - Bluetooth ----"); 650 pw.print(" mBtReverseTethered="); 651 pw.println(mBluetoothTethered); 652 653 pw.println(" - connectivity ------"); 654 pw.print(" mConnectedTransports="); 655 pw.println(mConnectedTransports); 656 pw.print(" mValidatedTransports="); 657 pw.println(mValidatedTransports); 658 pw.print(" mInetCondition="); 659 pw.println(mInetCondition); 660 pw.print(" mAirplaneMode="); 661 pw.println(mAirplaneMode); 662 pw.print(" mLocale="); 663 pw.println(mLocale); 664 665 for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { 666 mobileSignalController.dump(pw); 667 } 668 mWifiSignalController.dump(pw); 669 } 670 671 private boolean mDemoMode; 672 private int mDemoInetCondition; 673 private WifiSignalController.WifiState mDemoWifiState; 674 675 @Override 676 public void dispatchDemoCommand(String command, Bundle args) { 677 if (!mDemoMode && command.equals(COMMAND_ENTER)) { 678 if (DEBUG) Log.d(TAG, "Entering demo mode"); 679 unregisterListeners(); 680 mDemoMode = true; 681 mDemoInetCondition = mInetCondition ? 1 : 0; 682 mDemoWifiState = mWifiSignalController.getState(); 683 } else if (mDemoMode && command.equals(COMMAND_EXIT)) { 684 if (DEBUG) Log.d(TAG, "Exiting demo mode"); 685 mDemoMode = false; 686 // Update what MobileSignalControllers, because they may change 687 // to set the number of sim slots. 688 updateMobileControllers(); 689 for (MobileSignalController controller : mMobileSignalControllers.values()) { 690 controller.resetLastState(); 691 } 692 mWifiSignalController.resetLastState(); 693 registerListeners(); 694 notifyAllListeners(); 695 refreshCarrierLabel(); 696 } else if (mDemoMode && command.equals(COMMAND_NETWORK)) { 697 String airplane = args.getString("airplane"); 698 if (airplane != null) { 699 boolean show = airplane.equals("show"); 700 int length = mSignalClusters.size(); 701 for (int i = 0; i < length; i++) { 702 mSignalClusters.get(i).setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON, 703 R.string.accessibility_airplane_mode); 704 } 705 } 706 String fully = args.getString("fully"); 707 if (fully != null) { 708 mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0; 709 mWifiSignalController.setInetCondition(mDemoInetCondition); 710 for (MobileSignalController controller : mMobileSignalControllers.values()) { 711 controller.setInetCondition(mDemoInetCondition, mDemoInetCondition); 712 } 713 } 714 String wifi = args.getString("wifi"); 715 if (wifi != null) { 716 boolean show = wifi.equals("show"); 717 String level = args.getString("level"); 718 if (level != null) { 719 mDemoWifiState.level = level.equals("null") ? -1 720 : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1); 721 mDemoWifiState.connected = mDemoWifiState.level >= 0; 722 } 723 mDemoWifiState.enabled = show; 724 mWifiSignalController.notifyListeners(); 725 } 726 String sims = args.getString("sims"); 727 if (sims != null) { 728 int num = Integer.parseInt(sims); 729 List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>(); 730 if (num != mMobileSignalControllers.size()) { 731 mMobileSignalControllers.clear(); 732 int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax(); 733 for (int i = start /* get out of normal index range */; i < start + num; i++) { 734 SubscriptionInfo info = new SubscriptionInfo(i, "", i, "", "", 0, 0, "", 0, 735 null, 0, 0, ""); 736 subs.add(info); 737 mMobileSignalControllers.put(i, new MobileSignalController(mContext, 738 mConfig, mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, 739 mSignalClusters, this, info)); 740 } 741 } 742 final int n = mSignalClusters.size(); 743 for (int i = 0; i < n; i++) { 744 mSignalClusters.get(i).setSubs(subs); 745 } 746 } 747 String nosim = args.getString("nosim"); 748 if (nosim != null) { 749 boolean show = nosim.equals("show"); 750 final int n = mSignalClusters.size(); 751 for (int i = 0; i < n; i++) { 752 mSignalClusters.get(i).setNoSims(show); 753 } 754 } 755 String mobile = args.getString("mobile"); 756 if (mobile != null) { 757 boolean show = mobile.equals("show"); 758 String datatype = args.getString("datatype"); 759 String slotString = args.getString("slot"); 760 int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString); 761 // Hack to index linearly for easy use. 762 MobileSignalController controller = mMobileSignalControllers 763 .values().toArray(new MobileSignalController[0])[slot]; 764 controller.getState().dataSim = datatype != null; 765 if (datatype != null) { 766 controller.getState().iconGroup = 767 datatype.equals("1x") ? TelephonyIcons.ONE_X : 768 datatype.equals("3g") ? TelephonyIcons.THREE_G : 769 datatype.equals("4g") ? TelephonyIcons.FOUR_G : 770 datatype.equals("e") ? TelephonyIcons.E : 771 datatype.equals("g") ? TelephonyIcons.G : 772 datatype.equals("h") ? TelephonyIcons.H : 773 datatype.equals("lte") ? TelephonyIcons.LTE : 774 datatype.equals("roam") ? TelephonyIcons.ROAMING : 775 TelephonyIcons.UNKNOWN; 776 } 777 int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH; 778 String level = args.getString("level"); 779 if (level != null) { 780 controller.getState().level = level.equals("null") ? -1 781 : Math.min(Integer.parseInt(level), icons[0].length - 1); 782 controller.getState().connected = controller.getState().level >= 0; 783 } 784 controller.getState().enabled = show; 785 controller.notifyListeners(); 786 } 787 refreshCarrierLabel(); 788 } 789 } 790 791 private final OnSubscriptionsChangedListener mSubscriptionListener = 792 new OnSubscriptionsChangedListener() { 793 public void onSubscriptionInfoChanged() { 794 updateMobileControllers(); 795 }; 796 }; 797 798 // TODO: Move to its own file. 799 static class WifiSignalController extends 800 SignalController<WifiSignalController.WifiState, SignalController.IconGroup> { 801 private final WifiManager mWifiManager; 802 private final AsyncChannel mWifiChannel; 803 private final boolean mHasMobileData; 804 805 public WifiSignalController(Context context, boolean hasMobileData, 806 List<NetworkSignalChangedCallback> signalCallbacks, 807 List<SignalCluster> signalClusters, NetworkControllerImpl networkController) { 808 super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, 809 signalCallbacks, signalClusters, networkController); 810 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 811 mHasMobileData = hasMobileData; 812 Handler handler = new WifiHandler(); 813 mWifiChannel = new AsyncChannel(); 814 Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger(); 815 if (wifiMessenger != null) { 816 mWifiChannel.connect(context, handler, wifiMessenger); 817 } 818 // WiFi only has one state. 819 mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup( 820 "Wi-Fi Icons", 821 WifiIcons.WIFI_SIGNAL_STRENGTH, 822 WifiIcons.QS_WIFI_SIGNAL_STRENGTH, 823 AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH, 824 WifiIcons.WIFI_NO_NETWORK, 825 WifiIcons.QS_WIFI_NO_NETWORK, 826 WifiIcons.WIFI_NO_NETWORK, 827 WifiIcons.QS_WIFI_NO_NETWORK, 828 AccessibilityContentDescriptions.WIFI_NO_CONNECTION 829 ); 830 } 831 832 @Override 833 protected WifiState cleanState() { 834 return new WifiState(); 835 } 836 837 @Override 838 public void notifyListeners() { 839 // only show wifi in the cluster if connected or if wifi-only 840 boolean wifiVisible = mCurrentState.enabled 841 && (mCurrentState.connected || !mHasMobileData); 842 String wifiDesc = wifiVisible ? mCurrentState.ssid : null; 843 boolean ssidPresent = wifiVisible && mCurrentState.ssid != null; 844 String contentDescription = getStringIfExists(getContentDescription()); 845 int length = mSignalsChangedCallbacks.size(); 846 for (int i = 0; i < length; i++) { 847 mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled, 848 mCurrentState.connected, getQsCurrentIconId(), 849 ssidPresent && mCurrentState.activityIn, 850 ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc); 851 } 852 853 int signalClustersLength = mSignalClusters.size(); 854 for (int i = 0; i < signalClustersLength; i++) { 855 mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(), 856 contentDescription); 857 } 858 } 859 860 /** 861 * Extract wifi state directly from broadcasts about changes in wifi state. 862 */ 863 public void handleBroadcast(Intent intent) { 864 String action = intent.getAction(); 865 if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 866 mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 867 WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; 868 } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 869 final NetworkInfo networkInfo = (NetworkInfo) 870 intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 871 mCurrentState.connected = networkInfo != null && networkInfo.isConnected(); 872 // If Connected grab the signal strength and ssid. 873 if (mCurrentState.connected) { 874 // try getting it out of the intent first 875 WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null 876 ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) 877 : mWifiManager.getConnectionInfo(); 878 if (info != null) { 879 mCurrentState.ssid = getSsid(info); 880 } else { 881 mCurrentState.ssid = null; 882 } 883 } else if (!mCurrentState.connected) { 884 mCurrentState.ssid = null; 885 } 886 } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { 887 // Default to -200 as its below WifiManager.MIN_RSSI. 888 mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); 889 mCurrentState.level = WifiManager.calculateSignalLevel( 890 mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT); 891 } 892 893 notifyListenersIfNecessary(); 894 } 895 896 private String getSsid(WifiInfo info) { 897 String ssid = info.getSSID(); 898 if (ssid != null) { 899 return ssid; 900 } 901 // OK, it's not in the connectionInfo; we have to go hunting for it 902 List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks(); 903 int length = networks.size(); 904 for (int i = 0; i < length; i++) { 905 if (networks.get(i).networkId == info.getNetworkId()) { 906 return networks.get(i).SSID; 907 } 908 } 909 return null; 910 } 911 912 @VisibleForTesting 913 void setActivity(int wifiActivity) { 914 mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT 915 || wifiActivity == WifiManager.DATA_ACTIVITY_IN; 916 mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT 917 || wifiActivity == WifiManager.DATA_ACTIVITY_OUT; 918 notifyListenersIfNecessary(); 919 } 920 921 /** 922 * Handler to receive the data activity on wifi. 923 */ 924 class WifiHandler extends Handler { 925 @Override 926 public void handleMessage(Message msg) { 927 switch (msg.what) { 928 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 929 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { 930 mWifiChannel.sendMessage(Message.obtain(this, 931 AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)); 932 } else { 933 Log.e(mTag, "Failed to connect to wifi"); 934 } 935 break; 936 case WifiManager.DATA_ACTIVITY_NOTIFICATION: 937 setActivity(msg.arg1); 938 break; 939 default: 940 // Ignore 941 break; 942 } 943 } 944 } 945 946 static class WifiState extends SignalController.State { 947 String ssid; 948 949 @Override 950 public void copyFrom(State s) { 951 super.copyFrom(s); 952 WifiState state = (WifiState) s; 953 ssid = state.ssid; 954 } 955 956 @Override 957 protected void toString(StringBuilder builder) { 958 super.toString(builder); 959 builder.append(',').append("ssid=").append(ssid); 960 } 961 962 @Override 963 public boolean equals(Object o) { 964 return super.equals(o) 965 && Objects.equals(((WifiState) o).ssid, ssid); 966 } 967 } 968 } 969 970 // TODO: Move to its own file. 971 static class MobileSignalController extends SignalController<MobileSignalController.MobileState, 972 MobileSignalController.MobileIconGroup> { 973 private final Config mConfig; 974 private final TelephonyManager mPhone; 975 private final String mNetworkNameDefault; 976 private final String mNetworkNameSeparator; 977 @VisibleForTesting 978 final PhoneStateListener mPhoneStateListener; 979 // Save entire info for logging, we only use the id. 980 private final SubscriptionInfo mSubscriptionInfo; 981 982 // @VisibleForDemoMode 983 Map<Integer, MobileIconGroup> mNetworkToIconLookup; 984 985 // Since some pieces of the phone state are interdependent we store it locally, 986 // this could potentially become part of MobileState for simplification/complication 987 // of code. 988 private IccCardConstants.State mSimState = IccCardConstants.State.READY; 989 private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN; 990 private int mDataState = TelephonyManager.DATA_DISCONNECTED; 991 private ServiceState mServiceState; 992 private SignalStrength mSignalStrength; 993 private MobileIconGroup mDefaultIcons; 994 995 // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't 996 // need listener lists anymore. 997 public MobileSignalController(Context context, Config config, boolean hasMobileData, 998 TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks, 999 List<SignalCluster> signalClusters, NetworkControllerImpl networkController, 1000 SubscriptionInfo info) { 1001 super("MobileSignalController(" + info.getSubscriptionId() + ")", context, 1002 NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters, 1003 networkController); 1004 mConfig = config; 1005 mPhone = phone; 1006 mSubscriptionInfo = info; 1007 mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId()); 1008 mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator); 1009 mNetworkNameDefault = getStringIfExists( 1010 com.android.internal.R.string.lockscreen_carrier_default); 1011 1012 mapIconSets(); 1013 1014 mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault; 1015 mLastState.enabled = mCurrentState.enabled = hasMobileData; 1016 mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons; 1017 // Get initial data sim state. 1018 updateDataSim(); 1019 } 1020 1021 /** 1022 * Get (the mobile parts of) the carrier string. 1023 * 1024 * @param currentLabel can be used for concatenation, currently just empty 1025 * @param connected whether the device has connection to the internet at all 1026 * @param isMobileLabel whether to always return the network or just when data is connected 1027 */ 1028 public String getLabel(String currentLabel, boolean connected, boolean isMobileLabel) { 1029 if (!mCurrentState.enabled) { 1030 return ""; 1031 } else { 1032 String mobileLabel = ""; 1033 // We want to show the carrier name if in service and either: 1034 // - We are connected to mobile data, or 1035 // - We are not connected to mobile data, as long as the *reason* packets are not 1036 // being routed over that link is that we have better connectivity via wifi. 1037 // If data is disconnected for some other reason but wifi (or ethernet/bluetooth) 1038 // is connected, we show nothing. 1039 // Otherwise (nothing connected) we show "No internet connection". 1040 if (mCurrentState.dataConnected) { 1041 mobileLabel = mCurrentState.networkName; 1042 } else if (connected || mCurrentState.isEmergency) { 1043 if (mCurrentState.connected || mCurrentState.isEmergency) { 1044 // The isEmergencyOnly test covers the case of a phone with no SIM 1045 mobileLabel = mCurrentState.networkName; 1046 } 1047 } else { 1048 mobileLabel = mContext.getString( 1049 R.string.status_bar_settings_signal_meter_disconnected); 1050 } 1051 1052 if (currentLabel.length() != 0) { 1053 currentLabel = currentLabel + mNetworkNameSeparator; 1054 } 1055 // Now for things that should only be shown when actually using mobile data. 1056 if (isMobileLabel) { 1057 return currentLabel + mobileLabel; 1058 } else { 1059 return currentLabel 1060 + (mCurrentState.dataConnected ? mobileLabel : currentLabel); 1061 } 1062 } 1063 } 1064 1065 public int getDataContentDescription() { 1066 return getIcons().mDataContentDescription; 1067 } 1068 1069 public void setAirplaneMode(boolean airplaneMode) { 1070 mCurrentState.airplaneMode = airplaneMode; 1071 notifyListenersIfNecessary(); 1072 } 1073 1074 public void setInetCondition(int inetCondition, int inetConditionForNetwork) { 1075 // For mobile data, use general inet condition for phone signal indexing, 1076 // and network specific for data indexing (I think this might be a bug, but 1077 // keeping for now). 1078 // TODO: Update with explanation of why. 1079 mCurrentState.inetForNetwork = inetConditionForNetwork; 1080 setInetCondition(inetCondition); 1081 } 1082 1083 /** 1084 * Start listening for phone state changes. 1085 */ 1086 public void registerListener() { 1087 mPhone.listen(mPhoneStateListener, 1088 PhoneStateListener.LISTEN_SERVICE_STATE 1089 | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS 1090 | PhoneStateListener.LISTEN_CALL_STATE 1091 | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE 1092 | PhoneStateListener.LISTEN_DATA_ACTIVITY); 1093 } 1094 1095 /** 1096 * Stop listening for phone state changes. 1097 */ 1098 public void unregisterListener() { 1099 mPhone.listen(mPhoneStateListener, 0); 1100 } 1101 1102 /** 1103 * Produce a mapping of data network types to icon groups for simple and quick use in 1104 * updateTelephony. 1105 * 1106 * TODO: See if config can change with locale, this may need to be regenerated on Locale 1107 * change. 1108 */ 1109 private void mapIconSets() { 1110 mNetworkToIconLookup = new HashMap<Integer, MobileIconGroup>(); 1111 1112 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G); 1113 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G); 1114 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G); 1115 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G); 1116 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G); 1117 1118 if (!mConfig.showAtLeast3G) { 1119 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, 1120 TelephonyIcons.UNKNOWN); 1121 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E); 1122 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X); 1123 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X); 1124 1125 mDefaultIcons = TelephonyIcons.G; 1126 } else { 1127 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, 1128 TelephonyIcons.THREE_G); 1129 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, 1130 TelephonyIcons.THREE_G); 1131 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, 1132 TelephonyIcons.THREE_G); 1133 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, 1134 TelephonyIcons.THREE_G); 1135 mDefaultIcons = TelephonyIcons.THREE_G; 1136 } 1137 1138 MobileIconGroup hGroup = TelephonyIcons.THREE_G; 1139 if (mConfig.hspaDataDistinguishable) { 1140 hGroup = TelephonyIcons.H; 1141 } 1142 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup); 1143 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup); 1144 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup); 1145 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup); 1146 1147 if (mConfig.show4gForLte) { 1148 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G); 1149 } else { 1150 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE); 1151 } 1152 } 1153 1154 @Override 1155 public void notifyListeners() { 1156 MobileIconGroup icons = getIcons(); 1157 1158 String contentDescription = getStringIfExists(getContentDescription()); 1159 String dataContentDescription = getStringIfExists(icons.mDataContentDescription); 1160 // Only send data sim callbacks to QS. 1161 if (mCurrentState.dataSim) { 1162 int qsTypeIcon = mCurrentState.dataConnected ? 1163 icons.mQsDataType[mCurrentState.inetForNetwork] : 0; 1164 int length = mSignalsChangedCallbacks.size(); 1165 for (int i = 0; i < length; i++) { 1166 mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled 1167 && !mCurrentState.isEmergency && !mCurrentState.airplaneMode, 1168 getQsCurrentIconId(), contentDescription, 1169 qsTypeIcon, 1170 mCurrentState.dataConnected && mCurrentState.activityIn, 1171 mCurrentState.dataConnected && mCurrentState.activityOut, 1172 dataContentDescription, 1173 mCurrentState.isEmergency ? null : mCurrentState.networkName, 1174 // Only wide if actually showing something. 1175 icons.mIsWide && qsTypeIcon != 0); 1176 } 1177 } 1178 boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0 1179 || mCurrentState.iconGroup == TelephonyIcons.ROAMING; 1180 int typeIcon = showDataIcon ? icons.mDataType : 0; 1181 int signalClustersLength = mSignalClusters.size(); 1182 for (int i = 0; i < signalClustersLength; i++) { 1183 mSignalClusters.get(i).setMobileDataIndicators( 1184 mCurrentState.enabled && !mCurrentState.airplaneMode, 1185 getCurrentIconId(), 1186 typeIcon, 1187 contentDescription, 1188 dataContentDescription, 1189 // Only wide if actually showing something. 1190 icons.mIsWide && typeIcon != 0, 1191 mSubscriptionInfo.getSubscriptionId()); 1192 } 1193 } 1194 1195 @Override 1196 protected MobileState cleanState() { 1197 return new MobileState(); 1198 } 1199 1200 private boolean hasService() { 1201 if (mServiceState != null) { 1202 // Consider the device to be in service if either voice or data 1203 // service is available. Some SIM cards are marketed as data-only 1204 // and do not support voice service, and on these SIM cards, we 1205 // want to show signal bars for data service as well as the "no 1206 // service" or "emergency calls only" text that indicates that voice 1207 // is not available. 1208 switch (mServiceState.getVoiceRegState()) { 1209 case ServiceState.STATE_POWER_OFF: 1210 return false; 1211 case ServiceState.STATE_OUT_OF_SERVICE: 1212 case ServiceState.STATE_EMERGENCY_ONLY: 1213 return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; 1214 default: 1215 return true; 1216 } 1217 } else { 1218 return false; 1219 } 1220 } 1221 1222 private boolean isCdma() { 1223 return (mSignalStrength != null) && !mSignalStrength.isGsm(); 1224 } 1225 1226 public boolean isEmergencyOnly() { 1227 return (mServiceState != null && mServiceState.isEmergencyOnly()); 1228 } 1229 1230 private boolean isRoaming() { 1231 if (isCdma()) { 1232 final int iconMode = mServiceState.getCdmaEriIconMode(); 1233 return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF 1234 && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL 1235 || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH); 1236 } else { 1237 return mServiceState != null && mServiceState.getRoaming(); 1238 } 1239 } 1240 1241 public void handleBroadcast(Intent intent) { 1242 String action = intent.getAction(); 1243 if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { 1244 updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false), 1245 intent.getStringExtra(TelephonyIntents.EXTRA_SPN), 1246 intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false), 1247 intent.getStringExtra(TelephonyIntents.EXTRA_PLMN)); 1248 notifyListenersIfNecessary(); 1249 } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { 1250 updateDataSim(); 1251 } 1252 } 1253 1254 private void updateDataSim() { 1255 int defaultDataSub = SubscriptionManager.getDefaultDataSubId(); 1256 if (SubscriptionManager.isValidSubId(defaultDataSub)) { 1257 mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId(); 1258 } else { 1259 // There doesn't seem to be a data sim selected, however if 1260 // there isn't a MobileSignalController with dataSim set, then 1261 // QS won't get any callbacks and will be blank. Instead 1262 // lets just assume we are the data sim (which will basically 1263 // show one at random) in QS until one is selected. The user 1264 // should pick one soon after, so we shouldn't be in this state 1265 // for long. 1266 mCurrentState.dataSim = true; 1267 } 1268 notifyListenersIfNecessary(); 1269 } 1270 1271 /** 1272 * Updates the network's name based on incoming spn and plmn. 1273 */ 1274 void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { 1275 if (CHATTY) { 1276 Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn 1277 + " showPlmn=" + showPlmn + " plmn=" + plmn); 1278 } 1279 StringBuilder str = new StringBuilder(); 1280 if (showPlmn && plmn != null) { 1281 str.append(plmn); 1282 } 1283 if (showSpn && spn != null) { 1284 if (str.length() != 0) { 1285 str.append(mNetworkNameSeparator); 1286 } 1287 str.append(spn); 1288 } 1289 if (str.length() != 0) { 1290 mCurrentState.networkName = str.toString(); 1291 } else { 1292 mCurrentState.networkName = mNetworkNameDefault; 1293 } 1294 } 1295 1296 /** 1297 * Updates the current state based on mServiceState, mSignalStrength, mDataNetType, 1298 * mDataState, and mSimState. It should be called any time one of these is updated. 1299 * This will call listeners if necessary. 1300 */ 1301 private final void updateTelephony() { 1302 if (DEBUG) { 1303 Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService() 1304 + " ss=" + mSignalStrength); 1305 } 1306 mCurrentState.connected = hasService() && mSignalStrength != null; 1307 if (mCurrentState.connected) { 1308 if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) { 1309 mCurrentState.level = mSignalStrength.getCdmaLevel(); 1310 } else { 1311 mCurrentState.level = mSignalStrength.getLevel(); 1312 } 1313 } 1314 if (mNetworkToIconLookup.containsKey(mDataNetType)) { 1315 mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType); 1316 } else { 1317 mCurrentState.iconGroup = mDefaultIcons; 1318 } 1319 mCurrentState.dataConnected = mCurrentState.connected 1320 && mDataState == TelephonyManager.DATA_CONNECTED; 1321 1322 if (isRoaming()) { 1323 mCurrentState.iconGroup = TelephonyIcons.ROAMING; 1324 } 1325 if (isEmergencyOnly() != mCurrentState.isEmergency) { 1326 mCurrentState.isEmergency = isEmergencyOnly(); 1327 mNetworkController.recalculateEmergency(); 1328 } 1329 // Fill in the network name if we think we have it. 1330 if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null 1331 && mServiceState.getOperatorAlphaShort() != null) { 1332 mCurrentState.networkName = mServiceState.getOperatorAlphaShort(); 1333 } 1334 notifyListenersIfNecessary(); 1335 } 1336 1337 @VisibleForTesting 1338 void setActivity(int activity) { 1339 mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT 1340 || activity == TelephonyManager.DATA_ACTIVITY_IN; 1341 mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT 1342 || activity == TelephonyManager.DATA_ACTIVITY_OUT; 1343 notifyListenersIfNecessary(); 1344 } 1345 1346 @Override 1347 public void dump(PrintWriter pw) { 1348 super.dump(pw); 1349 pw.println(" mSubscription=" + mSubscriptionInfo + ","); 1350 pw.println(" mServiceState=" + mServiceState + ","); 1351 pw.println(" mSignalStrength=" + mSignalStrength + ","); 1352 pw.println(" mDataState=" + mDataState + ","); 1353 pw.println(" mDataNetType=" + mDataNetType + ","); 1354 } 1355 1356 class MobilePhoneStateListener extends PhoneStateListener { 1357 public MobilePhoneStateListener(int subId) { 1358 super(subId); 1359 } 1360 1361 @Override 1362 public void onSignalStrengthsChanged(SignalStrength signalStrength) { 1363 if (DEBUG) { 1364 Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength + 1365 ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel()))); 1366 } 1367 mSignalStrength = signalStrength; 1368 updateTelephony(); 1369 } 1370 1371 @Override 1372 public void onServiceStateChanged(ServiceState state) { 1373 if (DEBUG) { 1374 Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState() 1375 + " dataState=" + state.getDataRegState()); 1376 } 1377 mServiceState = state; 1378 updateTelephony(); 1379 } 1380 1381 @Override 1382 public void onDataConnectionStateChanged(int state, int networkType) { 1383 if (DEBUG) { 1384 Log.d(mTag, "onDataConnectionStateChanged: state=" + state 1385 + " type=" + networkType); 1386 } 1387 mDataState = state; 1388 mDataNetType = networkType; 1389 updateTelephony(); 1390 } 1391 1392 @Override 1393 public void onDataActivity(int direction) { 1394 if (DEBUG) { 1395 Log.d(mTag, "onDataActivity: direction=" + direction); 1396 } 1397 setActivity(direction); 1398 } 1399 }; 1400 1401 static class MobileIconGroup extends SignalController.IconGroup { 1402 final int mDataContentDescription; // mContentDescriptionDataType 1403 final int mDataType; 1404 final boolean mIsWide; 1405 final int[] mQsDataType; 1406 1407 public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, 1408 int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, 1409 int discContentDesc, int dataContentDesc, int dataType, boolean isWide, 1410 int[] qsDataType) { 1411 super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState, 1412 qsDiscState, discContentDesc); 1413 mDataContentDescription = dataContentDesc; 1414 mDataType = dataType; 1415 mIsWide = isWide; 1416 mQsDataType = qsDataType; 1417 } 1418 } 1419 1420 static class MobileState extends SignalController.State { 1421 String networkName; 1422 boolean dataSim; 1423 boolean dataConnected; 1424 boolean isEmergency; 1425 boolean airplaneMode; 1426 int inetForNetwork; 1427 1428 @Override 1429 public void copyFrom(State s) { 1430 super.copyFrom(s); 1431 MobileState state = (MobileState) s; 1432 dataSim = state.dataSim; 1433 networkName = state.networkName; 1434 dataConnected = state.dataConnected; 1435 inetForNetwork = state.inetForNetwork; 1436 isEmergency = state.isEmergency; 1437 airplaneMode = state.airplaneMode; 1438 } 1439 1440 @Override 1441 protected void toString(StringBuilder builder) { 1442 super.toString(builder); 1443 builder.append(','); 1444 builder.append("dataSim=").append(dataSim).append(','); 1445 builder.append("networkName=").append(networkName).append(','); 1446 builder.append("dataConnected=").append(dataConnected).append(','); 1447 builder.append("inetForNetwork=").append(inetForNetwork).append(','); 1448 builder.append("isEmergency=").append(isEmergency).append(','); 1449 builder.append("airplaneMode=").append(airplaneMode); 1450 } 1451 1452 @Override 1453 public boolean equals(Object o) { 1454 return super.equals(o) 1455 && Objects.equals(((MobileState) o).networkName, networkName) 1456 && ((MobileState) o).dataSim == dataSim 1457 && ((MobileState) o).dataConnected == dataConnected 1458 && ((MobileState) o).isEmergency == isEmergency 1459 && ((MobileState) o).airplaneMode == airplaneMode 1460 && ((MobileState) o).inetForNetwork == inetForNetwork; 1461 } 1462 } 1463 } 1464 1465 /** 1466 * Common base class for handling signal for both wifi and mobile data. 1467 */ 1468 static abstract class SignalController<T extends SignalController.State, 1469 I extends SignalController.IconGroup> { 1470 protected final String mTag; 1471 protected final T mCurrentState; 1472 protected final T mLastState; 1473 protected final int mTransportType; 1474 protected final Context mContext; 1475 // The owner of the SignalController (i.e. NetworkController will maintain the following 1476 // lists and call notifyListeners whenever the list has changed to ensure everyone 1477 // is aware of current state. 1478 protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks; 1479 protected final List<SignalCluster> mSignalClusters; 1480 protected final NetworkControllerImpl mNetworkController; 1481 1482 // Save the previous HISTORY_SIZE states for logging. 1483 private final State[] mHistory; 1484 // Where to copy the next state into. 1485 private int mHistoryIndex; 1486 1487 public SignalController(String tag, Context context, int type, 1488 List<NetworkSignalChangedCallback> signalCallbacks, 1489 List<SignalCluster> signalClusters, NetworkControllerImpl networkController) { 1490 mTag = TAG + "." + tag; 1491 mNetworkController = networkController; 1492 mTransportType = type; 1493 mContext = context; 1494 mSignalsChangedCallbacks = signalCallbacks; 1495 mSignalClusters = signalClusters; 1496 mCurrentState = cleanState(); 1497 mLastState = cleanState(); 1498 if (RECORD_HISTORY) { 1499 mHistory = new State[HISTORY_SIZE]; 1500 for (int i = 0; i < HISTORY_SIZE; i++) { 1501 mHistory[i] = cleanState(); 1502 } 1503 } 1504 } 1505 1506 public T getState() { 1507 return mCurrentState; 1508 } 1509 1510 public int getTransportType() { 1511 return mTransportType; 1512 } 1513 1514 public void setInetCondition(int inetCondition) { 1515 mCurrentState.inetCondition = inetCondition; 1516 notifyListenersIfNecessary(); 1517 } 1518 1519 /** 1520 * Used at the end of demo mode to clear out any ugly state that it has created. 1521 * Since we haven't had any callbacks, then isDirty will not have been triggered, 1522 * so we can just take the last good state directly from there. 1523 * 1524 * Used for demo mode. 1525 */ 1526 void resetLastState() { 1527 mCurrentState.copyFrom(mLastState); 1528 } 1529 1530 /** 1531 * Determines if the state of this signal controller has changed and 1532 * needs to trigger callbacks related to it. 1533 */ 1534 public boolean isDirty() { 1535 if (!mLastState.equals(mCurrentState)) { 1536 if (DEBUG) { 1537 Log.d(mTag, "Change in state from: " + mLastState + "\n" 1538 + "\tto: " + mCurrentState); 1539 } 1540 return true; 1541 } 1542 return false; 1543 } 1544 1545 public void saveLastState() { 1546 if (RECORD_HISTORY) { 1547 recordLastState(); 1548 } 1549 // Updates the current time. 1550 mCurrentState.time = System.currentTimeMillis(); 1551 mLastState.copyFrom(mCurrentState); 1552 } 1553 1554 /** 1555 * Gets the signal icon for QS based on current state of connected, enabled, and level. 1556 */ 1557 public int getQsCurrentIconId() { 1558 if (mCurrentState.connected) { 1559 return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level]; 1560 } else if (mCurrentState.enabled) { 1561 return getIcons().mQsDiscState; 1562 } else { 1563 return getIcons().mQsNullState; 1564 } 1565 } 1566 1567 /** 1568 * Gets the signal icon for SB based on current state of connected, enabled, and level. 1569 */ 1570 public int getCurrentIconId() { 1571 if (mCurrentState.connected) { 1572 return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level]; 1573 } else if (mCurrentState.enabled) { 1574 return getIcons().mSbDiscState; 1575 } else { 1576 return getIcons().mSbNullState; 1577 } 1578 } 1579 1580 /** 1581 * Gets the content description id for the signal based on current state of connected and 1582 * level. 1583 */ 1584 public int getContentDescription() { 1585 if (mCurrentState.connected) { 1586 return getIcons().mContentDesc[mCurrentState.level]; 1587 } else { 1588 return getIcons().mDiscContentDesc; 1589 } 1590 } 1591 1592 public void notifyListenersIfNecessary() { 1593 if (isDirty()) { 1594 saveLastState(); 1595 notifyListeners(); 1596 mNetworkController.refreshCarrierLabel(); 1597 } 1598 } 1599 1600 /** 1601 * Returns the resource if resId is not 0, and an empty string otherwise. 1602 */ 1603 protected String getStringIfExists(int resId) { 1604 return resId != 0 ? mContext.getString(resId) : ""; 1605 } 1606 1607 protected I getIcons() { 1608 return (I) mCurrentState.iconGroup; 1609 } 1610 1611 /** 1612 * Saves the last state of any changes, so we can log the current 1613 * and last value of any state data. 1614 */ 1615 protected void recordLastState() { 1616 mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState); 1617 } 1618 1619 public void dump(PrintWriter pw) { 1620 pw.println(" - " + mTag + " -----"); 1621 pw.println(" Current State: " + mCurrentState); 1622 if (RECORD_HISTORY) { 1623 // Count up the states that actually contain time stamps, and only display those. 1624 int size = 0; 1625 for (int i = 0; i < HISTORY_SIZE; i++) { 1626 if (mHistory[i].time != 0) size++; 1627 } 1628 // Print out the previous states in ordered number. 1629 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 1630 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 1631 pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + ": " 1632 + mHistory[i & (HISTORY_SIZE - 1)]); 1633 } 1634 } 1635 } 1636 1637 /** 1638 * Trigger callbacks based on current state. The callbacks should be completely 1639 * based on current state, and only need to be called in the scenario where 1640 * mCurrentState != mLastState. 1641 */ 1642 public abstract void notifyListeners(); 1643 1644 /** 1645 * Generate a blank T. 1646 */ 1647 protected abstract T cleanState(); 1648 1649 /* 1650 * Holds icons for a given state. Arrays are generally indexed as inet 1651 * state (full connectivity or not) first, and second dimension as 1652 * signal strength. 1653 */ 1654 static class IconGroup { 1655 final int[][] mSbIcons; 1656 final int[][] mQsIcons; 1657 final int[] mContentDesc; 1658 final int mSbNullState; 1659 final int mQsNullState; 1660 final int mSbDiscState; 1661 final int mQsDiscState; 1662 final int mDiscContentDesc; 1663 // For logging. 1664 final String mName; 1665 1666 public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, 1667 int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, 1668 int discContentDesc) { 1669 mName = name; 1670 mSbIcons = sbIcons; 1671 mQsIcons = qsIcons; 1672 mContentDesc = contentDesc; 1673 mSbNullState = sbNullState; 1674 mQsNullState = qsNullState; 1675 mSbDiscState = sbDiscState; 1676 mQsDiscState = qsDiscState; 1677 mDiscContentDesc = discContentDesc; 1678 } 1679 1680 @Override 1681 public String toString() { 1682 return "IconGroup(" + mName + ")"; 1683 } 1684 } 1685 1686 static class State { 1687 boolean connected; 1688 boolean enabled; 1689 boolean activityIn; 1690 boolean activityOut; 1691 int level; 1692 IconGroup iconGroup; 1693 int inetCondition; 1694 int rssi; // Only for logging. 1695 1696 // Not used for comparison, just used for logging. 1697 long time; 1698 1699 public void copyFrom(State state) { 1700 connected = state.connected; 1701 enabled = state.enabled; 1702 level = state.level; 1703 iconGroup = state.iconGroup; 1704 inetCondition = state.inetCondition; 1705 activityIn = state.activityIn; 1706 activityOut = state.activityOut; 1707 rssi = state.rssi; 1708 time = state.time; 1709 } 1710 1711 @Override 1712 public String toString() { 1713 if (time != 0) { 1714 StringBuilder builder = new StringBuilder(); 1715 toString(builder); 1716 return builder.toString(); 1717 } else { 1718 return "Empty " + getClass().getSimpleName(); 1719 } 1720 } 1721 1722 protected void toString(StringBuilder builder) { 1723 builder.append("connected=").append(connected).append(',') 1724 .append("enabled=").append(enabled).append(',') 1725 .append("level=").append(level).append(',') 1726 .append("inetCondition=").append(inetCondition).append(',') 1727 .append("iconGroup=").append(iconGroup).append(',') 1728 .append("activityIn=").append(activityIn).append(',') 1729 .append("activityOut=").append(activityOut).append(',') 1730 .append("rssi=").append(rssi).append(',') 1731 .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time)); 1732 } 1733 1734 @Override 1735 public boolean equals(Object o) { 1736 if (!o.getClass().equals(getClass())) { 1737 return false; 1738 } 1739 State other = (State) o; 1740 return other.connected == connected 1741 && other.enabled == enabled 1742 && other.level == level 1743 && other.inetCondition == inetCondition 1744 && other.iconGroup == iconGroup 1745 && other.activityIn == activityIn 1746 && other.activityOut == activityOut 1747 && other.rssi == rssi; 1748 } 1749 } 1750 } 1751 1752 public interface SignalCluster { 1753 void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription); 1754 1755 void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, 1756 String contentDescription, String typeContentDescription, boolean isTypeIconWide, 1757 int subId); 1758 void setSubs(List<SubscriptionInfo> subs); 1759 void setNoSims(boolean show); 1760 1761 void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription); 1762 } 1763 1764 public interface EmergencyListener { 1765 void setEmergencyCallsOnly(boolean emergencyOnly); 1766 } 1767 1768 public interface CarrierLabelListener { 1769 void setCarrierLabel(String label); 1770 } 1771 1772 @VisibleForTesting 1773 static class Config { 1774 boolean showAtLeast3G = false; 1775 boolean alwaysShowCdmaRssi = false; 1776 boolean show4gForLte = false; 1777 boolean hspaDataDistinguishable; 1778 1779 static Config readConfig(Context context) { 1780 Config config = new Config(); 1781 Resources res = context.getResources(); 1782 1783 config.showAtLeast3G = res.getBoolean(R.bool.config_showMin3G); 1784 config.alwaysShowCdmaRssi = 1785 res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi); 1786 config.show4gForLte = res.getBoolean(R.bool.config_show4GForLTE); 1787 config.hspaDataDistinguishable = 1788 res.getBoolean(R.bool.config_hspa_data_distinguishable); 1789 return config; 1790 } 1791 } 1792} 1793