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