Jeff Sharkey cdd02c5d76 Collect and persist tethering stats.
Use new "gettetherstats" netd command to retrieve statistics for
active tethering connections.  Keep tethering poll events separate
from UID poll, even though they end up same historical structures.

Bug: 5244846
Change-Id: Ia0c5165f6712c12b51586f86c331a2aad4ad6afb
2011-09-16 16:05:51 -07:00

2843 lines
110 KiB
Java

/*
* Copyright (C) 2008 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.server;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
import android.net.IConnectivityManager;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.MobileDataStateTracker;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkQuotaInfo;
import android.net.NetworkState;
import android.net.NetworkStateTracker;
import android.net.NetworkUtils;
import android.net.Proxy;
import android.net.ProxyProperties;
import android.net.RouteInfo;
import android.net.wifi.WifiStateTracker;
import android.os.Binder;
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseIntArray;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.telephony.Phone;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
/**
* @hide
*/
public class ConnectivityService extends IConnectivityManager.Stub {
private static final boolean DBG = true;
private static final boolean VDBG = true;
private static final String TAG = "ConnectivityService";
private static final boolean LOGD_RULES = false;
// how long to wait before switching back to a radio's default network
private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
// system property that can override the above value
private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
"android.telephony.apn-restore";
// used in recursive route setting to add gateways for the host for which
// a host route was requested.
private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
private Vpn mVpn;
/** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
private Object mRulesLock = new Object();
/** Currently active network rules by UID. */
private SparseIntArray mUidRules = new SparseIntArray();
/** Set of ifaces that are costly. */
private HashSet<String> mMeteredIfaces = Sets.newHashSet();
/**
* Sometimes we want to refer to the individual network state
* trackers separately, and sometimes we just want to treat them
* abstractly.
*/
private NetworkStateTracker mNetTrackers[];
/**
* The link properties that define the current links
*/
private LinkProperties mCurrentLinkProperties[];
/**
* A per Net list of the PID's that requested access to the net
* used both as a refcount and for per-PID DNS selection
*/
private List mNetRequestersPids[];
// priority order of the nettrackers
// (excluding dynamically set mNetworkPreference)
// TODO - move mNetworkTypePreference into this
private int[] mPriorityList;
private Context mContext;
private int mNetworkPreference;
private int mActiveDefaultNetwork = -1;
// 0 is full bad, 100 is full good
private int mDefaultInetCondition = 0;
private int mDefaultInetConditionPublished = 0;
private boolean mInetConditionChangeInFlight = false;
private int mDefaultConnectionSequence = 0;
private Object mDnsLock = new Object();
private int mNumDnsEntries;
private boolean mDnsOverridden = false;
private boolean mTestMode;
private static ConnectivityService sServiceInstance;
private INetworkManagementService mNetd;
private INetworkPolicyManager mPolicyManager;
private static final int ENABLED = 1;
private static final int DISABLED = 0;
// Share the event space with NetworkStateTracker (which can't see this
// internal class but sends us events). If you change these, change
// NetworkStateTracker.java too.
private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
/**
* used internally as a delayed event to make us switch back to the
* default network
*/
private static final int EVENT_RESTORE_DEFAULT_NETWORK =
MAX_NETWORK_STATE_TRACKER_EVENT + 1;
/**
* used internally to change our mobile data enabled flag
*/
private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED =
MAX_NETWORK_STATE_TRACKER_EVENT + 2;
/**
* used internally to change our network preference setting
* arg1 = networkType to prefer
*/
private static final int EVENT_SET_NETWORK_PREFERENCE =
MAX_NETWORK_STATE_TRACKER_EVENT + 3;
/**
* used internally to synchronize inet condition reports
* arg1 = networkType
* arg2 = condition (0 bad, 100 good)
*/
private static final int EVENT_INET_CONDITION_CHANGE =
MAX_NETWORK_STATE_TRACKER_EVENT + 4;
/**
* used internally to mark the end of inet condition hold periods
* arg1 = networkType
*/
private static final int EVENT_INET_CONDITION_HOLD_END =
MAX_NETWORK_STATE_TRACKER_EVENT + 5;
/**
* used internally to set enable/disable cellular data
* arg1 = ENBALED or DISABLED
*/
private static final int EVENT_SET_MOBILE_DATA =
MAX_NETWORK_STATE_TRACKER_EVENT + 7;
/**
* used internally to clear a wakelock when transitioning
* from one net to another
*/
private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK =
MAX_NETWORK_STATE_TRACKER_EVENT + 8;
/**
* used internally to reload global proxy settings
*/
private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY =
MAX_NETWORK_STATE_TRACKER_EVENT + 9;
/**
* used internally to set external dependency met/unmet
* arg1 = ENABLED (met) or DISABLED (unmet)
* arg2 = NetworkType
*/
private static final int EVENT_SET_DEPENDENCY_MET =
MAX_NETWORK_STATE_TRACKER_EVENT + 10;
/**
* used internally to restore DNS properties back to the
* default network
*/
private static final int EVENT_RESTORE_DNS =
MAX_NETWORK_STATE_TRACKER_EVENT + 11;
/**
* used internally to send a sticky broadcast delayed.
*/
private static final int EVENT_SEND_STICKY_BROADCAST_INTENT =
MAX_NETWORK_STATE_TRACKER_EVENT + 12;
/**
* Used internally to
* {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
*/
private static final int EVENT_SET_POLICY_DATA_ENABLE = MAX_NETWORK_STATE_TRACKER_EVENT + 13;
private Handler mHandler;
// list of DeathRecipients used to make sure features are turned off when
// a process dies
private List<FeatureUser> mFeatureUsers;
private boolean mSystemReady;
private Intent mInitialBroadcast;
private PowerManager.WakeLock mNetTransitionWakeLock;
private String mNetTransitionWakeLockCausedBy = "";
private int mNetTransitionWakeLockSerialNumber;
private int mNetTransitionWakeLockTimeout;
private InetAddress mDefaultDns;
// this collection is used to refcount the added routes - if there are none left
// it's time to remove the route from the route table
private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
// used in DBG mode to track inet condition reports
private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
private ArrayList mInetLog;
// track the current default http proxy - tell the world if we get a new one (real change)
private ProxyProperties mDefaultProxy = null;
// track the global proxy.
private ProxyProperties mGlobalProxy = null;
private final Object mGlobalProxyLock = new Object();
private SettingsObserver mSettingsObserver;
NetworkConfig[] mNetConfigs;
int mNetworksDefined;
private static class RadioAttributes {
public int mSimultaneity;
public int mType;
public RadioAttributes(String init) {
String fragments[] = init.split(",");
mType = Integer.parseInt(fragments[0]);
mSimultaneity = Integer.parseInt(fragments[1]);
}
}
RadioAttributes[] mRadioAttributes;
// the set of network types that can only be enabled by system/sig apps
List mProtectedNetworks;
public ConnectivityService(
Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
if (DBG) log("ConnectivityService starting up");
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
handlerThread.start();
mHandler = new MyHandler(handlerThread.getLooper());
// setup our unique device name
if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
String id = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
if (id != null && id.length() > 0) {
String name = new String("android_").concat(id);
SystemProperties.set("net.hostname", name);
}
}
// read our default dns server ip
String dns = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.DEFAULT_DNS_SERVER);
if (dns == null || dns.length() == 0) {
dns = context.getResources().getString(
com.android.internal.R.string.config_default_dns_server);
}
try {
mDefaultDns = NetworkUtils.numericToInetAddress(dns);
} catch (IllegalArgumentException e) {
loge("Error setting defaultDns using " + dns);
}
mContext = checkNotNull(context, "missing Context");
mNetd = checkNotNull(netd, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
try {
mPolicyManager.registerListener(mPolicyListener);
} catch (RemoteException e) {
// ouch, no rules updates means some processes may never get network
loge("unable to register INetworkPolicyListener" + e.toString());
}
final PowerManager powerManager = (PowerManager) context.getSystemService(
Context.POWER_SERVICE);
mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_networkTransitionTimeout);
mNetTrackers = new NetworkStateTracker[
ConnectivityManager.MAX_NETWORK_TYPE+1];
mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
mNetworkPreference = getPersistedNetworkPreference();
mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
// Load device network attributes from resources
String[] raStrings = context.getResources().getStringArray(
com.android.internal.R.array.radioAttributes);
for (String raString : raStrings) {
RadioAttributes r = new RadioAttributes(raString);
if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) {
loge("Error in radioAttributes - ignoring attempt to define type " + r.mType);
continue;
}
if (mRadioAttributes[r.mType] != null) {
loge("Error in radioAttributes - ignoring attempt to redefine type " +
r.mType);
continue;
}
mRadioAttributes[r.mType] = r;
}
String[] naStrings = context.getResources().getStringArray(
com.android.internal.R.array.networkAttributes);
for (String naString : naStrings) {
try {
NetworkConfig n = new NetworkConfig(naString);
if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
loge("Error in networkAttributes - ignoring attempt to define type " +
n.type);
continue;
}
if (mNetConfigs[n.type] != null) {
loge("Error in networkAttributes - ignoring attempt to redefine type " +
n.type);
continue;
}
if (mRadioAttributes[n.radio] == null) {
loge("Error in networkAttributes - ignoring attempt to use undefined " +
"radio " + n.radio + " in network type " + n.type);
continue;
}
mNetConfigs[n.type] = n;
mNetworksDefined++;
} catch(Exception e) {
// ignore it - leave the entry null
}
}
mProtectedNetworks = new ArrayList<Integer>();
int[] protectedNetworks = context.getResources().getIntArray(
com.android.internal.R.array.config_protectedNetworks);
for (int p : protectedNetworks) {
if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
mProtectedNetworks.add(p);
} else {
if (DBG) loge("Ignoring protectedNetwork " + p);
}
}
// high priority first
mPriorityList = new int[mNetworksDefined];
{
int insertionPoint = mNetworksDefined-1;
int currentLowest = 0;
int nextLowest = 0;
while (insertionPoint > -1) {
for (NetworkConfig na : mNetConfigs) {
if (na == null) continue;
if (na.priority < currentLowest) continue;
if (na.priority > currentLowest) {
if (na.priority < nextLowest || nextLowest == 0) {
nextLowest = na.priority;
}
continue;
}
mPriorityList[insertionPoint--] = na.type;
}
currentLowest = nextLowest;
nextLowest = 0;
}
}
mNetRequestersPids = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
for (int i : mPriorityList) {
mNetRequestersPids[i] = new ArrayList();
}
mFeatureUsers = new ArrayList<FeatureUser>();
mNumDnsEntries = 0;
mTestMode = SystemProperties.get("cm.test.mode").equals("true")
&& SystemProperties.get("ro.build.type").equals("eng");
/*
* Create the network state trackers for Wi-Fi and mobile
* data. Maybe this could be done with a factory class,
* but it's not clear that it's worth it, given that
* the number of different network types is not going
* to change very often.
*/
for (int netType : mPriorityList) {
switch (mNetConfigs[netType].radio) {
case ConnectivityManager.TYPE_WIFI:
mNetTrackers[netType] = new WifiStateTracker(netType,
mNetConfigs[netType].name);
mNetTrackers[netType].startMonitoring(context, mHandler);
break;
case ConnectivityManager.TYPE_MOBILE:
mNetTrackers[netType] = new MobileDataStateTracker(netType,
mNetConfigs[netType].name);
mNetTrackers[netType].startMonitoring(context, mHandler);
break;
case ConnectivityManager.TYPE_DUMMY:
mNetTrackers[netType] = new DummyDataStateTracker(netType,
mNetConfigs[netType].name);
mNetTrackers[netType].startMonitoring(context, mHandler);
break;
case ConnectivityManager.TYPE_BLUETOOTH:
mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance();
mNetTrackers[netType].startMonitoring(context, mHandler);
break;
case ConnectivityManager.TYPE_ETHERNET:
mNetTrackers[netType] = EthernetDataTracker.getInstance();
mNetTrackers[netType].startMonitoring(context, mHandler);
break;
default:
loge("Trying to create a DataStateTracker for an unknown radio type " +
mNetConfigs[netType].radio);
continue;
}
mCurrentLinkProperties[netType] = null;
if (mNetConfigs[netType].isDefault()) mNetTrackers[netType].reconnect();
}
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b);
mTethering = new Tethering(mContext, nmService, mHandler.getLooper());
mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
mTethering.getTetherableWifiRegexs().length != 0 ||
mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceTypes().length != 0);
mVpn = new Vpn(mContext, new VpnCallback());
try {
nmService.registerObserver(mTethering);
nmService.registerObserver(mVpn);
} catch (RemoteException e) {
loge("Error registering observer :" + e);
}
if (DBG) {
mInetLog = new ArrayList();
}
mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
mSettingsObserver.observe(mContext);
loadGlobalProxy();
}
/**
* Sets the preferred network.
* @param preference the new preference
*/
public void setNetworkPreference(int preference) {
enforceChangePermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0));
}
public int getNetworkPreference() {
enforceAccessPermission();
int preference;
synchronized(this) {
preference = mNetworkPreference;
}
return preference;
}
private void handleSetNetworkPreference(int preference) {
if (ConnectivityManager.isNetworkTypeValid(preference) &&
mNetConfigs[preference] != null &&
mNetConfigs[preference].isDefault()) {
if (mNetworkPreference != preference) {
final ContentResolver cr = mContext.getContentResolver();
Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, preference);
synchronized(this) {
mNetworkPreference = preference;
}
enforcePreference();
}
}
}
private int getConnectivityChangeDelay() {
final ContentResolver cr = mContext.getContentResolver();
/** Check system properties for the default value then use secure settings value, if any. */
int defaultDelay = SystemProperties.getInt(
"conn." + Settings.Secure.CONNECTIVITY_CHANGE_DELAY,
Settings.Secure.CONNECTIVITY_CHANGE_DELAY_DEFAULT);
return Settings.Secure.getInt(cr, Settings.Secure.CONNECTIVITY_CHANGE_DELAY,
defaultDelay);
}
private int getPersistedNetworkPreference() {
final ContentResolver cr = mContext.getContentResolver();
final int networkPrefSetting = Settings.Secure
.getInt(cr, Settings.Secure.NETWORK_PREFERENCE, -1);
if (networkPrefSetting != -1) {
return networkPrefSetting;
}
return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE;
}
/**
* Make the state of network connectivity conform to the preference settings
* In this method, we only tear down a non-preferred network. Establishing
* a connection to the preferred network is taken care of when we handle
* the disconnect event from the non-preferred network
* (see {@link #handleDisconnect(NetworkInfo)}).
*/
private void enforcePreference() {
if (mNetTrackers[mNetworkPreference].getNetworkInfo().isConnected())
return;
if (!mNetTrackers[mNetworkPreference].isAvailable())
return;
for (int t=0; t <= ConnectivityManager.MAX_RADIO_TYPE; t++) {
if (t != mNetworkPreference && mNetTrackers[t] != null &&
mNetTrackers[t].getNetworkInfo().isConnected()) {
if (DBG) {
log("tearing down " + mNetTrackers[t].getNetworkInfo() +
" in enforcePreference");
}
teardown(mNetTrackers[t]);
}
}
}
private boolean teardown(NetworkStateTracker netTracker) {
if (netTracker.teardown()) {
netTracker.setTeardownRequested(true);
return true;
} else {
return false;
}
}
/**
* Check if UID should be blocked from using the network represented by the
* given {@link NetworkStateTracker}.
*/
private boolean isNetworkBlocked(NetworkStateTracker tracker, int uid) {
final String iface = tracker.getLinkProperties().getInterfaceName();
final boolean networkCostly;
final int uidRules;
synchronized (mRulesLock) {
networkCostly = mMeteredIfaces.contains(iface);
uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
}
if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
return true;
}
// no restrictive rules; network is visible
return false;
}
/**
* Return a filtered {@link NetworkInfo}, potentially marked
* {@link DetailedState#BLOCKED} based on
* {@link #isNetworkBlocked(NetworkStateTracker, int)}.
*/
private NetworkInfo getFilteredNetworkInfo(NetworkStateTracker tracker, int uid) {
NetworkInfo info = tracker.getNetworkInfo();
if (isNetworkBlocked(tracker, uid)) {
// network is blocked; clone and override state
info = new NetworkInfo(info);
info.setDetailedState(DetailedState.BLOCKED, null, null);
}
return info;
}
/**
* Return NetworkInfo for the active (i.e., connected) network interface.
* It is assumed that at most one network is active at a time. If more
* than one is active, it is indeterminate which will be returned.
* @return the info for the active network, or {@code null} if none is
* active
*/
@Override
public NetworkInfo getActiveNetworkInfo() {
enforceAccessPermission();
final int uid = Binder.getCallingUid();
return getNetworkInfo(mActiveDefaultNetwork, uid);
}
@Override
public NetworkInfo getActiveNetworkInfoForUid(int uid) {
enforceConnectivityInternalPermission();
return getNetworkInfo(mActiveDefaultNetwork, uid);
}
@Override
public NetworkInfo getNetworkInfo(int networkType) {
enforceAccessPermission();
final int uid = Binder.getCallingUid();
return getNetworkInfo(networkType, uid);
}
private NetworkInfo getNetworkInfo(int networkType, int uid) {
NetworkInfo info = null;
if (isNetworkTypeValid(networkType)) {
final NetworkStateTracker tracker = mNetTrackers[networkType];
if (tracker != null) {
info = getFilteredNetworkInfo(tracker, uid);
}
}
return info;
}
@Override
public NetworkInfo[] getAllNetworkInfo() {
enforceAccessPermission();
final int uid = Binder.getCallingUid();
final ArrayList<NetworkInfo> result = Lists.newArrayList();
synchronized (mRulesLock) {
for (NetworkStateTracker tracker : mNetTrackers) {
if (tracker != null) {
result.add(getFilteredNetworkInfo(tracker, uid));
}
}
}
return result.toArray(new NetworkInfo[result.size()]);
}
@Override
public boolean isNetworkSupported(int networkType) {
enforceAccessPermission();
return (isNetworkTypeValid(networkType) && (mNetTrackers[networkType] != null));
}
/**
* Return LinkProperties for the active (i.e., connected) default
* network interface. It is assumed that at most one default network
* is active at a time. If more than one is active, it is indeterminate
* which will be returned.
* @return the ip properties for the active network, or {@code null} if
* none is active
*/
@Override
public LinkProperties getActiveLinkProperties() {
return getLinkProperties(mActiveDefaultNetwork);
}
@Override
public LinkProperties getLinkProperties(int networkType) {
enforceAccessPermission();
if (isNetworkTypeValid(networkType)) {
final NetworkStateTracker tracker = mNetTrackers[networkType];
if (tracker != null) {
return tracker.getLinkProperties();
}
}
return null;
}
@Override
public NetworkState[] getAllNetworkState() {
enforceAccessPermission();
final int uid = Binder.getCallingUid();
final ArrayList<NetworkState> result = Lists.newArrayList();
synchronized (mRulesLock) {
for (NetworkStateTracker tracker : mNetTrackers) {
if (tracker != null) {
final NetworkInfo info = getFilteredNetworkInfo(tracker, uid);
result.add(new NetworkState(
info, tracker.getLinkProperties(), tracker.getLinkCapabilities()));
}
}
}
return result.toArray(new NetworkState[result.size()]);
}
private NetworkState getNetworkStateUnchecked(int networkType) {
if (isNetworkTypeValid(networkType)) {
final NetworkStateTracker tracker = mNetTrackers[networkType];
if (tracker != null) {
return new NetworkState(tracker.getNetworkInfo(), tracker.getLinkProperties(),
tracker.getLinkCapabilities());
}
}
return null;
}
@Override
public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
enforceAccessPermission();
final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
if (state != null) {
try {
return mPolicyManager.getNetworkQuotaInfo(state);
} catch (RemoteException e) {
}
}
return null;
}
public boolean setRadios(boolean turnOn) {
boolean result = true;
enforceChangePermission();
for (NetworkStateTracker t : mNetTrackers) {
if (t != null) result = t.setRadio(turnOn) && result;
}
return result;
}
public boolean setRadio(int netType, boolean turnOn) {
enforceChangePermission();
if (!ConnectivityManager.isNetworkTypeValid(netType)) {
return false;
}
NetworkStateTracker tracker = mNetTrackers[netType];
return tracker != null && tracker.setRadio(turnOn);
}
/**
* Used to notice when the calling process dies so we can self-expire
*
* Also used to know if the process has cleaned up after itself when
* our auto-expire timer goes off. The timer has a link to an object.
*
*/
private class FeatureUser implements IBinder.DeathRecipient {
int mNetworkType;
String mFeature;
IBinder mBinder;
int mPid;
int mUid;
long mCreateTime;
FeatureUser(int type, String feature, IBinder binder) {
super();
mNetworkType = type;
mFeature = feature;
mBinder = binder;
mPid = getCallingPid();
mUid = getCallingUid();
mCreateTime = System.currentTimeMillis();
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
}
void unlinkDeathRecipient() {
mBinder.unlinkToDeath(this, 0);
}
public void binderDied() {
log("ConnectivityService FeatureUser binderDied(" +
mNetworkType + ", " + mFeature + ", " + mBinder + "), created " +
(System.currentTimeMillis() - mCreateTime) + " mSec ago");
stopUsingNetworkFeature(this, false);
}
public void expire() {
if (VDBG) {
log("ConnectivityService FeatureUser expire(" +
mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
(System.currentTimeMillis() - mCreateTime) + " mSec ago");
}
stopUsingNetworkFeature(this, false);
}
public boolean isSameUser(FeatureUser u) {
if (u == null) return false;
return isSameUser(u.mPid, u.mUid, u.mNetworkType, u.mFeature);
}
public boolean isSameUser(int pid, int uid, int networkType, String feature) {
if ((mPid == pid) && (mUid == uid) && (mNetworkType == networkType) &&
TextUtils.equals(mFeature, feature)) {
return true;
}
return false;
}
public String toString() {
return "FeatureUser("+mNetworkType+","+mFeature+","+mPid+","+mUid+"), created " +
(System.currentTimeMillis() - mCreateTime) + " mSec ago";
}
}
// javadoc from interface
public int startUsingNetworkFeature(int networkType, String feature,
IBinder binder) {
if (DBG) {
log("startUsingNetworkFeature for net " + networkType + ": " + feature);
}
enforceChangePermission();
if (!ConnectivityManager.isNetworkTypeValid(networkType) ||
mNetConfigs[networkType] == null) {
return Phone.APN_REQUEST_FAILED;
}
FeatureUser f = new FeatureUser(networkType, feature, binder);
// TODO - move this into individual networktrackers
int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
if (mProtectedNetworks.contains(usedNetworkType)) {
enforceConnectivityInternalPermission();
}
NetworkStateTracker network = mNetTrackers[usedNetworkType];
if (network != null) {
Integer currentPid = new Integer(getCallingPid());
if (usedNetworkType != networkType) {
NetworkInfo ni = network.getNetworkInfo();
if (ni.isAvailable() == false) {
if (DBG) log("special network not available");
if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
return Phone.APN_TYPE_NOT_AVAILABLE;
} else {
// else make the attempt anyway - probably giving REQUEST_STARTED below
}
}
int restoreTimer = getRestoreDefaultNetworkDelay(usedNetworkType);
synchronized(this) {
boolean addToList = true;
if (restoreTimer < 0) {
// In case there is no timer is specified for the feature,
// make sure we don't add duplicate entry with the same request.
for (FeatureUser u : mFeatureUsers) {
if (u.isSameUser(f)) {
// Duplicate user is found. Do not add.
addToList = false;
break;
}
}
}
if (addToList) mFeatureUsers.add(f);
if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
// this gets used for per-pid dns when connected
mNetRequestersPids[usedNetworkType].add(currentPid);
}
}
if (restoreTimer >= 0) {
mHandler.sendMessageDelayed(
mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, f), restoreTimer);
}
if ((ni.isConnectedOrConnecting() == true) &&
!network.isTeardownRequested()) {
if (ni.isConnected() == true) {
// add the pid-specific dns
handleDnsConfigurationChange(usedNetworkType);
if (DBG) log("special network already active");
return Phone.APN_ALREADY_ACTIVE;
}
if (DBG) log("special network already connecting");
return Phone.APN_REQUEST_STARTED;
}
// check if the radio in play can make another contact
// assume if cannot for now
if (DBG) log("reconnecting to special network");
network.reconnect();
return Phone.APN_REQUEST_STARTED;
} else {
// need to remember this unsupported request so we respond appropriately on stop
synchronized(this) {
mFeatureUsers.add(f);
if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
// this gets used for per-pid dns when connected
mNetRequestersPids[usedNetworkType].add(currentPid);
}
}
return -1;
}
}
return Phone.APN_TYPE_NOT_AVAILABLE;
}
// javadoc from interface
public int stopUsingNetworkFeature(int networkType, String feature) {
enforceChangePermission();
int pid = getCallingPid();
int uid = getCallingUid();
FeatureUser u = null;
boolean found = false;
synchronized(this) {
for (FeatureUser x : mFeatureUsers) {
if (x.isSameUser(pid, uid, networkType, feature)) {
u = x;
found = true;
break;
}
}
}
if (found && u != null) {
// stop regardless of how many other time this proc had called start
return stopUsingNetworkFeature(u, true);
} else {
// none found!
if (VDBG) log("ignoring stopUsingNetworkFeature - not a live request");
return 1;
}
}
private int stopUsingNetworkFeature(FeatureUser u, boolean ignoreDups) {
int networkType = u.mNetworkType;
String feature = u.mFeature;
int pid = u.mPid;
int uid = u.mUid;
NetworkStateTracker tracker = null;
boolean callTeardown = false; // used to carry our decision outside of sync block
if (DBG) {
log("stopUsingNetworkFeature for net " + networkType +
": " + feature);
}
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
return -1;
}
// need to link the mFeatureUsers list with the mNetRequestersPids state in this
// sync block
synchronized(this) {
// check if this process still has an outstanding start request
if (!mFeatureUsers.contains(u)) {
if (DBG) log("ignoring - this process has no outstanding requests");
return 1;
}
u.unlinkDeathRecipient();
mFeatureUsers.remove(mFeatureUsers.indexOf(u));
// If we care about duplicate requests, check for that here.
//
// This is done to support the extension of a request - the app
// can request we start the network feature again and renew the
// auto-shutoff delay. Normal "stop" calls from the app though
// do not pay attention to duplicate requests - in effect the
// API does not refcount and a single stop will counter multiple starts.
if (ignoreDups == false) {
for (FeatureUser x : mFeatureUsers) {
if (x.isSameUser(u)) {
if (DBG) log("ignoring stopUsingNetworkFeature as dup is found");
return 1;
}
}
}
// TODO - move to individual network trackers
int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
tracker = mNetTrackers[usedNetworkType];
if (tracker == null) {
if (DBG) log("ignoring - no known tracker for net type " + usedNetworkType);
return -1;
}
if (usedNetworkType != networkType) {
Integer currentPid = new Integer(pid);
mNetRequestersPids[usedNetworkType].remove(currentPid);
reassessPidDns(pid, true);
if (mNetRequestersPids[usedNetworkType].size() != 0) {
if (DBG) log("not tearing down special network - " +
"others still using it");
return 1;
}
callTeardown = true;
} else {
if (DBG) log("not a known feature - dropping");
}
}
if (DBG) log("Doing network teardown");
if (callTeardown) {
tracker.teardown();
return 1;
} else {
return -1;
}
}
/**
* @deprecated use requestRouteToHostAddress instead
*
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface.
* @param networkType the type of the network over which traffic to the
* specified host is to be routed
* @param hostAddress the IP address of the host to which the route is
* desired
* @return {@code true} on success, {@code false} on failure
*/
public boolean requestRouteToHost(int networkType, int hostAddress) {
InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
if (inetAddress == null) {
return false;
}
return requestRouteToHostAddress(networkType, inetAddress.getAddress());
}
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface.
* @param networkType the type of the network over which traffic to the
* specified host is to be routed
* @param hostAddress the IP address of the host to which the route is
* desired
* @return {@code true} on success, {@code false} on failure
*/
public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
enforceChangePermission();
if (mProtectedNetworks.contains(networkType)) {
enforceConnectivityInternalPermission();
}
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
return false;
}
NetworkStateTracker tracker = mNetTrackers[networkType];
if (tracker == null || !tracker.getNetworkInfo().isConnected() ||
tracker.isTeardownRequested()) {
if (VDBG) {
log("requestRouteToHostAddress on down network " +
"(" + networkType + ") - dropped");
}
return false;
}
try {
InetAddress addr = InetAddress.getByAddress(hostAddress);
LinkProperties lp = tracker.getLinkProperties();
return addRouteToAddress(lp, addr);
} catch (UnknownHostException e) {}
return false;
}
private boolean addRoute(LinkProperties p, RouteInfo r) {
return modifyRoute(p.getInterfaceName(), p, r, 0, true);
}
private boolean removeRoute(LinkProperties p, RouteInfo r) {
return modifyRoute(p.getInterfaceName(), p, r, 0, false);
}
private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) {
return modifyRouteToAddress(lp, addr, true);
}
private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) {
return modifyRouteToAddress(lp, addr, false);
}
private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd) {
RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr);
if (bestRoute == null) {
bestRoute = RouteInfo.makeHostRoute(addr);
} else {
if (bestRoute.getGateway().equals(addr)) {
// if there is no better route, add the implied hostroute for our gateway
bestRoute = RouteInfo.makeHostRoute(addr);
} else {
// if we will connect to this through another route, add a direct route
// to it's gateway
bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway());
}
}
return modifyRoute(lp.getInterfaceName(), lp, bestRoute, 0, doAdd);
}
private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount,
boolean doAdd) {
if ((ifaceName == null) || (lp == null) || (r == null)) return false;
if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
loge("Error adding route - too much recursion");
return false;
}
if (r.isHostRoute() == false) {
RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), r.getGateway());
if (bestRoute != null) {
if (bestRoute.getGateway().equals(r.getGateway())) {
// if there is no better route, add the implied hostroute for our gateway
bestRoute = RouteInfo.makeHostRoute(r.getGateway());
} else {
// if we will connect to our gateway through another route, add a direct
// route to it's gateway
bestRoute = RouteInfo.makeHostRoute(r.getGateway(), bestRoute.getGateway());
}
modifyRoute(ifaceName, lp, bestRoute, cycleCount+1, doAdd);
}
}
if (doAdd) {
if (VDBG) log("Adding " + r + " for interface " + ifaceName);
mAddedRoutes.add(r);
try {
mNetd.addRoute(ifaceName, r);
} catch (Exception e) {
// never crash - catch them all
if (VDBG) loge("Exception trying to add a route: " + e);
return false;
}
} else {
// if we remove this one and there are no more like it, then refcount==0 and
// we can remove it from the table
mAddedRoutes.remove(r);
if (mAddedRoutes.contains(r) == false) {
if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
mNetd.removeRoute(ifaceName, r);
} catch (Exception e) {
// never crash - catch them all
if (VDBG) loge("Exception trying to remove a route: " + e);
return false;
}
} else {
if (VDBG) log("not removing " + r + " as it's still in use");
}
}
return true;
}
/**
* @see ConnectivityManager#getMobileDataEnabled()
*/
public boolean getMobileDataEnabled() {
// TODO: This detail should probably be in DataConnectionTracker's
// which is where we store the value and maybe make this
// asynchronous.
enforceAccessPermission();
boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.MOBILE_DATA, 1) == 1;
if (VDBG) log("getMobileDataEnabled returning " + retVal);
return retVal;
}
public void setDataDependency(int networkType, boolean met) {
enforceConnectivityInternalPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_DEPENDENCY_MET,
(met ? ENABLED : DISABLED), networkType));
}
private void handleSetDependencyMet(int networkType, boolean met) {
if (mNetTrackers[networkType] != null) {
if (DBG) {
log("handleSetDependencyMet(" + networkType + ", " + met + ")");
}
mNetTrackers[networkType].setDependencyMet(met);
}
}
private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
@Override
public void onUidRulesChanged(int uid, int uidRules) {
// only someone like NPMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
if (LOGD_RULES) {
log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
}
synchronized (mRulesLock) {
// skip update when we've already applied rules
final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
if (oldRules == uidRules) return;
mUidRules.put(uid, uidRules);
}
// TODO: dispatch into NMS to push rules towards kernel module
// TODO: notify UID when it has requested targeted updates
}
@Override
public void onMeteredIfacesChanged(String[] meteredIfaces) {
// only someone like NPMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
if (LOGD_RULES) {
log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
}
synchronized (mRulesLock) {
mMeteredIfaces.clear();
for (String iface : meteredIfaces) {
mMeteredIfaces.add(iface);
}
}
}
};
/**
* @see ConnectivityManager#setMobileDataEnabled(boolean)
*/
public void setMobileDataEnabled(boolean enabled) {
enforceChangePermission();
if (DBG) log("setMobileDataEnabled(" + enabled + ")");
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA,
(enabled ? ENABLED : DISABLED), 0));
}
private void handleSetMobileData(boolean enabled) {
if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
if (VDBG) {
log(mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
}
mNetTrackers[ConnectivityManager.TYPE_MOBILE].setUserDataEnable(enabled);
}
}
@Override
public void setPolicyDataEnable(int networkType, boolean enabled) {
// only someone like NPMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_SET_POLICY_DATA_ENABLE, networkType, (enabled ? ENABLED : DISABLED)));
}
private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
if (isNetworkTypeValid(networkType)) {
final NetworkStateTracker tracker = mNetTrackers[networkType];
if (tracker != null) {
tracker.setPolicyDataEnable(enabled);
}
}
}
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
"ConnectivityService");
}
private void enforceChangePermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CHANGE_NETWORK_STATE,
"ConnectivityService");
}
// TODO Make this a special check when it goes public
private void enforceTetherChangePermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CHANGE_NETWORK_STATE,
"ConnectivityService");
}
private void enforceTetherAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
"ConnectivityService");
}
private void enforceConnectivityInternalPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL,
"ConnectivityService");
}
/**
* Handle a {@code DISCONNECTED} event. If this pertains to the non-active
* network, we ignore it. If it is for the active network, we send out a
* broadcast. But first, we check whether it might be possible to connect
* to a different network.
* @param info the {@code NetworkInfo} for the network
*/
private void handleDisconnect(NetworkInfo info) {
int prevNetType = info.getType();
mNetTrackers[prevNetType].setTeardownRequested(false);
/*
* If the disconnected network is not the active one, then don't report
* this as a loss of connectivity. What probably happened is that we're
* getting the disconnect for a network that we explicitly disabled
* in accordance with network preference policies.
*/
if (!mNetConfigs[prevNetType].isDefault()) {
List pids = mNetRequestersPids[prevNetType];
for (int i = 0; i<pids.size(); i++) {
Integer pid = (Integer)pids.get(i);
// will remove them because the net's no longer connected
// need to do this now as only now do we know the pids and
// can properly null things that are no longer referenced.
reassessPidDns(pid.intValue(), false);
}
}
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
if (info.isFailover()) {
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
}
if (info.getReason() != null) {
intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
}
if (info.getExtraInfo() != null) {
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
info.getExtraInfo());
}
if (mNetConfigs[prevNetType].isDefault()) {
tryFailover(prevNetType);
if (mActiveDefaultNetwork != -1) {
NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
} else {
mDefaultInetConditionPublished = 0; // we're not connected anymore
intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
}
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
// Reset interface if no other connections are using the same interface
boolean doReset = true;
LinkProperties linkProperties = mNetTrackers[prevNetType].getLinkProperties();
if (linkProperties != null) {
String oldIface = linkProperties.getInterfaceName();
if (TextUtils.isEmpty(oldIface) == false) {
for (NetworkStateTracker networkStateTracker : mNetTrackers) {
if (networkStateTracker == null) continue;
NetworkInfo networkInfo = networkStateTracker.getNetworkInfo();
if (networkInfo.isConnected() && networkInfo.getType() != prevNetType) {
LinkProperties l = networkStateTracker.getLinkProperties();
if (l == null) continue;
if (oldIface.equals(l.getInterfaceName())) {
doReset = false;
break;
}
}
}
}
}
// do this before we broadcast the change
handleConnectivityChange(prevNetType, doReset);
final Intent immediateIntent = new Intent(intent);
immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
sendStickyBroadcast(immediateIntent);
sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
/*
* If the failover network is already connected, then immediately send
* out a followup broadcast indicating successful failover
*/
if (mActiveDefaultNetwork != -1) {
sendConnectedBroadcastDelayed(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(),
getConnectivityChangeDelay());
}
}
private void tryFailover(int prevNetType) {
/*
* If this is a default network, check if other defaults are available.
* Try to reconnect on all available and let them hash it out when
* more than one connects.
*/
if (mNetConfigs[prevNetType].isDefault()) {
if (mActiveDefaultNetwork == prevNetType) {
mActiveDefaultNetwork = -1;
}
// don't signal a reconnect for anything lower or equal priority than our
// current connected default
// TODO - don't filter by priority now - nice optimization but risky
// int currentPriority = -1;
// if (mActiveDefaultNetwork != -1) {
// currentPriority = mNetConfigs[mActiveDefaultNetwork].mPriority;
// }
for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
if (checkType == prevNetType) continue;
if (mNetConfigs[checkType] == null) continue;
if (!mNetConfigs[checkType].isDefault()) continue;
// Enabling the isAvailable() optimization caused mobile to not get
// selected if it was in the middle of error handling. Specifically
// a moble connection that took 30 seconds to complete the DEACTIVATE_DATA_CALL
// would not be available and we wouldn't get connected to anything.
// So removing the isAvailable() optimization below for now. TODO: This
// optimization should work and we need to investigate why it doesn't work.
// This could be related to how DEACTIVATE_DATA_CALL is reporting its
// complete before it is really complete.
// if (!mNetTrackers[checkType].isAvailable()) continue;
// if (currentPriority >= mNetConfigs[checkType].mPriority) continue;
NetworkStateTracker checkTracker = mNetTrackers[checkType];
NetworkInfo checkInfo = checkTracker.getNetworkInfo();
if (!checkInfo.isConnectedOrConnecting() || checkTracker.isTeardownRequested()) {
checkInfo.setFailover(true);
checkTracker.reconnect();
}
if (DBG) log("Attempting to switch to " + checkInfo.getTypeName());
}
}
}
private void sendConnectedBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
private void sendConnectedBroadcastDelayed(NetworkInfo info, int delayMs) {
sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcastDelayed(info, CONNECTIVITY_ACTION, delayMs);
}
private void sendInetConditionBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION);
}
private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
Intent intent = new Intent(bcastType);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
if (info.isFailover()) {
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
}
if (info.getReason() != null) {
intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
}
if (info.getExtraInfo() != null) {
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
info.getExtraInfo());
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
return intent;
}
private void sendGeneralBroadcast(NetworkInfo info, String bcastType) {
sendStickyBroadcast(makeGeneralIntent(info, bcastType));
}
private void sendGeneralBroadcastDelayed(NetworkInfo info, String bcastType, int delayMs) {
sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
}
/**
* Called when an attempt to fail over to another network has failed.
* @param info the {@link NetworkInfo} for the failed network
*/
private void handleConnectionFailure(NetworkInfo info) {
mNetTrackers[info.getType()].setTeardownRequested(false);
String reason = info.getReason();
String extraInfo = info.getExtraInfo();
String reasonText;
if (reason == null) {
reasonText = ".";
} else {
reasonText = " (" + reason + ").";
}
loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
if (getActiveNetworkInfo() == null) {
intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
}
if (reason != null) {
intent.putExtra(ConnectivityManager.EXTRA_REASON, reason);
}
if (extraInfo != null) {
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo);
}
if (info.isFailover()) {
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
}
if (mNetConfigs[info.getType()].isDefault()) {
tryFailover(info.getType());
if (mActiveDefaultNetwork != -1) {
NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
} else {
mDefaultInetConditionPublished = 0;
intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
}
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
final Intent immediateIntent = new Intent(intent);
immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
sendStickyBroadcast(immediateIntent);
sendStickyBroadcast(intent);
/*
* If the failover network is already connected, then immediately send
* out a followup broadcast indicating successful failover
*/
if (mActiveDefaultNetwork != -1) {
sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo());
}
}
private void sendStickyBroadcast(Intent intent) {
synchronized(this) {
if (!mSystemReady) {
mInitialBroadcast = new Intent(intent);
}
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
if (DBG) {
log("sendStickyBroadcast: action=" + intent.getAction());
}
mContext.sendStickyBroadcast(intent);
}
}
private void sendStickyBroadcastDelayed(Intent intent, int delayMs) {
if (delayMs <= 0) {
sendStickyBroadcast(intent);
} else {
if (DBG) {
log("sendStickyBroadcastDelayed: delayMs=" + delayMs + ", action="
+ intent.getAction());
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(
EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
}
}
void systemReady() {
synchronized(this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
mContext.sendStickyBroadcast(mInitialBroadcast);
mInitialBroadcast = null;
}
}
// load the global proxy at startup
mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
}
private void handleConnect(NetworkInfo info) {
int type = info.getType();
// snapshot isFailover, because sendConnectedBroadcast() resets it
boolean isFailover = info.isFailover();
NetworkStateTracker thisNet = mNetTrackers[type];
// if this is a default net and other default is running
// kill the one not preferred
if (mNetConfigs[type].isDefault()) {
if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
if ((type != mNetworkPreference &&
mNetConfigs[mActiveDefaultNetwork].priority >
mNetConfigs[type].priority) ||
mNetworkPreference == mActiveDefaultNetwork) {
// don't accept this one
if (VDBG) {
log("Not broadcasting CONNECT_ACTION " +
"to torn down network " + info.getTypeName());
}
teardown(thisNet);
return;
} else {
// tear down the other
NetworkStateTracker otherNet =
mNetTrackers[mActiveDefaultNetwork];
if (DBG) {
log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
" teardown");
}
if (!teardown(otherNet)) {
loge("Network declined teardown request");
teardown(thisNet);
return;
}
}
}
synchronized (ConnectivityService.this) {
// have a new default network, release the transition wakelock in a second
// if it's held. The second pause is to allow apps to reconnect over the
// new network
if (mNetTransitionWakeLock.isHeld()) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(
EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
mNetTransitionWakeLockSerialNumber, 0),
1000);
}
}
mActiveDefaultNetwork = type;
// this will cause us to come up initially as unconnected and switching
// to connected after our normal pause unless somebody reports us as reall
// disconnected
mDefaultInetConditionPublished = 0;
mDefaultConnectionSequence++;
mInetConditionChangeInFlight = false;
// Don't do this - if we never sign in stay, grey
//reportNetworkCondition(mActiveDefaultNetwork, 100);
}
thisNet.setTeardownRequested(false);
updateNetworkSettings(thisNet);
handleConnectivityChange(type, false);
sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
}
/**
* After a change in the connectivity state of a network. We're mainly
* concerned with making sure that the list of DNS servers is set up
* according to which networks are connected, and ensuring that the
* right routing table entries exist.
*/
private void handleConnectivityChange(int netType, boolean doReset) {
int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
/*
* If a non-default network is enabled, add the host routes that
* will allow it's DNS servers to be accessed.
*/
handleDnsConfigurationChange(netType);
LinkProperties curLp = mCurrentLinkProperties[netType];
LinkProperties newLp = null;
if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
newLp = mNetTrackers[netType].getLinkProperties();
if (VDBG) {
log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
" doReset=" + doReset + " resetMask=" + resetMask +
"\n curLp=" + curLp +
"\n newLp=" + newLp);
}
if (curLp != null) {
if (curLp.isIdenticalInterfaceName(newLp)) {
CompareResult<LinkAddress> car = curLp.compareAddresses(newLp);
if ((car.removed.size() != 0) || (car.added.size() != 0)) {
for (LinkAddress linkAddr : car.removed) {
if (linkAddr.getAddress() instanceof Inet4Address) {
resetMask |= NetworkUtils.RESET_IPV4_ADDRESSES;
}
if (linkAddr.getAddress() instanceof Inet6Address) {
resetMask |= NetworkUtils.RESET_IPV6_ADDRESSES;
}
}
if (DBG) {
log("handleConnectivityChange: addresses changed" +
" linkProperty[" + netType + "]:" + " resetMask=" + resetMask +
"\n car=" + car);
}
} else {
if (DBG) {
log("handleConnectivityChange: address are the same reset per doReset" +
" linkProperty[" + netType + "]:" +
" resetMask=" + resetMask);
}
}
} else {
resetMask = NetworkUtils.RESET_ALL_ADDRESSES;
if (DBG) {
log("handleConnectivityChange: interface not not equivalent reset both" +
" linkProperty[" + netType + "]:" +
" resetMask=" + resetMask);
}
}
}
if (mNetConfigs[netType].isDefault()) {
handleApplyDefaultProxy(netType);
}
} else {
if (VDBG) {
log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
" doReset=" + doReset + " resetMask=" + resetMask +
"\n curLp=" + curLp +
"\n newLp= null");
}
}
mCurrentLinkProperties[netType] = newLp;
boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
if (resetMask != 0 || resetDns) {
LinkProperties linkProperties = mNetTrackers[netType].getLinkProperties();
if (linkProperties != null) {
String iface = linkProperties.getInterfaceName();
if (TextUtils.isEmpty(iface) == false) {
if (resetMask != 0) {
if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
NetworkUtils.resetConnections(iface, resetMask);
// Tell VPN the interface is down. It is a temporary
// but effective fix to make VPN aware of the change.
if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
mVpn.interfaceStatusChanged(iface, false);
}
}
if (resetDns) {
if (DBG) log("resetting DNS cache for " + iface);
try {
mNetd.flushInterfaceDnsCache(iface);
} catch (Exception e) {
// never crash - catch them all
loge("Exception resetting dns cache: " + e);
}
}
}
}
}
// TODO: Temporary notifying upstread change to Tethering.
// @see bug/4455071
/** Notify TetheringService if interface name has been changed. */
if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(),
Phone.REASON_LINK_PROPERTIES_CHANGED)) {
if (isTetheringSupported()) {
mTethering.handleTetherIfaceChange();
}
}
}
/**
* Add and remove routes using the old properties (null if not previously connected),
* new properties (null if becoming disconnected). May even be double null, which
* is a noop.
* Uses isLinkDefault to determine if default routes should be set or conversely if
* host routes should be set to the dns servers
* returns a boolean indicating the routes changed
*/
private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
boolean isLinkDefault) {
Collection<RouteInfo> routesToAdd = null;
CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>();
CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
if (curLp != null) {
// check for the delta between the current set and the new
routeDiff = curLp.compareRoutes(newLp);
dnsDiff = curLp.compareDnses(newLp);
} else if (newLp != null) {
routeDiff.added = newLp.getRoutes();
dnsDiff.added = newLp.getDnses();
}
boolean routesChanged = (routeDiff.removed.size() != 0 || routeDiff.added.size() != 0);
for (RouteInfo r : routeDiff.removed) {
if (isLinkDefault || ! r.isDefaultRoute()) {
removeRoute(curLp, r);
}
}
for (RouteInfo r : routeDiff.added) {
if (isLinkDefault || ! r.isDefaultRoute()) {
addRoute(newLp, r);
} else {
// many radios add a default route even when we don't want one.
// remove the default route unless somebody else has asked for it
String ifaceName = newLp.getInterfaceName();
if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
if (DBG) log("Removing " + r + " for interface " + ifaceName);
try {
mNetd.removeRoute(ifaceName, r);
} catch (Exception e) {
// never crash - catch them all
loge("Exception trying to remove a route: " + e);
}
}
}
}
if (!isLinkDefault) {
// handle DNS routes
if (routesChanged) {
// routes changed - remove all old dns entries and add new
if (curLp != null) {
for (InetAddress oldDns : curLp.getDnses()) {
removeRouteToAddress(curLp, oldDns);
}
}
if (newLp != null) {
for (InetAddress newDns : newLp.getDnses()) {
addRouteToAddress(newLp, newDns);
}
}
} else {
// no change in routes, check for change in dns themselves
for (InetAddress oldDns : dnsDiff.removed) {
removeRouteToAddress(curLp, oldDns);
}
for (InetAddress newDns : dnsDiff.added) {
addRouteToAddress(newLp, newDns);
}
}
}
return routesChanged;
}
/**
* Reads the network specific TCP buffer sizes from SystemProperties
* net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
* wide use
*/
public void updateNetworkSettings(NetworkStateTracker nt) {
String key = nt.getTcpBufferSizesPropName();
String bufferSizes = SystemProperties.get(key);
if (bufferSizes.length() == 0) {
if (VDBG) log(key + " not found in system properties. Using defaults");
// Setting to default values so we won't be stuck to previous values
key = "net.tcp.buffersize.default";
bufferSizes = SystemProperties.get(key);
}
// Set values in kernel
if (bufferSizes.length() != 0) {
if (VDBG) {
log("Setting TCP values: [" + bufferSizes
+ "] which comes from [" + key + "]");
}
setBufferSize(bufferSizes);
}
}
/**
* Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
* which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
*
* @param bufferSizes in the format of "readMin, readInitial, readMax,
* writeMin, writeInitial, writeMax"
*/
private void setBufferSize(String bufferSizes) {
try {
String[] values = bufferSizes.split(",");
if (values.length == 6) {
final String prefix = "/sys/kernel/ipv4/tcp_";
FileUtils.stringToFile(prefix + "rmem_min", values[0]);
FileUtils.stringToFile(prefix + "rmem_def", values[1]);
FileUtils.stringToFile(prefix + "rmem_max", values[2]);
FileUtils.stringToFile(prefix + "wmem_min", values[3]);
FileUtils.stringToFile(prefix + "wmem_def", values[4]);
FileUtils.stringToFile(prefix + "wmem_max", values[5]);
} else {
loge("Invalid buffersize string: " + bufferSizes);
}
} catch (IOException e) {
loge("Can't set tcp buffer sizes:" + e);
}
}
/**
* Adjust the per-process dns entries (net.dns<x>.<pid>) based
* on the highest priority active net which this process requested.
* If there aren't any, clear it out
*/
private void reassessPidDns(int myPid, boolean doBump)
{
if (VDBG) log("reassessPidDns for pid " + myPid);
for(int i : mPriorityList) {
if (mNetConfigs[i].isDefault()) {
continue;
}
NetworkStateTracker nt = mNetTrackers[i];
if (nt.getNetworkInfo().isConnected() &&
!nt.isTeardownRequested()) {
LinkProperties p = nt.getLinkProperties();
if (p == null) continue;
List pids = mNetRequestersPids[i];
for (int j=0; j<pids.size(); j++) {
Integer pid = (Integer)pids.get(j);
if (pid.intValue() == myPid) {
Collection<InetAddress> dnses = p.getDnses();
writePidDns(dnses, myPid);
if (doBump) {
bumpDns();
}
return;
}
}
}
}
// nothing found - delete
for (int i = 1; ; i++) {
String prop = "net.dns" + i + "." + myPid;
if (SystemProperties.get(prop).length() == 0) {
if (doBump) {
bumpDns();
}
return;
}
SystemProperties.set(prop, "");
}
}
// return true if results in a change
private boolean writePidDns(Collection <InetAddress> dnses, int pid) {
int j = 1;
boolean changed = false;
for (InetAddress dns : dnses) {
String dnsString = dns.getHostAddress();
if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) {
changed = true;
SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress());
}
}
return changed;
}
private void bumpDns() {
/*
* Bump the property that tells the name resolver library to reread
* the DNS server list from the properties.
*/
String propVal = SystemProperties.get("net.dnschange");
int n = 0;
if (propVal.length() != 0) {
try {
n = Integer.parseInt(propVal);
} catch (NumberFormatException e) {}
}
SystemProperties.set("net.dnschange", "" + (n+1));
/*
* Tell the VMs to toss their DNS caches
*/
Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
/*
* Connectivity events can happen before boot has completed ...
*/
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent);
}
// Caller must grab mDnsLock.
private boolean updateDns(String network, String iface,
Collection<InetAddress> dnses, String domains) {
boolean changed = false;
int last = 0;
if (dnses.size() == 0 && mDefaultDns != null) {
++last;
String value = mDefaultDns.getHostAddress();
if (!value.equals(SystemProperties.get("net.dns1"))) {
if (DBG) {
loge("no dns provided for " + network + " - using " + value);
}
changed = true;
SystemProperties.set("net.dns1", value);
}
} else {
for (InetAddress dns : dnses) {
++last;
String key = "net.dns" + last;
String value = dns.getHostAddress();
if (!changed && value.equals(SystemProperties.get(key))) {
continue;
}
if (VDBG) {
log("adding dns " + value + " for " + network);
}
changed = true;
SystemProperties.set(key, value);
}
}
for (int i = last + 1; i <= mNumDnsEntries; ++i) {
String key = "net.dns" + i;
if (VDBG) log("erasing " + key);
changed = true;
SystemProperties.set(key, "");
}
mNumDnsEntries = last;
if (changed) {
try {
mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses));
mNetd.setDefaultInterfaceForDns(iface);
} catch (Exception e) {
loge("exception setting default dns interface: " + e);
}
}
if (!domains.equals(SystemProperties.get("net.dns.search"))) {
SystemProperties.set("net.dns.search", domains);
changed = true;
}
return changed;
}
private void handleDnsConfigurationChange(int netType) {
// add default net's dns entries
NetworkStateTracker nt = mNetTrackers[netType];
if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
LinkProperties p = nt.getLinkProperties();
if (p == null) return;
Collection<InetAddress> dnses = p.getDnses();
boolean changed = false;
if (mNetConfigs[netType].isDefault()) {
String network = nt.getNetworkInfo().getTypeName();
synchronized (mDnsLock) {
if (!mDnsOverridden) {
changed = updateDns(network, p.getInterfaceName(), dnses, "");
}
}
} else {
try {
mNetd.setDnsServersForInterface(p.getInterfaceName(),
NetworkUtils.makeStrings(dnses));
} catch (Exception e) {
loge("exception setting dns servers: " + e);
}
// set per-pid dns for attached secondary nets
List pids = mNetRequestersPids[netType];
for (int y=0; y< pids.size(); y++) {
Integer pid = (Integer)pids.get(y);
changed = writePidDns(dnses, pid.intValue());
}
}
if (changed) bumpDns();
}
}
private int getRestoreDefaultNetworkDelay(int networkType) {
String restoreDefaultNetworkDelayStr = SystemProperties.get(
NETWORK_RESTORE_DELAY_PROP_NAME);
if(restoreDefaultNetworkDelayStr != null &&
restoreDefaultNetworkDelayStr.length() != 0) {
try {
return Integer.valueOf(restoreDefaultNetworkDelayStr);
} catch (NumberFormatException e) {
}
}
// if the system property isn't set, use the value for the apn type
int ret = RESTORE_DEFAULT_NETWORK_DELAY;
if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) &&
(mNetConfigs[networkType] != null)) {
ret = mNetConfigs[networkType].restoreTime;
}
return ret;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump ConnectivityService " +
"from from pid=" + Binder.getCallingPid() + ", uid=" +
Binder.getCallingUid());
return;
}
pw.println();
for (NetworkStateTracker nst : mNetTrackers) {
if (nst != null) {
if (nst.getNetworkInfo().isConnected()) {
pw.println("Active network: " + nst.getNetworkInfo().
getTypeName());
}
pw.println(nst.getNetworkInfo());
pw.println(nst);
pw.println();
}
}
pw.println("Network Requester Pids:");
for (int net : mPriorityList) {
String pidString = net + ": ";
for (Object pid : mNetRequestersPids[net]) {
pidString = pidString + pid.toString() + ", ";
}
pw.println(pidString);
}
pw.println();
pw.println("FeatureUsers:");
for (Object requester : mFeatureUsers) {
pw.println(requester.toString());
}
pw.println();
synchronized (this) {
pw.println("NetworkTranstionWakeLock is currently " +
(mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held.");
pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy);
}
pw.println();
mTethering.dump(fd, pw, args);
if (mInetLog != null) {
pw.println();
pw.println("Inet condition reports:");
for(int i = 0; i < mInetLog.size(); i++) {
pw.println(mInetLog.get(i));
}
}
}
// must be stateless - things change under us.
private class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
NetworkInfo info;
switch (msg.what) {
case NetworkStateTracker.EVENT_STATE_CHANGED:
info = (NetworkInfo) msg.obj;
int type = info.getType();
NetworkInfo.State state = info.getState();
if (DBG) log("ConnectivityChange for " +
info.getTypeName() + ": " +
state + "/" + info.getDetailedState());
// Connectivity state changed:
// [31-13] Reserved for future use
// [12-9] Network subtype (for mobile network, as defined
// by TelephonyManager)
// [8-3] Detailed state ordinal (as defined by
// NetworkInfo.DetailedState)
// [2-0] Network type (as defined by ConnectivityManager)
int eventLogParam = (info.getType() & 0x7) |
((info.getDetailedState().ordinal() & 0x3f) << 3) |
(info.getSubtype() << 9);
EventLog.writeEvent(EventLogTags.CONNECTIVITY_STATE_CHANGED,
eventLogParam);
if (info.getDetailedState() ==
NetworkInfo.DetailedState.FAILED) {
handleConnectionFailure(info);
} else if (state == NetworkInfo.State.DISCONNECTED) {
handleDisconnect(info);
} else if (state == NetworkInfo.State.SUSPENDED) {
// TODO: need to think this over.
// the logic here is, handle SUSPENDED the same as
// DISCONNECTED. The only difference being we are
// broadcasting an intent with NetworkInfo that's
// suspended. This allows the applications an
// opportunity to handle DISCONNECTED and SUSPENDED
// differently, or not.
handleDisconnect(info);
} else if (state == NetworkInfo.State.CONNECTED) {
handleConnect(info);
}
break;
case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
info = (NetworkInfo) msg.obj;
// TODO: Temporary allowing network configuration
// change not resetting sockets.
// @see bug/4455071
handleConnectivityChange(info.getType(), false);
break;
case EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
String causedBy = null;
synchronized (ConnectivityService.this) {
if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
mNetTransitionWakeLock.isHeld()) {
mNetTransitionWakeLock.release();
causedBy = mNetTransitionWakeLockCausedBy;
}
}
if (causedBy != null) {
log("NetTransition Wakelock for " + causedBy + " released by timeout");
}
break;
case EVENT_RESTORE_DEFAULT_NETWORK:
FeatureUser u = (FeatureUser)msg.obj;
u.expire();
break;
case EVENT_INET_CONDITION_CHANGE:
{
int netType = msg.arg1;
int condition = msg.arg2;
handleInetConditionChange(netType, condition);
break;
}
case EVENT_INET_CONDITION_HOLD_END:
{
int netType = msg.arg1;
int sequence = msg.arg2;
handleInetConditionHoldEnd(netType, sequence);
break;
}
case EVENT_SET_NETWORK_PREFERENCE:
{
int preference = msg.arg1;
handleSetNetworkPreference(preference);
break;
}
case EVENT_SET_MOBILE_DATA:
{
boolean enabled = (msg.arg1 == ENABLED);
handleSetMobileData(enabled);
break;
}
case EVENT_APPLY_GLOBAL_HTTP_PROXY:
{
handleDeprecatedGlobalHttpProxy();
break;
}
case EVENT_SET_DEPENDENCY_MET:
{
boolean met = (msg.arg1 == ENABLED);
handleSetDependencyMet(msg.arg2, met);
break;
}
case EVENT_RESTORE_DNS:
{
if (mActiveDefaultNetwork != -1) {
handleDnsConfigurationChange(mActiveDefaultNetwork);
}
break;
}
case EVENT_SEND_STICKY_BROADCAST_INTENT:
{
Intent intent = (Intent)msg.obj;
sendStickyBroadcast(intent);
break;
}
case EVENT_SET_POLICY_DATA_ENABLE: {
final int networkType = msg.arg1;
final boolean enabled = msg.arg2 == ENABLED;
handleSetPolicyDataEnable(networkType, enabled);
}
}
}
}
// javadoc from interface
public int tether(String iface) {
enforceTetherChangePermission();
if (isTetheringSupported()) {
return mTethering.tether(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
}
// javadoc from interface
public int untether(String iface) {
enforceTetherChangePermission();
if (isTetheringSupported()) {
return mTethering.untether(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
}
// javadoc from interface
public int getLastTetherError(String iface) {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
return mTethering.getLastTetherError(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
}
// TODO - proper iface API for selection by property, inspection, etc
public String[] getTetherableUsbRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
return mTethering.getTetherableUsbRegexs();
} else {
return new String[0];
}
}
public String[] getTetherableWifiRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
return mTethering.getTetherableWifiRegexs();
} else {
return new String[0];
}
}
public String[] getTetherableBluetoothRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
return mTethering.getTetherableBluetoothRegexs();
} else {
return new String[0];
}
}
public int setUsbTethering(boolean enable) {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
return mTethering.setUsbTethering(enable);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
}
// TODO - move iface listing, queries, etc to new module
// javadoc from interface
public String[] getTetherableIfaces() {
enforceTetherAccessPermission();
return mTethering.getTetherableIfaces();
}
public String[] getTetheredIfaces() {
enforceTetherAccessPermission();
return mTethering.getTetheredIfaces();
}
@Override
public String[] getTetheredIfacePairs() {
enforceTetherAccessPermission();
return mTethering.getTetheredIfacePairs();
}
public String[] getTetheringErroredIfaces() {
enforceTetherAccessPermission();
return mTethering.getErroredIfaces();
}
// if ro.tether.denied = true we default to no tethering
// gservices could set the secure setting to 1 though to enable it on a build where it
// had previously been turned off.
public boolean isTetheringSupported() {
enforceTetherAccessPermission();
int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
boolean tetherEnabledInSettings = (Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.TETHER_SUPPORTED, defaultVal) != 0);
return tetherEnabledInSettings && mTetheringConfigValid;
}
// An API NetworkStateTrackers can call when they lose their network.
// This will automatically be cleared after X seconds or a network becomes CONNECTED,
// whichever happens first. The timer is started by the first caller and not
// restarted by subsequent callers.
public void requestNetworkTransitionWakelock(String forWhom) {
enforceConnectivityInternalPermission();
synchronized (this) {
if (mNetTransitionWakeLock.isHeld()) return;
mNetTransitionWakeLockSerialNumber++;
mNetTransitionWakeLock.acquire();
mNetTransitionWakeLockCausedBy = forWhom;
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(
EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
mNetTransitionWakeLockSerialNumber, 0),
mNetTransitionWakeLockTimeout);
return;
}
// 100 percent is full good, 0 is full bad.
public void reportInetCondition(int networkType, int percentage) {
if (VDBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.STATUS_BAR,
"ConnectivityService");
if (DBG) {
int pid = getCallingPid();
int uid = getCallingUid();
String s = pid + "(" + uid + ") reports inet is " +
(percentage > 50 ? "connected" : "disconnected") + " (" + percentage + ") on " +
"network Type " + networkType + " at " + GregorianCalendar.getInstance().getTime();
mInetLog.add(s);
while(mInetLog.size() > INET_CONDITION_LOG_MAX_SIZE) {
mInetLog.remove(0);
}
}
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_INET_CONDITION_CHANGE, networkType, percentage));
}
private void handleInetConditionChange(int netType, int condition) {
if (DBG) {
log("Inet connectivity change, net=" +
netType + ", condition=" + condition +
",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
}
if (mActiveDefaultNetwork == -1) {
if (DBG) log("no active default network - aborting");
return;
}
if (mActiveDefaultNetwork != netType) {
if (DBG) log("given net not default - aborting");
return;
}
mDefaultInetCondition = condition;
int delay;
if (mInetConditionChangeInFlight == false) {
if (VDBG) log("starting a change hold");
// setup a new hold to debounce this
if (mDefaultInetCondition > 50) {
delay = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
} else {
delay = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
}
mInetConditionChangeInFlight = true;
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END,
mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
} else {
// we've set the new condition, when this hold ends that will get
// picked up
if (VDBG) log("currently in hold - not setting new end evt");
}
}
private void handleInetConditionHoldEnd(int netType, int sequence) {
if (VDBG) {
log("Inet hold end, net=" + netType +
", condition =" + mDefaultInetCondition +
", published condition =" + mDefaultInetConditionPublished);
}
mInetConditionChangeInFlight = false;
if (mActiveDefaultNetwork == -1) {
if (DBG) log("no active default network - aborting");
return;
}
if (mDefaultConnectionSequence != sequence) {
if (DBG) log("event hold for obsolete network - aborting");
return;
}
// TODO: Figure out why this optimization sometimes causes a
// change in mDefaultInetCondition to be missed and the
// UI to not be updated.
//if (mDefaultInetConditionPublished == mDefaultInetCondition) {
// if (DBG) log("no change in condition - aborting");
// return;
//}
NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
if (networkInfo.isConnected() == false) {
if (DBG) log("default network not connected - aborting");
return;
}
mDefaultInetConditionPublished = mDefaultInetCondition;
sendInetConditionBroadcast(networkInfo);
return;
}
public synchronized ProxyProperties getProxy() {
if (mGlobalProxy != null) return mGlobalProxy;
if (mDefaultProxy != null) return mDefaultProxy;
return null;
}
public void setGlobalProxy(ProxyProperties proxyProperties) {
enforceChangePermission();
synchronized (mGlobalProxyLock) {
if (proxyProperties == mGlobalProxy) return;
if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
String host = "";
int port = 0;
String exclList = "";
if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) {
mGlobalProxy = new ProxyProperties(proxyProperties);
host = mGlobalProxy.getHost();
port = mGlobalProxy.getPort();
exclList = mGlobalProxy.getExclusionList();
} else {
mGlobalProxy = null;
}
ContentResolver res = mContext.getContentResolver();
Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, host);
Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, port);
Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
exclList);
}
if (mGlobalProxy == null) {
proxyProperties = mDefaultProxy;
}
sendProxyBroadcast(proxyProperties);
}
private void loadGlobalProxy() {
ContentResolver res = mContext.getContentResolver();
String host = Settings.Secure.getString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST);
int port = Settings.Secure.getInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, 0);
String exclList = Settings.Secure.getString(res,
Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
if (!TextUtils.isEmpty(host)) {
ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList);
synchronized (mGlobalProxyLock) {
mGlobalProxy = proxyProperties;
}
}
}
public ProxyProperties getGlobalProxy() {
synchronized (mGlobalProxyLock) {
return mGlobalProxy;
}
}
private void handleApplyDefaultProxy(int type) {
// check if new default - push it out to all VM if so
ProxyProperties proxy = mNetTrackers[type].getLinkProperties().getHttpProxy();
synchronized (this) {
if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
if (mDefaultProxy == proxy) return;
if (proxy != null && !TextUtils.isEmpty(proxy.getHost())) {
mDefaultProxy = proxy;
} else {
mDefaultProxy = null;
}
}
if (VDBG) log("changing default proxy to " + proxy);
if ((proxy == null && mGlobalProxy == null) || proxy.equals(mGlobalProxy)) return;
if (mGlobalProxy != null) return;
sendProxyBroadcast(proxy);
}
private void handleDeprecatedGlobalHttpProxy() {
String proxy = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.HTTP_PROXY);
if (!TextUtils.isEmpty(proxy)) {
String data[] = proxy.split(":");
String proxyHost = data[0];
int proxyPort = 8080;
if (data.length > 1) {
try {
proxyPort = Integer.parseInt(data[1]);
} catch (NumberFormatException e) {
return;
}
}
ProxyProperties p = new ProxyProperties(data[0], proxyPort, "");
setGlobalProxy(p);
}
}
private void sendProxyBroadcast(ProxyProperties proxy) {
if (proxy == null) proxy = new ProxyProperties("", 0, "");
if (DBG) log("sending Proxy Broadcast for " + proxy);
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
mContext.sendStickyBroadcast(intent);
}
private static class SettingsObserver extends ContentObserver {
private int mWhat;
private Handler mHandler;
SettingsObserver(Handler handler, int what) {
super(handler);
mHandler = handler;
mWhat = what;
}
void observe(Context context) {
ContentResolver resolver = context.getContentResolver();
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.HTTP_PROXY), false, this);
}
@Override
public void onChange(boolean selfChange) {
mHandler.obtainMessage(mWhat).sendToTarget();
}
}
private void log(String s) {
Slog.d(TAG, s);
}
private void loge(String s) {
Slog.e(TAG, s);
}
int convertFeatureToNetworkType(int networkType, String feature) {
int usedNetworkType = networkType;
if(networkType == ConnectivityManager.TYPE_MOBILE) {
if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) ||
TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_FOTA;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_IMS;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_CBS;
} else {
Slog.e(TAG, "Can't match any mobile netTracker!");
}
} else if (networkType == ConnectivityManager.TYPE_WIFI) {
if (TextUtils.equals(feature, "p2p")) {
usedNetworkType = ConnectivityManager.TYPE_WIFI_P2P;
} else {
Slog.e(TAG, "Can't match any wifi netTracker!");
}
} else {
Slog.e(TAG, "Unexpected network type");
}
return usedNetworkType;
}
private static <T> T checkNotNull(T value, String message) {
if (value == null) {
throw new NullPointerException(message);
}
return value;
}
/**
* Protect a socket from VPN routing rules. This method is used by
* VpnBuilder and not available in ConnectivityManager. Permissions
* are checked in Vpn class.
* @hide
*/
@Override
public boolean protectVpn(ParcelFileDescriptor socket) {
try {
int type = mActiveDefaultNetwork;
if (ConnectivityManager.isNetworkTypeValid(type)) {
mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName());
return true;
}
} catch (Exception e) {
// ignore
} finally {
try {
socket.close();
} catch (Exception e) {
// ignore
}
}
return false;
}
/**
* Prepare for a VPN application. This method is used by VpnDialogs
* and not available in ConnectivityManager. Permissions are checked
* in Vpn class.
* @hide
*/
@Override
public boolean prepareVpn(String oldPackage, String newPackage) {
return mVpn.prepare(oldPackage, newPackage);
}
/**
* Configure a TUN interface and return its file descriptor. Parameters
* are encoded and opaque to this class. This method is used by VpnBuilder
* and not available in ConnectivityManager. Permissions are checked in
* Vpn class.
* @hide
*/
@Override
public ParcelFileDescriptor establishVpn(VpnConfig config) {
return mVpn.establish(config);
}
/**
* Start legacy VPN and return an intent to VpnDialogs. This method is
* used by VpnSettings and not available in ConnectivityManager.
* Permissions are checked in Vpn class.
* @hide
*/
@Override
public void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
mVpn.startLegacyVpn(config, racoon, mtpd);
}
/**
* Return the information of the ongoing legacy VPN. This method is used
* by VpnSettings and not available in ConnectivityManager. Permissions
* are checked in Vpn class.
* @hide
*/
@Override
public LegacyVpnInfo getLegacyVpnInfo() {
return mVpn.getLegacyVpnInfo();
}
/**
* Callback for VPN subsystem. Currently VPN is not adapted to the service
* through NetworkStateTracker since it works differently. For example, it
* needs to override DNS servers but never takes the default routes. It
* relies on another data network, and it could keep existing connections
* alive after reconnecting, switching between networks, or even resuming
* from deep sleep. Calls from applications should be done synchronously
* to avoid race conditions. As these are all hidden APIs, refactoring can
* be done whenever a better abstraction is developed.
*/
public class VpnCallback {
private VpnCallback() {
}
public void override(List<String> dnsServers, List<String> searchDomains) {
if (dnsServers == null) {
restore();
return;
}
// Convert DNS servers into addresses.
List<InetAddress> addresses = new ArrayList<InetAddress>();
for (String address : dnsServers) {
// Double check the addresses and remove invalid ones.
try {
addresses.add(InetAddress.parseNumericAddress(address));
} catch (Exception e) {
// ignore
}
}
if (addresses.isEmpty()) {
restore();
return;
}
// Concatenate search domains into a string.
StringBuilder buffer = new StringBuilder();
if (searchDomains != null) {
for (String domain : searchDomains) {
buffer.append(domain).append(' ');
}
}
String domains = buffer.toString().trim();
// Apply DNS changes.
boolean changed = false;
synchronized (mDnsLock) {
changed = updateDns("VPN", "VPN", addresses, domains);
mDnsOverridden = true;
}
if (changed) {
bumpDns();
}
// TODO: temporarily remove http proxy?
}
public void restore() {
synchronized (mDnsLock) {
if (!mDnsOverridden) {
return;
}
mDnsOverridden = false;
}
mHandler.sendEmptyMessage(EVENT_RESTORE_DNS);
}
}
}