blob: 833ccd5f17edc5ce52e9097ed2a287c2311175c0 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.policy;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.provider.Settings;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.cdma.EriInfo;
import com.android.internal.util.AsyncChannel;
import com.android.systemui.DemoMode;
import com.android.systemui.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
implements NetworkController, DemoMode {
// debug
static final String TAG = "NetworkController";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// additional diagnostics, but not logspew
static final boolean CHATTY = Log.isLoggable(TAG + ".Chat", Log.DEBUG);
// Save the previous SignalController.States of all SignalControllers for dumps.
static final boolean RECORD_HISTORY = true;
// If RECORD_HISTORY how many to save, must be a power of 2.
static final int HISTORY_SIZE = 16;
private static final int INET_CONDITION_THRESHOLD = 50;
private final Context mContext;
private final TelephonyManager mPhone;
private final WifiManager mWifiManager;
private final ConnectivityManager mConnectivityManager;
private final SubscriptionManager mSubscriptionManager;
private final boolean mHasMobileDataFeature;
private final Config mConfig;
// Subcontrollers.
@VisibleForTesting
final WifiSignalController mWifiSignalController;
@VisibleForTesting
final Map<Integer, MobileSignalController> mMobileSignalControllers =
new HashMap<Integer, MobileSignalController>();
// When no SIMs are around at setup, and one is added later, it seems to default to the first
// SIM for most actions. This may be null if there aren't any SIMs around.
private MobileSignalController mDefaultSignalController;
private final AccessPointControllerImpl mAccessPoints;
private final MobileDataControllerImpl mMobileDataController;
// Network types that replace the carrier label if the device does not support mobile data.
private boolean mBluetoothTethered = false;
private boolean mEthernetConnected = false;
// state of inet connection
private boolean mConnected = false;
private boolean mInetCondition; // Used for Logging and demo.
// BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are
// connected and validated, respectively.
private final BitSet mConnectedTransports = new BitSet();
private final BitSet mValidatedTransports = new BitSet();
// States that don't belong to a subcontroller.
private boolean mAirplaneMode = false;
private boolean mHasNoSims;
private Locale mLocale = null;
// This list holds our ordering.
private List<SubscriptionInfo> mCurrentSubscriptions
= new ArrayList<SubscriptionInfo>();
// All the callbacks.
private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>();
private ArrayList<CarrierLabelListener> mCarrierListeners =
new ArrayList<CarrierLabelListener>();
private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
new ArrayList<NetworkSignalChangedCallback>();
private boolean mListening;
// The current user ID.
private int mCurrentUserId;
/**
* Construct this controller object and register for updates.
*/
public NetworkControllerImpl(Context context) {
this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
(WifiManager) context.getSystemService(Context.WIFI_SERVICE),
SubscriptionManager.from(context), Config.readConfig(context),
new AccessPointControllerImpl(context), new MobileDataControllerImpl(context));
registerListeners();
}
@VisibleForTesting
NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
TelephonyManager telephonyManager, WifiManager wifiManager,
SubscriptionManager subManager, Config config,
AccessPointControllerImpl accessPointController,
MobileDataControllerImpl mobileDataController) {
mContext = context;
mConfig = config;
mSubscriptionManager = subManager;
mConnectivityManager = connectivityManager;
mHasMobileDataFeature =
mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
// telephony
mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
// wifi
mWifiManager = wifiManager;
mLocale = mContext.getResources().getConfiguration().locale;
mAccessPoints = accessPointController;
mMobileDataController = mobileDataController;
mMobileDataController.setNetworkController(this);
// TODO: Find a way to move this into MobileDataController.
mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
@Override
public void onMobileDataEnabled(boolean enabled) {
notifyMobileDataEnabled(enabled);
}
});
mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
mSignalsChangedCallbacks, mSignalClusters, this);
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
mAccessPoints.setNetworkController(this);
}
private void registerListeners() {
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.registerListener();
}
mSubscriptionManager.registerOnSubscriptionsChangedListener(mSubscriptionListener);
// broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
mContext.registerReceiver(this, filter);
mListening = true;
updateMobileControllers();
}
private void unregisterListeners() {
mListening = false;
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.unregisterListener();
}
mSubscriptionManager.unregisterOnSubscriptionsChangedListener(mSubscriptionListener);
mContext.unregisterReceiver(this);
}
public int getConnectedWifiLevel() {
return mWifiSignalController.getState().level;
}
@Override
public AccessPointController getAccessPointController() {
return mAccessPoints;
}
@Override
public MobileDataController getMobileDataController() {
return mMobileDataController;
}
public void addEmergencyListener(EmergencyListener listener) {
mEmergencyListeners.add(listener);
listener.setEmergencyCallsOnly(isEmergencyOnly());
}
public void addCarrierLabel(CarrierLabelListener listener) {
mCarrierListeners.add(listener);
refreshCarrierLabel();
}
private void notifyMobileDataEnabled(boolean enabled) {
final int length = mSignalsChangedCallbacks.size();
for (int i = 0; i < length; i++) {
mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
}
}
public boolean hasMobileDataFeature() {
return mHasMobileDataFeature;
}
public boolean hasVoiceCallingFeature() {
return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
}
private MobileSignalController getDataController() {
int dataSubId = SubscriptionManager.getDefaultDataSubId();
if (!SubscriptionManager.isValidSubscriptionId(dataSubId)) {
if (DEBUG) Log.e(TAG, "No data sim selected");
return mDefaultSignalController;
}
if (mMobileSignalControllers.containsKey(dataSubId)) {
return mMobileSignalControllers.get(dataSubId);
}
if (DEBUG) Log.e(TAG, "Cannot find controller for data sub: " + dataSubId);
return mDefaultSignalController;
}
public String getMobileNetworkName() {
MobileSignalController controller = getDataController();
return controller != null ? controller.getState().networkName : "";
}
public boolean isEmergencyOnly() {
int voiceSubId = SubscriptionManager.getDefaultVoiceSubId();
if (!SubscriptionManager.isValidSubscriptionId(voiceSubId)) {
for (MobileSignalController mobileSignalController :
mMobileSignalControllers.values()) {
if (!mobileSignalController.isEmergencyOnly()) {
return false;
}
}
}
if (mMobileSignalControllers.containsKey(voiceSubId)) {
return mMobileSignalControllers.get(voiceSubId).isEmergencyOnly();
}
if (DEBUG) Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
// Something is wrong, better assume we can't make calls...
return true;
}
/**
* Emergency status may have changed (triggered by MobileSignalController),
* so we should recheck and send out the state to listeners.
*/
void recalculateEmergency() {
final boolean emergencyOnly = isEmergencyOnly();
final int length = mEmergencyListeners.size();
for (int i = 0; i < length; i++) {
mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly);
}
// If the emergency has a chance to change, then so does the carrier
// label.
refreshCarrierLabel();
}
public void addSignalCluster(SignalCluster cluster) {
mSignalClusters.add(cluster);
cluster.setSubs(mCurrentSubscriptions);
cluster.setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
R.string.accessibility_airplane_mode);
cluster.setNoSims(mHasNoSims);
mWifiSignalController.notifyListeners();
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.notifyListeners();
}
}
public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
mSignalsChangedCallbacks.add(cb);
cb.onAirplaneModeChanged(mAirplaneMode);
cb.onNoSimVisibleChanged(mHasNoSims);
mWifiSignalController.notifyListeners();
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.notifyListeners();
}
}
public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
mSignalsChangedCallbacks.remove(cb);
}
@Override
public void setWifiEnabled(final boolean enabled) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... args) {
// Disable tethering if enabling Wifi
final int wifiApState = mWifiManager.getWifiApState();
if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
(wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
mWifiManager.setWifiApEnabled(null, false);
}
mWifiManager.setWifiEnabled(enabled);
return null;
}
}.execute();
}
@Override
public void onUserSwitched(int newUserId) {
mCurrentUserId = newUserId;
mAccessPoints.onUserSwitched(newUserId);
updateConnectivity();
refreshCarrierLabel();
}
@Override
public void onReceive(Context context, Intent intent) {
if (CHATTY) {
Log.d(TAG, "onReceive: intent=" + intent);
}
final String action = intent.getAction();
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) ||
action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
updateConnectivity();
refreshCarrierLabel();
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
refreshLocale();
refreshCarrierLabel();
} else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
refreshLocale();
updateAirplaneMode(false);
refreshCarrierLabel();
} else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) {
// We are using different subs now, we might be able to make calls.
recalculateEmergency();
} else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
// Notify every MobileSignalController so they can know whether they are the
// data sim or not.
for (MobileSignalController controller : mMobileSignalControllers.values()) {
controller.handleBroadcast(intent);
}
} else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
// Might have different subscriptions now.
updateMobileControllers();
} else {
int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
if (mMobileSignalControllers.containsKey(subId)) {
mMobileSignalControllers.get(subId).handleBroadcast(intent);
} else {
// Can't find this subscription... We must be out of date.
updateMobileControllers();
}
} else {
// No sub id, must be for the wifi.
mWifiSignalController.handleBroadcast(intent);
}
}
}
private void updateMobileControllers() {
if (!mListening) {
return;
}
List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
// If there have been no relevant changes to any of the subscriptions, we can leave as is.
if (hasCorrectMobileControllers(subscriptions)) {
// Even if the controllers are correct, make sure we have the right no sims state.
// Such as on boot, don't need any controllers, because there are no sims,
// but we still need to update the no sim state.
updateNoSims();
return;
}
setCurrentSubscriptions(subscriptions);
updateNoSims();
}
@VisibleForTesting
protected void updateNoSims() {
boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
if (hasNoSims != mHasNoSims) {
mHasNoSims = hasNoSims;
notifyListeners();
}
}
@VisibleForTesting
void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
@Override
public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
return lhs.getSimSlotIndex() == rhs.getSimSlotIndex()
? lhs.getSubscriptionId() - rhs.getSubscriptionId()
: lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
}
});
final int length = mSignalClusters.size();
for (int i = 0; i < length; i++) {
mSignalClusters.get(i).setSubs(subscriptions);
}
mCurrentSubscriptions = subscriptions;
HashMap<Integer, MobileSignalController> cachedControllers =
new HashMap<Integer, MobileSignalController>(mMobileSignalControllers);
mMobileSignalControllers.clear();
final int num = subscriptions.size();
for (int i = 0; i < num; i++) {
int subId = subscriptions.get(i).getSubscriptionId();
// If we have a copy of this controller already reuse it, otherwise make a new one.
if (cachedControllers.containsKey(subId)) {
mMobileSignalControllers.put(subId, cachedControllers.get(subId));
} else {
MobileSignalController controller = new MobileSignalController(mContext, mConfig,
mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters,
this, subscriptions.get(i));
mMobileSignalControllers.put(subId, controller);
if (subscriptions.get(i).getSimSlotIndex() == 0) {
mDefaultSignalController = controller;
}
if (mListening) {
controller.registerListener();
}
}
}
if (mListening) {
for (Integer key : cachedControllers.keySet()) {
if (cachedControllers.get(key) == mDefaultSignalController) {
mDefaultSignalController = null;
}
cachedControllers.get(key).unregisterListener();
}
}
// There may be new MobileSignalControllers around, make sure they get the current
// inet condition and airplane mode.
pushConnectivityToSignals();
updateAirplaneMode(true /* force */);
}
private boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) {
if (allSubscriptions == null) {
// If null then the system doesn't know the subscriptions yet, instead just wait
// to update the MobileControllers until it knows the state.
return true;
}
for (SubscriptionInfo info : allSubscriptions) {
if (!mMobileSignalControllers.containsKey(info.getSubscriptionId())) {
return false;
}
}
return true;
}
private void updateAirplaneMode(boolean force) {
boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
if (airplaneMode != mAirplaneMode || force) {
mAirplaneMode = airplaneMode;
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.setAirplaneMode(mAirplaneMode);
}
notifyListeners();
refreshCarrierLabel();
}
}
private void refreshLocale() {
Locale current = mContext.getResources().getConfiguration().locale;
if (!current.equals(mLocale)) {
mLocale = current;
notifyAllListeners();
}
}
/**
* Forces update of all callbacks on both SignalClusters and
* NetworkSignalChangedCallbacks.
*/
private void notifyAllListeners() {
notifyListeners();
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.notifyListeners();
}
mWifiSignalController.notifyListeners();
}
/**
* Notifies listeners of changes in state of to the NetworkController, but
* does not notify for any info on SignalControllers, for that call
* notifyAllListeners.
*/
private void notifyListeners() {
int length = mSignalClusters.size();
for (int i = 0; i < length; i++) {
mSignalClusters.get(i).setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
R.string.accessibility_airplane_mode);
mSignalClusters.get(i).setNoSims(mHasNoSims);
}
int signalsChangedLength = mSignalsChangedCallbacks.size();
for (int i = 0; i < signalsChangedLength; i++) {
mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode);
mSignalsChangedCallbacks.get(i).onNoSimVisibleChanged(mHasNoSims);
}
}
/**
* Update the Inet conditions and what network we are connected to.
*/
private void updateConnectivity() {
mConnectedTransports.clear();
mValidatedTransports.clear();
for (NetworkCapabilities nc :
mConnectivityManager.getDefaultNetworkCapabilitiesForUser(mCurrentUserId)) {
for (int transportType : nc.getTransportTypes()) {
mConnectedTransports.set(transportType);
if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
mValidatedTransports.set(transportType);
}
}
}
if (CHATTY) {
Log.d(TAG, "updateConnectivity: mConnectedTransports=" + mConnectedTransports);
Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
}
mConnected = !mConnectedTransports.isEmpty();
mInetCondition = !mValidatedTransports.isEmpty();
mBluetoothTethered = mConnectedTransports.get(TRANSPORT_BLUETOOTH);
mEthernetConnected = mConnectedTransports.get(TRANSPORT_ETHERNET);
pushConnectivityToSignals();
}
/**
* Pushes the current connectivity state to all SignalControllers.
*/
private void pushConnectivityToSignals() {
// We want to update all the icons, all at once, for any condition change
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.setInetCondition(
mInetCondition ? 1 : 0,
mValidatedTransports.get(mobileSignalController.getTransportType()) ? 1 : 0);
}
mWifiSignalController.setInetCondition(
mValidatedTransports.get(mWifiSignalController.getTransportType()) ? 1 : 0);
}
/**
* Recalculate and update the carrier label.
*/
void refreshCarrierLabel() {
Context context = mContext;
WifiSignalController.WifiState wifiState = mWifiSignalController.getState();
String label = "";
for (MobileSignalController controller : mMobileSignalControllers.values()) {
label = controller.getLabel(label, mConnected, mHasMobileDataFeature);
}
// TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore
// but stay for the sake of history.
if (mBluetoothTethered && !mHasMobileDataFeature) {
label = mContext.getString(R.string.bluetooth_tethered);
}
if (mEthernetConnected && !mHasMobileDataFeature) {
label = context.getString(R.string.ethernet_label);
}
if (mAirplaneMode && !isEmergencyOnly()) {
// combined values from connected wifi take precedence over airplane mode
if (wifiState.connected && mHasMobileDataFeature) {
// Suppress "No internet connection." from mobile if wifi connected.
label = "";
} else {
if (!mHasMobileDataFeature) {
label = context.getString(
R.string.status_bar_settings_signal_meter_disconnected);
}
}
} else if (!isMobileDataConnected() && !wifiState.connected && !mBluetoothTethered &&
!mEthernetConnected && !mHasMobileDataFeature) {
// Pretty much no connection.
label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
}
// for mobile devices, we always show mobile connection info here (SPN/PLMN)
// for other devices, we show whatever network is connected
// This is determined above by references to mHasMobileDataFeature.
int length = mCarrierListeners.size();
for (int i = 0; i < length; i++) {
mCarrierListeners.get(i).setCarrierLabel(label);
}
}
private boolean isMobileDataConnected() {
MobileSignalController controller = getDataController();
return controller != null ? controller.getState().dataConnected : false;
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NetworkController state:");
pw.println(" - telephony ------");
pw.print(" hasVoiceCallingFeature()=");
pw.println(hasVoiceCallingFeature());
pw.println(" - Bluetooth ----");
pw.print(" mBtReverseTethered=");
pw.println(mBluetoothTethered);
pw.println(" - connectivity ------");
pw.print(" mConnectedTransports=");
pw.println(mConnectedTransports);
pw.print(" mValidatedTransports=");
pw.println(mValidatedTransports);
pw.print(" mInetCondition=");
pw.println(mInetCondition);
pw.print(" mAirplaneMode=");
pw.println(mAirplaneMode);
pw.print(" mLocale=");
pw.println(mLocale);
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.dump(pw);
}
mWifiSignalController.dump(pw);
}
private boolean mDemoMode;
private int mDemoInetCondition;
private WifiSignalController.WifiState mDemoWifiState;
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoMode && command.equals(COMMAND_ENTER)) {
if (DEBUG) Log.d(TAG, "Entering demo mode");
unregisterListeners();
mDemoMode = true;
mDemoInetCondition = mInetCondition ? 1 : 0;
mDemoWifiState = mWifiSignalController.getState();
} else if (mDemoMode && command.equals(COMMAND_EXIT)) {
if (DEBUG) Log.d(TAG, "Exiting demo mode");
mDemoMode = false;
// Update what MobileSignalControllers, because they may change
// to set the number of sim slots.
updateMobileControllers();
for (MobileSignalController controller : mMobileSignalControllers.values()) {
controller.resetLastState();
}
mWifiSignalController.resetLastState();
registerListeners();
notifyAllListeners();
refreshCarrierLabel();
} else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
String airplane = args.getString("airplane");
if (airplane != null) {
boolean show = airplane.equals("show");
int length = mSignalClusters.size();
for (int i = 0; i < length; i++) {
mSignalClusters.get(i).setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON,
R.string.accessibility_airplane_mode);
}
}
String fully = args.getString("fully");
if (fully != null) {
mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
mWifiSignalController.setInetCondition(mDemoInetCondition);
for (MobileSignalController controller : mMobileSignalControllers.values()) {
controller.setInetCondition(mDemoInetCondition, mDemoInetCondition);
}
}
String wifi = args.getString("wifi");
if (wifi != null) {
boolean show = wifi.equals("show");
String level = args.getString("level");
if (level != null) {
mDemoWifiState.level = level.equals("null") ? -1
: Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
mDemoWifiState.connected = mDemoWifiState.level >= 0;
}
mDemoWifiState.enabled = show;
mWifiSignalController.notifyListeners();
}
String sims = args.getString("sims");
if (sims != null) {
int num = Integer.parseInt(sims);
List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
if (num != mMobileSignalControllers.size()) {
mMobileSignalControllers.clear();
int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
for (int i = start /* get out of normal index range */; i < start + num; i++) {
SubscriptionInfo info = new SubscriptionInfo(i, "", i, "", "", 0, 0, "", 0,
null, 0, 0, "");
subs.add(info);
mMobileSignalControllers.put(i, new MobileSignalController(mContext,
mConfig, mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks,
mSignalClusters, this, info));
}
}
final int n = mSignalClusters.size();
for (int i = 0; i < n; i++) {
mSignalClusters.get(i).setSubs(subs);
}
}
String nosim = args.getString("nosim");
if (nosim != null) {
boolean show = nosim.equals("show");
final int n = mSignalClusters.size();
for (int i = 0; i < n; i++) {
mSignalClusters.get(i).setNoSims(show);
}
}
String mobile = args.getString("mobile");
if (mobile != null) {
boolean show = mobile.equals("show");
String datatype = args.getString("datatype");
String slotString = args.getString("slot");
int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
// Hack to index linearly for easy use.
MobileSignalController controller = mMobileSignalControllers
.values().toArray(new MobileSignalController[0])[slot];
controller.getState().dataSim = datatype != null;
if (datatype != null) {
controller.getState().iconGroup =
datatype.equals("1x") ? TelephonyIcons.ONE_X :
datatype.equals("3g") ? TelephonyIcons.THREE_G :
datatype.equals("4g") ? TelephonyIcons.FOUR_G :
datatype.equals("e") ? TelephonyIcons.E :
datatype.equals("g") ? TelephonyIcons.G :
datatype.equals("h") ? TelephonyIcons.H :
datatype.equals("lte") ? TelephonyIcons.LTE :
datatype.equals("roam") ? TelephonyIcons.ROAMING :
TelephonyIcons.UNKNOWN;
}
int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
String level = args.getString("level");
if (level != null) {
controller.getState().level = level.equals("null") ? -1
: Math.min(Integer.parseInt(level), icons[0].length - 1);
controller.getState().connected = controller.getState().level >= 0;
}
controller.getState().enabled = show;
controller.notifyListeners();
}
refreshCarrierLabel();
}
}
private final OnSubscriptionsChangedListener mSubscriptionListener =
new OnSubscriptionsChangedListener() {
public void onSubscriptionInfoChanged() {
updateMobileControllers();
};
};
// TODO: Move to its own file.
static class WifiSignalController extends
SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
private final WifiManager mWifiManager;
private final AsyncChannel mWifiChannel;
private final boolean mHasMobileData;
public WifiSignalController(Context context, boolean hasMobileData,
List<NetworkSignalChangedCallback> signalCallbacks,
List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
signalCallbacks, signalClusters, networkController);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mHasMobileData = hasMobileData;
Handler handler = new WifiHandler();
mWifiChannel = new AsyncChannel();
Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
if (wifiMessenger != null) {
mWifiChannel.connect(context, handler, wifiMessenger);
}
// WiFi only has one state.
mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
"Wi-Fi Icons",
WifiIcons.WIFI_SIGNAL_STRENGTH,
WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
WifiIcons.WIFI_NO_NETWORK,
WifiIcons.QS_WIFI_NO_NETWORK,
AccessibilityContentDescriptions.WIFI_NO_CONNECTION
);
}
@Override
protected WifiState cleanState() {
return new WifiState();
}
@Override
public void notifyListeners() {
// only show wifi in the cluster if connected or if wifi-only
boolean wifiVisible = mCurrentState.enabled
&& (mCurrentState.connected || !mHasMobileData);
String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
String contentDescription = getStringIfExists(getContentDescription());
int length = mSignalsChangedCallbacks.size();
for (int i = 0; i < length; i++) {
mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
mCurrentState.connected, getQsCurrentIconId(),
ssidPresent && mCurrentState.activityIn,
ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
}
int signalClustersLength = mSignalClusters.size();
for (int i = 0; i < signalClustersLength; i++) {
mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
contentDescription);
}
}
/**
* Extract wifi state directly from broadcasts about changes in wifi state.
*/
public void handleBroadcast(Intent intent) {
String action = intent.getAction();
if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
} else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
final NetworkInfo networkInfo = (NetworkInfo)
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
// If Connected grab the signal strength and ssid.
if (mCurrentState.connected) {
// try getting it out of the intent first
WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
: mWifiManager.getConnectionInfo();
if (info != null) {
mCurrentState.ssid = getSsid(info);
} else {
mCurrentState.ssid = null;
}
} else if (!mCurrentState.connected) {
mCurrentState.ssid = null;
}
} else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
// Default to -200 as its below WifiManager.MIN_RSSI.
mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
mCurrentState.level = WifiManager.calculateSignalLevel(
mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
}
notifyListenersIfNecessary();
}
private String getSsid(WifiInfo info) {
String ssid = info.getSSID();
if (ssid != null) {
return ssid;
}
// OK, it's not in the connectionInfo; we have to go hunting for it
List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
int length = networks.size();
for (int i = 0; i < length; i++) {
if (networks.get(i).networkId == info.getNetworkId()) {
return networks.get(i).SSID;
}
}
return null;
}
@VisibleForTesting
void setActivity(int wifiActivity) {
mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
|| wifiActivity == WifiManager.DATA_ACTIVITY_IN;
mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
|| wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
notifyListenersIfNecessary();
}
/**
* Handler to receive the data activity on wifi.
*/
class WifiHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
mWifiChannel.sendMessage(Message.obtain(this,
AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
} else {
Log.e(mTag, "Failed to connect to wifi");
}
break;
case WifiManager.DATA_ACTIVITY_NOTIFICATION:
setActivity(msg.arg1);
break;
default:
// Ignore
break;
}
}
}
static class WifiState extends SignalController.State {
String ssid;
@Override
public void copyFrom(State s) {
super.copyFrom(s);
WifiState state = (WifiState) s;
ssid = state.ssid;
}
@Override
protected void toString(StringBuilder builder) {
super.toString(builder);
builder.append(',').append("ssid=").append(ssid);
}
@Override
public boolean equals(Object o) {
return super.equals(o)
&& Objects.equals(((WifiState) o).ssid, ssid);
}
}
}
// TODO: Move to its own file.
static class MobileSignalController extends SignalController<MobileSignalController.MobileState,
MobileSignalController.MobileIconGroup> {
private final Config mConfig;
private final TelephonyManager mPhone;
private final String mNetworkNameDefault;
private final String mNetworkNameSeparator;
@VisibleForTesting
final PhoneStateListener mPhoneStateListener;
// Save entire info for logging, we only use the id.
private final SubscriptionInfo mSubscriptionInfo;
// @VisibleForDemoMode
Map<Integer, MobileIconGroup> mNetworkToIconLookup;
// Since some pieces of the phone state are interdependent we store it locally,
// this could potentially become part of MobileState for simplification/complication
// of code.
private IccCardConstants.State mSimState = IccCardConstants.State.READY;
private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
private int mDataState = TelephonyManager.DATA_DISCONNECTED;
private ServiceState mServiceState;
private SignalStrength mSignalStrength;
private MobileIconGroup mDefaultIcons;
// TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
// need listener lists anymore.
public MobileSignalController(Context context, Config config, boolean hasMobileData,
TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
SubscriptionInfo info) {
super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
networkController);
mConfig = config;
mPhone = phone;
mSubscriptionInfo = info;
mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
mNetworkNameDefault = getStringIfExists(
com.android.internal.R.string.lockscreen_carrier_default);
mapIconSets();
mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
mLastState.enabled = mCurrentState.enabled = hasMobileData;
mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
// Get initial data sim state.
updateDataSim();
}
/**
* Get (the mobile parts of) the carrier string.
*
* @param currentLabel can be used for concatenation, currently just empty
* @param connected whether the device has connection to the internet at all
* @param isMobileLabel whether to always return the network or just when data is connected
*/
public String getLabel(String currentLabel, boolean connected, boolean isMobileLabel) {
if (!mCurrentState.enabled) {
return "";
} else {
String mobileLabel = "";
// We want to show the carrier name if in service and either:
// - We are connected to mobile data, or
// - We are not connected to mobile data, as long as the *reason* packets are not
// being routed over that link is that we have better connectivity via wifi.
// If data is disconnected for some other reason but wifi (or ethernet/bluetooth)
// is connected, we show nothing.
// Otherwise (nothing connected) we show "No internet connection".
if (mCurrentState.dataConnected) {
mobileLabel = mCurrentState.networkName;
} else if (connected || mCurrentState.isEmergency) {
if (mCurrentState.connected || mCurrentState.isEmergency) {
// The isEmergencyOnly test covers the case of a phone with no SIM
mobileLabel = mCurrentState.networkName;
}
} else {
mobileLabel = mContext.getString(
R.string.status_bar_settings_signal_meter_disconnected);
}
if (currentLabel.length() != 0) {
currentLabel = currentLabel + mNetworkNameSeparator;
}
// Now for things that should only be shown when actually using mobile data.
if (isMobileLabel) {
return currentLabel + mobileLabel;
} else {
return currentLabel
+ (mCurrentState.dataConnected ? mobileLabel : currentLabel);
}
}
}
public int getDataContentDescription() {
return getIcons().mDataContentDescription;
}
public void setAirplaneMode(boolean airplaneMode) {
mCurrentState.airplaneMode = airplaneMode;
notifyListenersIfNecessary();
}
public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
// For mobile data, use general inet condition for phone signal indexing,
// and network specific for data indexing (I think this might be a bug, but
// keeping for now).
// TODO: Update with explanation of why.
mCurrentState.inetForNetwork = inetConditionForNetwork;
setInetCondition(inetCondition);
}
/**
* Start listening for phone state changes.
*/
public void registerListener() {
mPhone.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_SERVICE_STATE
| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
| PhoneStateListener.LISTEN_CALL_STATE
| PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
| PhoneStateListener.LISTEN_DATA_ACTIVITY);
}
/**
* Stop listening for phone state changes.
*/
public void unregisterListener() {
mPhone.listen(mPhoneStateListener, 0);
}
/**
* Produce a mapping of data network types to icon groups for simple and quick use in
* updateTelephony.
*
* TODO: See if config can change with locale, this may need to be regenerated on Locale
* change.
*/
private void mapIconSets() {
mNetworkToIconLookup = new HashMap<Integer, MobileIconGroup>();
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
if (!mConfig.showAtLeast3G) {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
TelephonyIcons.UNKNOWN);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
mDefaultIcons = TelephonyIcons.G;
} else {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyIcons.THREE_G);
mDefaultIcons = TelephonyIcons.THREE_G;
}
MobileIconGroup hGroup = TelephonyIcons.THREE_G;
if (mConfig.hspaDataDistinguishable) {
hGroup = TelephonyIcons.H;
}
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
if (mConfig.show4gForLte) {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
} else {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
}
}
@Override
public void notifyListeners() {
MobileIconGroup icons = getIcons();
String contentDescription = getStringIfExists(getContentDescription());
String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
// Only send data sim callbacks to QS.
if (mCurrentState.dataSim) {
int qsTypeIcon = mCurrentState.dataConnected ?
icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
int length = mSignalsChangedCallbacks.size();
for (int i = 0; i < length; i++) {
mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
&& !mCurrentState.isEmergency && !mCurrentState.airplaneMode,
getQsCurrentIconId(), contentDescription,
qsTypeIcon,
mCurrentState.dataConnected && mCurrentState.activityIn,
mCurrentState.dataConnected && mCurrentState.activityOut,
dataContentDescription,
mCurrentState.isEmergency ? null : mCurrentState.networkName,
// Only wide if actually showing something.
icons.mIsWide && qsTypeIcon != 0);
}
}
boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
|| mCurrentState.iconGroup == TelephonyIcons.ROAMING;
int typeIcon = showDataIcon ? icons.mDataType : 0;
int signalClustersLength = mSignalClusters.size();
for (int i = 0; i < signalClustersLength; i++) {
mSignalClusters.get(i).setMobileDataIndicators(
mCurrentState.enabled && !mCurrentState.airplaneMode,
getCurrentIconId(),
typeIcon,
contentDescription,
dataContentDescription,
// Only wide if actually showing something.
icons.mIsWide && typeIcon != 0,
mSubscriptionInfo.getSubscriptionId());
}
}
@Override
protected MobileState cleanState() {
return new MobileState();
}
private boolean hasService() {
if (mServiceState != null) {
// Consider the device to be in service if either voice or data
// service is available. Some SIM cards are marketed as data-only
// and do not support voice service, and on these SIM cards, we
// want to show signal bars for data service as well as the "no
// service" or "emergency calls only" text that indicates that voice
// is not available.
switch (mServiceState.getVoiceRegState()) {
case ServiceState.STATE_POWER_OFF:
return false;
case ServiceState.STATE_OUT_OF_SERVICE:
case ServiceState.STATE_EMERGENCY_ONLY:
return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
default:
return true;
}
} else {
return false;
}
}
private boolean isCdma() {
return (mSignalStrength != null) && !mSignalStrength.isGsm();
}
public boolean isEmergencyOnly() {
return (mServiceState != null && mServiceState.isEmergencyOnly());
}
private boolean isRoaming() {
if (isCdma()) {
final int iconMode = mServiceState.getCdmaEriIconMode();
return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
&& (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
|| iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
} else {
return mServiceState != null && mServiceState.getRoaming();
}
}
public void handleBroadcast(Intent intent) {
String action = intent.getAction();
if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
notifyListenersIfNecessary();
} else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
updateDataSim();
}
}
private void updateDataSim() {
int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
} else {
// There doesn't seem to be a data sim selected, however if
// there isn't a MobileSignalController with dataSim set, then
// QS won't get any callbacks and will be blank. Instead
// lets just assume we are the data sim (which will basically
// show one at random) in QS until one is selected. The user
// should pick one soon after, so we shouldn't be in this state
// for long.
mCurrentState.dataSim = true;
}
notifyListenersIfNecessary();
}
/**
* Updates the network's name based on incoming spn and plmn.
*/
void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) {
if (CHATTY) {
Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
+ " showPlmn=" + showPlmn + " plmn=" + plmn);
}
StringBuilder str = new StringBuilder();
if (showPlmn && plmn != null) {
str.append(plmn);
}
if (showSpn && spn != null) {
if (str.length() != 0) {
str.append(mNetworkNameSeparator);
}
str.append(spn);
}
if (str.length() != 0) {
mCurrentState.networkName = str.toString();
} else {
mCurrentState.networkName = mNetworkNameDefault;
}
}
/**
* Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
* mDataState, and mSimState. It should be called any time one of these is updated.
* This will call listeners if necessary.
*/
private final void updateTelephony() {
if (DEBUG) {
Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService()
+ " ss=" + mSignalStrength);
}
mCurrentState.connected = hasService() && mSignalStrength != null;
if (mCurrentState.connected) {
if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
mCurrentState.level = mSignalStrength.getCdmaLevel();
} else {
mCurrentState.level = mSignalStrength.getLevel();
}
}
if (mNetworkToIconLookup.containsKey(mDataNetType)) {
mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
} else {
mCurrentState.iconGroup = mDefaultIcons;
}
mCurrentState.dataConnected = mCurrentState.connected
&& mDataState == TelephonyManager.DATA_CONNECTED;
if (isRoaming()) {
mCurrentState.iconGroup = TelephonyIcons.ROAMING;
}
if (isEmergencyOnly() != mCurrentState.isEmergency) {
mCurrentState.isEmergency = isEmergencyOnly();
mNetworkController.recalculateEmergency();
}
// Fill in the network name if we think we have it.
if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
&& mServiceState.getOperatorAlphaShort() != null) {
mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
}
notifyListenersIfNecessary();
}
@VisibleForTesting
void setActivity(int activity) {
mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
|| activity == TelephonyManager.DATA_ACTIVITY_IN;
mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
|| activity == TelephonyManager.DATA_ACTIVITY_OUT;
notifyListenersIfNecessary();
}
@Override
public void dump(PrintWriter pw) {
super.dump(pw);
pw.println(" mSubscription=" + mSubscriptionInfo + ",");
pw.println(" mServiceState=" + mServiceState + ",");
pw.println(" mSignalStrength=" + mSignalStrength + ",");
pw.println(" mDataState=" + mDataState + ",");
pw.println(" mDataNetType=" + mDataNetType + ",");
}
class MobilePhoneStateListener extends PhoneStateListener {
public MobilePhoneStateListener(int subId) {
super(subId);
}
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
if (DEBUG) {
Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
}
mSignalStrength = signalStrength;
updateTelephony();
}
@Override
public void onServiceStateChanged(ServiceState state) {
if (DEBUG) {
Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
+ " dataState=" + state.getDataRegState());
}
mServiceState = state;
updateTelephony();
}
@Override
public void onDataConnectionStateChanged(int state, int networkType) {
if (DEBUG) {
Log.d(mTag, "onDataConnectionStateChanged: state=" + state
+ " type=" + networkType);
}
mDataState = state;
mDataNetType = networkType;
updateTelephony();
}
@Override
public void onDataActivity(int direction) {
if (DEBUG) {
Log.d(mTag, "onDataActivity: direction=" + direction);
}
setActivity(direction);
}
};
static class MobileIconGroup extends SignalController.IconGroup {
final int mDataContentDescription; // mContentDescriptionDataType
final int mDataType;
final boolean mIsWide;
final int[] mQsDataType;
public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
int[] qsDataType) {
super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
qsDiscState, discContentDesc);
mDataContentDescription = dataContentDesc;
mDataType = dataType;
mIsWide = isWide;
mQsDataType = qsDataType;
}
}
static class MobileState extends SignalController.State {
String networkName;
boolean dataSim;
boolean dataConnected;
boolean isEmergency;
boolean airplaneMode;
int inetForNetwork;
@Override
public void copyFrom(State s) {
super.copyFrom(s);
MobileState state = (MobileState) s;
dataSim = state.dataSim;
networkName = state.networkName;
dataConnected = state.dataConnected;
inetForNetwork = state.inetForNetwork;
isEmergency = state.isEmergency;
airplaneMode = state.airplaneMode;
}
@Override
protected void toString(StringBuilder builder) {
super.toString(builder);
builder.append(',');
builder.append("dataSim=").append(dataSim).append(',');
builder.append("networkName=").append(networkName).append(',');
builder.append("dataConnected=").append(dataConnected).append(',');
builder.append("inetForNetwork=").append(inetForNetwork).append(',');
builder.append("isEmergency=").append(isEmergency).append(',');
builder.append("airplaneMode=").append(airplaneMode);
}
@Override
public boolean equals(Object o) {
return super.equals(o)
&& Objects.equals(((MobileState) o).networkName, networkName)
&& ((MobileState) o).dataSim == dataSim
&& ((MobileState) o).dataConnected == dataConnected
&& ((MobileState) o).isEmergency == isEmergency
&& ((MobileState) o).airplaneMode == airplaneMode
&& ((MobileState) o).inetForNetwork == inetForNetwork;
}
}
}
/**
* Common base class for handling signal for both wifi and mobile data.
*/
static abstract class SignalController<T extends SignalController.State,
I extends SignalController.IconGroup> {
protected final String mTag;
protected final T mCurrentState;
protected final T mLastState;
protected final int mTransportType;
protected final Context mContext;
// The owner of the SignalController (i.e. NetworkController will maintain the following
// lists and call notifyListeners whenever the list has changed to ensure everyone
// is aware of current state.
protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks;
protected final List<SignalCluster> mSignalClusters;
protected final NetworkControllerImpl mNetworkController;
// Save the previous HISTORY_SIZE states for logging.
private final State[] mHistory;
// Where to copy the next state into.
private int mHistoryIndex;
public SignalController(String tag, Context context, int type,
List<NetworkSignalChangedCallback> signalCallbacks,
List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
mTag = TAG + "." + tag;
mNetworkController = networkController;
mTransportType = type;
mContext = context;
mSignalsChangedCallbacks = signalCallbacks;
mSignalClusters = signalClusters;
mCurrentState = cleanState();
mLastState = cleanState();
if (RECORD_HISTORY) {
mHistory = new State[HISTORY_SIZE];
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistory[i] = cleanState();
}
}
}
public T getState() {
return mCurrentState;
}
public int getTransportType() {
return mTransportType;
}
public void setInetCondition(int inetCondition) {
mCurrentState.inetCondition = inetCondition;
notifyListenersIfNecessary();
}
/**
* Used at the end of demo mode to clear out any ugly state that it has created.
* Since we haven't had any callbacks, then isDirty will not have been triggered,
* so we can just take the last good state directly from there.
*
* Used for demo mode.
*/
void resetLastState() {
mCurrentState.copyFrom(mLastState);
}
/**
* Determines if the state of this signal controller has changed and
* needs to trigger callbacks related to it.
*/
public boolean isDirty() {
if (!mLastState.equals(mCurrentState)) {
if (DEBUG) {
Log.d(mTag, "Change in state from: " + mLastState + "\n"
+ "\tto: " + mCurrentState);
}
return true;
}
return false;
}
public void saveLastState() {
if (RECORD_HISTORY) {
recordLastState();
}
// Updates the current time.
mCurrentState.time = System.currentTimeMillis();
mLastState.copyFrom(mCurrentState);
}
/**
* Gets the signal icon for QS based on current state of connected, enabled, and level.
*/
public int getQsCurrentIconId() {
if (mCurrentState.connected) {
return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
} else if (mCurrentState.enabled) {
return getIcons().mQsDiscState;
} else {
return getIcons().mQsNullState;
}
}
/**
* Gets the signal icon for SB based on current state of connected, enabled, and level.
*/
public int getCurrentIconId() {
if (mCurrentState.connected) {
return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
} else if (mCurrentState.enabled) {
return getIcons().mSbDiscState;
} else {
return getIcons().mSbNullState;
}
}
/**
* Gets the content description id for the signal based on current state of connected and
* level.
*/
public int getContentDescription() {
if (mCurrentState.connected) {
return getIcons().mContentDesc[mCurrentState.level];
} else {
return getIcons().mDiscContentDesc;
}
}
public void notifyListenersIfNecessary() {
if (isDirty()) {
saveLastState();
notifyListeners();
mNetworkController.refreshCarrierLabel();
}
}
/**
* Returns the resource if resId is not 0, and an empty string otherwise.
*/
protected String getStringIfExists(int resId) {
return resId != 0 ? mContext.getString(resId) : "";
}
protected I getIcons() {
return (I) mCurrentState.iconGroup;
}
/**
* Saves the last state of any changes, so we can log the current
* and last value of any state data.
*/
protected void recordLastState() {
mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
}
public void dump(PrintWriter pw) {
pw.println(" - " + mTag + " -----");
pw.println(" Current State: " + mCurrentState);
if (RECORD_HISTORY) {
// Count up the states that actually contain time stamps, and only display those.
int size = 0;
for (int i = 0; i < HISTORY_SIZE; i++) {
if (mHistory[i].time != 0) size++;
}
// Print out the previous states in ordered number.
for (int i = mHistoryIndex + HISTORY_SIZE - 1;
i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + ": "
+ mHistory[i & (HISTORY_SIZE - 1)]);
}
}
}
/**
* Trigger callbacks based on current state. The callbacks should be completely
* based on current state, and only need to be called in the scenario where
* mCurrentState != mLastState.
*/
public abstract void notifyListeners();
/**
* Generate a blank T.
*/
protected abstract T cleanState();
/*
* Holds icons for a given state. Arrays are generally indexed as inet
* state (full connectivity or not) first, and second dimension as
* signal strength.
*/
static class IconGroup {
final int[][] mSbIcons;
final int[][] mQsIcons;
final int[] mContentDesc;
final int mSbNullState;
final int mQsNullState;
final int mSbDiscState;
final int mQsDiscState;
final int mDiscContentDesc;
// For logging.
final String mName;
public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
int discContentDesc) {
mName = name;
mSbIcons = sbIcons;
mQsIcons = qsIcons;
mContentDesc = contentDesc;
mSbNullState = sbNullState;
mQsNullState = qsNullState;
mSbDiscState = sbDiscState;
mQsDiscState = qsDiscState;
mDiscContentDesc = discContentDesc;
}
@Override
public String toString() {
return "IconGroup(" + mName + ")";
}
}
static class State {
boolean connected;
boolean enabled;
boolean activityIn;
boolean activityOut;
int level;
IconGroup iconGroup;
int inetCondition;
int rssi; // Only for logging.
// Not used for comparison, just used for logging.
long time;
public void copyFrom(State state) {
connected = state.connected;
enabled = state.enabled;
level = state.level;
iconGroup = state.iconGroup;
inetCondition = state.inetCondition;
activityIn = state.activityIn;
activityOut = state.activityOut;
rssi = state.rssi;
time = state.time;
}
@Override
public String toString() {
if (time != 0) {
StringBuilder builder = new StringBuilder();
toString(builder);
return builder.toString();
} else {
return "Empty " + getClass().getSimpleName();
}
}
protected void toString(StringBuilder builder) {
builder.append("connected=").append(connected).append(',')
.append("enabled=").append(enabled).append(',')
.append("level=").append(level).append(',')
.append("inetCondition=").append(inetCondition).append(',')
.append("iconGroup=").append(iconGroup).append(',')
.append("activityIn=").append(activityIn).append(',')
.append("activityOut=").append(activityOut).append(',')
.append("rssi=").append(rssi).append(',')
.append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time));
}
@Override
public boolean equals(Object o) {
if (!o.getClass().equals(getClass())) {
return false;
}
State other = (State) o;
return other.connected == connected
&& other.enabled == enabled
&& other.level == level
&& other.inetCondition == inetCondition
&& other.iconGroup == iconGroup
&& other.activityIn == activityIn
&& other.activityOut == activityOut
&& other.rssi == rssi;
}
}
}
public interface SignalCluster {
void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
String contentDescription, String typeContentDescription, boolean isTypeIconWide,
int subId);
void setSubs(List<SubscriptionInfo> subs);
void setNoSims(boolean show);
void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription);
}
public interface EmergencyListener {
void setEmergencyCallsOnly(boolean emergencyOnly);
}
public interface CarrierLabelListener {
void setCarrierLabel(String label);
}
@VisibleForTesting
static class Config {
boolean showAtLeast3G = false;
boolean alwaysShowCdmaRssi = false;
boolean show4gForLte = false;
boolean hspaDataDistinguishable;
static Config readConfig(Context context) {
Config config = new Config();
Resources res = context.getResources();
config.showAtLeast3G = res.getBoolean(R.bool.config_showMin3G);
config.alwaysShowCdmaRssi =
res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi);
config.show4gForLte = res.getBoolean(R.bool.config_show4GForLTE);
config.hspaDataDistinguishable =
res.getBoolean(R.bool.config_hspa_data_distinguishable);
return config;
}
}
}