Wink Saville 127beffb05 Tighten condition for calling checkMobileProvisioning.
Add the condition that the connected network needs to be TYPE_MOBILE.
This eliminates checking on connecting to secondary networks like
FOTA, MMS, SUPL ...

This reduces unnecessary attempts to bring up multiple connections at
the same time. Especially in the light of the trouble we're having with HFA
on Sprint, where we don't want to connect to any other network until HFA
has completed.

Bug: 10758001
Change-Id: Id294536b70304a51de4752bc1a4ffac734f10f1a
2013-09-14 09:04:53 -07:00

4805 lines
193 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.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_DUMMY;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.CaptivePortalTracker;
import android.net.ConnectivityManager;
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.LinkQualityInfo;
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.SamplingDataTracker;
import android.net.Uri;
import android.net.wifi.WifiStateTracker;
import android.net.wimax.WimaxManagerConstants;
import android.os.AsyncTask;
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.Messenger;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.Xml;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.PacManager;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
import dalvik.system.DexClassLoader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
*/
public class ConnectivityService extends IConnectivityManager.Stub {
private static final String TAG = "ConnectivityService";
private static final boolean DBG = true;
private static final boolean VDBG = false;
private static final boolean LOGD_RULES = false;
// TODO: create better separation between radio types and network types
// 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";
// Default value if FAIL_FAST_TIME_MS is not set
private static final int DEFAULT_FAIL_FAST_TIME_MS = 1 * 60 * 1000;
// system property that can override DEFAULT_FAIL_FAST_TIME_MS
private static final String FAIL_FAST_TIME_MS =
"persist.radio.fail_fast_time_ms";
private static final String ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED =
"android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED";
private static final int SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE = 0;
private PendingIntent mSampleIntervalElapsedIntent;
// Set network sampling interval at 12 minutes, this way, even if the timers get
// aggregated, it will fire at around 15 minutes, which should allow us to
// aggregate this timer with other timers (specially the socket keep alive timers)
private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 12 * 60);
// start network sampling a minute after booting ...
private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 60);
AlarmManager mAlarmManager;
// 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 KeyStore mKeyStore;
@GuardedBy("mVpns")
private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
private VpnCallback mVpnCallback = new VpnCallback();
private boolean mLockdownEnabled;
private LockdownVpnTracker mLockdownTracker;
private Nat464Xlat mClat;
/** 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[];
/* Handles captive portal check on a network */
private CaptivePortalTracker mCaptivePortalTracker;
/**
* 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<Integer> 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 mTestMode;
private static ConnectivityService sServiceInstance;
private INetworkManagementService mNetd;
private INetworkPolicyManager mPolicyManager;
private static final int ENABLED = 1;
private static final int DISABLED = 0;
private static final boolean ADD = true;
private static final boolean REMOVE = false;
private static final boolean TO_DEFAULT_TABLE = true;
private static final boolean TO_SECONDARY_TABLE = false;
private static final boolean EXEMPT = true;
private static final boolean UNEXEMPT = false;
/**
* used internally as a delayed event to make us switch back to the
* default network
*/
private static final int EVENT_RESTORE_DEFAULT_NETWORK = 1;
/**
* used internally to change our mobile data enabled flag
*/
private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
/**
* used internally to change our network preference setting
* arg1 = networkType to prefer
*/
private static final int EVENT_SET_NETWORK_PREFERENCE = 3;
/**
* used internally to synchronize inet condition reports
* arg1 = networkType
* arg2 = condition (0 bad, 100 good)
*/
private static final int EVENT_INET_CONDITION_CHANGE = 4;
/**
* used internally to mark the end of inet condition hold periods
* arg1 = networkType
*/
private static final int EVENT_INET_CONDITION_HOLD_END = 5;
/**
* used internally to set enable/disable cellular data
* arg1 = ENBALED or DISABLED
*/
private static final int EVENT_SET_MOBILE_DATA = 7;
/**
* used internally to clear a wakelock when transitioning
* from one net to another
*/
private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
/**
* used internally to reload global proxy settings
*/
private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 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 = 10;
/**
* used internally to send a sticky broadcast delayed.
*/
private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11;
/**
* Used internally to
* {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
*/
private static final int EVENT_SET_POLICY_DATA_ENABLE = 12;
private static final int EVENT_VPN_STATE_CHANGED = 13;
/**
* Used internally to disable fail fast of mobile data
*/
private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
/**
* user internally to indicate that data sampling interval is up
*/
private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15;
/** Handler used for internal events. */
private InternalHandler mHandler;
/** Handler used for incoming {@link NetworkStateTracker} events. */
private NetworkStateTrackerHandler mTrackerHandler;
// 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;
// Lock for protecting access to mAddedRoutes and mExemptAddresses
private final Object mRoutesLock = new Object();
// 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
@GuardedBy("mRoutesLock")
private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
// this collection corresponds to the entries of mAddedRoutes that have routing exemptions
// used to handle cleanup of exempt rules
@GuardedBy("mRoutesLock")
private Collection<LinkAddress> mExemptAddresses = new ArrayList<LinkAddress>();
// 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;
private Object mProxyLock = new Object();
private boolean mDefaultProxyDisabled = false;
// track the global proxy.
private ProxyProperties mGlobalProxy = null;
private PacManager mPacManager = null;
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;
private DataConnectionStats mDataConnectionStats;
private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
TelephonyManager mTelephonyManager;
public ConnectivityService(Context context, INetworkManagementService netd,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
// Currently, omitting a NetworkFactory will create one internally
// TODO: create here when we have cleaner WiMAX support
this(context, netd, statsService, policyManager, null);
}
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
NetworkFactory netFactory) {
if (DBG) log("ConnectivityService starting up");
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
handlerThread.start();
mHandler = new InternalHandler(handlerThread.getLooper());
mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper());
if (netFactory == null) {
netFactory = new DefaultNetworkFactory(context, mTrackerHandler);
}
// 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.Global.getString(context.getContentResolver(),
Settings.Global.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(netManager, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
mKeyStore = KeyStore.getInstance();
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
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];
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 (VDBG) log("raString=" + raString + " r=" + r);
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;
}
// TODO: What is the "correct" way to do determine if this is a wifi only device?
boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
log("wifiOnly=" + wifiOnly);
String[] naStrings = context.getResources().getStringArray(
com.android.internal.R.array.networkAttributes);
for (String naString : naStrings) {
try {
NetworkConfig n = new NetworkConfig(naString);
if (VDBG) log("naString=" + naString + " config=" + n);
if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
loge("Error in networkAttributes - ignoring attempt to define type " +
n.type);
continue;
}
if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
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
}
}
if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
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;
}
}
// Update mNetworkPreference according to user mannually first then overlay config.xml
mNetworkPreference = getPersistedNetworkPreference();
if (mNetworkPreference == -1) {
for (int n : mPriorityList) {
if (mNetConfigs[n].isDefault() && ConnectivityManager.isNetworkTypeValid(n)) {
mNetworkPreference = n;
break;
}
}
if (mNetworkPreference == -1) {
throw new IllegalStateException(
"You should set at least one default Network in config.xml!");
}
}
mNetRequestersPids =
(List<Integer> [])new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
for (int i : mPriorityList) {
mNetRequestersPids[i] = new ArrayList<Integer>();
}
mFeatureUsers = new ArrayList<FeatureUser>();
mTestMode = SystemProperties.get("cm.test.mode").equals("true")
&& SystemProperties.get("ro.build.type").equals("eng");
// Create and start trackers for hard-coded networks
for (int targetNetworkType : mPriorityList) {
final NetworkConfig config = mNetConfigs[targetNetworkType];
final NetworkStateTracker tracker;
try {
tracker = netFactory.createTracker(targetNetworkType, config);
mNetTrackers[targetNetworkType] = tracker;
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Problem creating " + getNetworkTypeName(targetNetworkType)
+ " tracker: " + e);
continue;
}
tracker.startMonitoring(context, mTrackerHandler);
if (config.isDefault()) {
tracker.reconnect();
}
}
mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
//set up the listener for user state for creating user VPNs
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_STARTING);
intentFilter.addAction(Intent.ACTION_USER_STOPPING);
mContext.registerReceiverAsUser(
mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
try {
mNetd.registerObserver(mTethering);
mNetd.registerObserver(mDataActivityObserver);
mNetd.registerObserver(mClat);
} 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);
mDataConnectionStats = new DataConnectionStats(mContext);
mDataConnectionStats.startMonitoring();
// start network sampling ..
Intent intent = new Intent(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED, null);
mSampleIntervalElapsedIntent = PendingIntent.getBroadcast(mContext,
SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE, intent, 0);
mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
setAlarm(DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS * 1000, mSampleIntervalElapsedIntent);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED);
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED)) {
mHandler.sendMessage(mHandler.obtainMessage
(EVENT_SAMPLE_INTERVAL_ELAPSED));
}
}
},
new IntentFilter(filter));
mPacManager = new PacManager(mContext);
filter = new IntentFilter();
filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
mContext.registerReceiver(mProvisioningReceiver, filter);
}
/**
* Factory that creates {@link NetworkStateTracker} instances using given
* {@link NetworkConfig}.
*/
public interface NetworkFactory {
public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config);
}
private static class DefaultNetworkFactory implements NetworkFactory {
private final Context mContext;
private final Handler mTrackerHandler;
public DefaultNetworkFactory(Context context, Handler trackerHandler) {
mContext = context;
mTrackerHandler = trackerHandler;
}
@Override
public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) {
switch (config.radio) {
case TYPE_WIFI:
return new WifiStateTracker(targetNetworkType, config.name);
case TYPE_MOBILE:
return new MobileDataStateTracker(targetNetworkType, config.name);
case TYPE_DUMMY:
return new DummyDataStateTracker(targetNetworkType, config.name);
case TYPE_BLUETOOTH:
return BluetoothTetheringDataTracker.getInstance();
case TYPE_WIMAX:
return makeWimaxStateTracker(mContext, mTrackerHandler);
case TYPE_ETHERNET:
return EthernetDataTracker.getInstance();
default:
throw new IllegalArgumentException(
"Trying to create a NetworkStateTracker for an unknown radio type: "
+ config.radio);
}
}
}
/**
* Loads external WiMAX library and registers as system service, returning a
* {@link NetworkStateTracker} for WiMAX. Caller is still responsible for
* invoking {@link NetworkStateTracker#startMonitoring(Context, Handler)}.
*/
private static NetworkStateTracker makeWimaxStateTracker(
Context context, Handler trackerHandler) {
// Initialize Wimax
DexClassLoader wimaxClassLoader;
Class wimaxStateTrackerClass = null;
Class wimaxServiceClass = null;
Class wimaxManagerClass;
String wimaxJarLocation;
String wimaxLibLocation;
String wimaxManagerClassName;
String wimaxServiceClassName;
String wimaxStateTrackerClassName;
NetworkStateTracker wimaxStateTracker = null;
boolean isWimaxEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_wimaxEnabled);
if (isWimaxEnabled) {
try {
wimaxJarLocation = context.getResources().getString(
com.android.internal.R.string.config_wimaxServiceJarLocation);
wimaxLibLocation = context.getResources().getString(
com.android.internal.R.string.config_wimaxNativeLibLocation);
wimaxManagerClassName = context.getResources().getString(
com.android.internal.R.string.config_wimaxManagerClassname);
wimaxServiceClassName = context.getResources().getString(
com.android.internal.R.string.config_wimaxServiceClassname);
wimaxStateTrackerClassName = context.getResources().getString(
com.android.internal.R.string.config_wimaxStateTrackerClassname);
if (DBG) log("wimaxJarLocation: " + wimaxJarLocation);
wimaxClassLoader = new DexClassLoader(wimaxJarLocation,
new ContextWrapper(context).getCacheDir().getAbsolutePath(),
wimaxLibLocation, ClassLoader.getSystemClassLoader());
try {
wimaxManagerClass = wimaxClassLoader.loadClass(wimaxManagerClassName);
wimaxStateTrackerClass = wimaxClassLoader.loadClass(wimaxStateTrackerClassName);
wimaxServiceClass = wimaxClassLoader.loadClass(wimaxServiceClassName);
} catch (ClassNotFoundException ex) {
loge("Exception finding Wimax classes: " + ex.toString());
return null;
}
} catch(Resources.NotFoundException ex) {
loge("Wimax Resources does not exist!!! ");
return null;
}
try {
if (DBG) log("Starting Wimax Service... ");
Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor
(new Class[] {Context.class, Handler.class});
wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance(
context, trackerHandler);
Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor
(new Class[] {Context.class, wimaxStateTrackerClass});
wmxSrvConst.setAccessible(true);
IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker);
wmxSrvConst.setAccessible(false);
ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker);
} catch(Exception ex) {
loge("Exception creating Wimax classes: " + ex.toString());
return null;
}
} else {
loge("Wimax is not enabled or not added to the network attributes!!! ");
return null;
}
return wimaxStateTracker;
}
/**
* 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.Global.putInt(cr, Settings.Global.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.Global.CONNECTIVITY_CHANGE_DELAY,
ConnectivityManager.CONNECTIVITY_CHANGE_DELAY_DEFAULT);
return Settings.Global.getInt(cr, Settings.Global.CONNECTIVITY_CHANGE_DELAY,
defaultDelay);
}
private int getPersistedNetworkPreference() {
final ContentResolver cr = mContext.getContentResolver();
final int networkPrefSetting = Settings.Global
.getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1);
return networkPrefSetting;
}
/**
* 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);
}
if (mLockdownTracker != null) {
info = mLockdownTracker.augmentNetworkInfo(info);
}
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);
}
/**
* Find the first Provisioning network.
*
* @return NetworkInfo or null if none.
*/
private NetworkInfo getProvisioningNetworkInfo() {
enforceAccessPermission();
// Find the first Provisioning Network
NetworkInfo provNi = null;
for (NetworkInfo ni : getAllNetworkInfo()) {
if (ni.isConnectedToProvisioningNetwork()) {
provNi = ni;
break;
}
}
if (DBG) log("getProvisioningNetworkInfo: X provNi=" + provNi);
return provNi;
}
/**
* Find the first Provisioning network or the ActiveDefaultNetwork
* if there is no Provisioning network
*
* @return NetworkInfo or null if none.
*/
@Override
public NetworkInfo getProvisioningOrActiveNetworkInfo() {
enforceAccessPermission();
NetworkInfo provNi = getProvisioningNetworkInfo();
if (provNi == null) {
final int uid = Binder.getCallingUid();
provNi = getNetworkInfo(mActiveDefaultNetwork, uid);
}
if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi);
return provNi;
}
public NetworkInfo getActiveNetworkInfoUnfiltered() {
enforceAccessPermission();
if (isNetworkTypeValid(mActiveDefaultNetwork)) {
final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
if (tracker != null) {
return tracker.getNetworkInfo();
}
}
return null;
}
@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 long token = Binder.clearCallingIdentity();
try {
final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
if (state != null) {
try {
return mPolicyManager.getNetworkQuotaInfo(state);
} catch (RemoteException e) {
}
}
return null;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
final long token = Binder.clearCallingIdentity();
try {
return isNetworkMeteredUnchecked(mActiveDefaultNetwork);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private boolean isNetworkMeteredUnchecked(int networkType) {
final NetworkState state = getNetworkStateUnchecked(networkType);
if (state != null) {
try {
return mPolicyManager.isNetworkMetered(state);
} catch (RemoteException e) {
}
}
return false;
}
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);
}
private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
@Override
public void interfaceClassDataActivityChanged(String label, boolean active) {
int deviceType = Integer.parseInt(label);
sendDataActivityBroadcast(deviceType, active);
}
};
/**
* 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) {
long startTime = 0;
if (DBG) {
startTime = SystemClock.elapsedRealtime();
}
if (VDBG) {
log("startUsingNetworkFeature for net " + networkType + ": " + feature + ", uid="
+ Binder.getCallingUid());
}
enforceChangePermission();
try {
if (!ConnectivityManager.isNetworkTypeValid(networkType) ||
mNetConfigs[networkType] == null) {
return PhoneConstants.APN_REQUEST_FAILED;
}
FeatureUser f = new FeatureUser(networkType, feature, binder);
// TODO - move this into individual networktrackers
int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
if (mLockdownEnabled) {
// Since carrier APNs usually aren't available from VPN
// endpoint, mark them as unavailable.
return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
}
if (mProtectedNetworks.contains(usedNetworkType)) {
enforceConnectivityInternalPermission();
}
// if UID is restricted, don't allow them to bring up metered APNs
final boolean networkMetered = isNetworkMeteredUnchecked(usedNetworkType);
final int uidRules;
synchronized (mRulesLock) {
uidRules = mUidRules.get(Binder.getCallingUid(), RULE_ALLOW_ALL);
}
if (networkMetered && (uidRules & RULE_REJECT_METERED) != 0) {
return PhoneConstants.APN_REQUEST_FAILED;
}
NetworkStateTracker network = mNetTrackers[usedNetworkType];
if (network != null) {
Integer currentPid = new Integer(getCallingPid());
if (usedNetworkType != networkType) {
NetworkInfo ni = network.getNetworkInfo();
if (ni.isAvailable() == false) {
if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
if (DBG) log("special network not available ni=" + ni.getTypeName());
return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
} else {
// else make the attempt anyway - probably giving REQUEST_STARTED below
if (DBG) {
log("special network not available, but try anyway ni=" +
ni.getTypeName());
}
}
}
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) {
final long token = Binder.clearCallingIdentity();
try {
// add the pid-specific dns
handleDnsConfigurationChange(usedNetworkType);
if (VDBG) log("special network already active");
} finally {
Binder.restoreCallingIdentity(token);
}
return PhoneConstants.APN_ALREADY_ACTIVE;
}
if (VDBG) log("special network already connecting");
return PhoneConstants.APN_REQUEST_STARTED;
}
// check if the radio in play can make another contact
// assume if cannot for now
if (DBG) {
log("startUsingNetworkFeature reconnecting to " + networkType + ": " +
feature);
}
if (network.reconnect()) {
if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_STARTED");
return PhoneConstants.APN_REQUEST_STARTED;
} else {
if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_FAILED");
return PhoneConstants.APN_REQUEST_FAILED;
}
} 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);
}
}
if (DBG) log("startUsingNetworkFeature X: return -1 unsupported feature.");
return -1;
}
}
if (DBG) log("startUsingNetworkFeature X: return APN_TYPE_NOT_AVAILABLE");
return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
} finally {
if (DBG) {
final long execTime = SystemClock.elapsedRealtime() - startTime;
if (execTime > 250) {
loge("startUsingNetworkFeature took too long: " + execTime + "ms");
} else {
if (VDBG) log("startUsingNetworkFeature took " + execTime + "ms");
}
}
}
}
// 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) {
if (VDBG) log("stopUsingNetworkFeature: X");
// stop regardless of how many other time this proc had called start
return stopUsingNetworkFeature(u, true);
} else {
// none found!
if (VDBG) log("stopUsingNetworkFeature: X not a live request, ignoring");
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 (VDBG) {
log("stopUsingNetworkFeature: net " + networkType + ": " + feature);
}
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
if (DBG) {
log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
", net is invalid");
}
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 (VDBG) {
log("stopUsingNetworkFeature: this process has no outstanding requests" +
", ignoring");
}
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 (VDBG) log("stopUsingNetworkFeature: dup is found, ignoring");
return 1;
}
}
}
// TODO - move to individual network trackers
int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
tracker = mNetTrackers[usedNetworkType];
if (tracker == null) {
if (DBG) {
log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
" no known tracker for used net type " + usedNetworkType);
}
return -1;
}
if (usedNetworkType != networkType) {
Integer currentPid = new Integer(pid);
mNetRequestersPids[usedNetworkType].remove(currentPid);
final long token = Binder.clearCallingIdentity();
try {
reassessPidDns(pid, true);
} finally {
Binder.restoreCallingIdentity(token);
}
flushVmDnsCache();
if (mNetRequestersPids[usedNetworkType].size() != 0) {
if (VDBG) {
log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
" others still using it");
}
return 1;
}
callTeardown = true;
} else {
if (DBG) {
log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
" not a known feature - dropping");
}
}
}
if (callTeardown) {
if (DBG) {
log("stopUsingNetworkFeature: teardown net " + networkType + ": " + feature);
}
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)) {
if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
return false;
}
NetworkStateTracker tracker = mNetTrackers[networkType];
DetailedState netState = tracker.getNetworkInfo().getDetailedState();
if (tracker == null || (netState != DetailedState.CONNECTED &&
netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
tracker.isTeardownRequested()) {
if (VDBG) {
log("requestRouteToHostAddress on down network "
+ "(" + networkType + ") - dropped"
+ " tracker=" + tracker
+ " netState=" + netState
+ " isTeardownRequested="
+ ((tracker != null) ? tracker.isTeardownRequested() : "tracker:null"));
}
return false;
}
final long token = Binder.clearCallingIdentity();
try {
InetAddress addr = InetAddress.getByAddress(hostAddress);
LinkProperties lp = tracker.getLinkProperties();
boolean ok = addRouteToAddress(lp, addr, EXEMPT);
if (DBG) log("requestRouteToHostAddress ok=" + ok);
return ok;
} catch (UnknownHostException e) {
if (DBG) log("requestRouteToHostAddress got " + e.toString());
} finally {
Binder.restoreCallingIdentity(token);
}
if (DBG) log("requestRouteToHostAddress X bottom return false");
return false;
}
private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
boolean exempt) {
return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt);
}
private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT);
}
private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt) {
return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt);
}
private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) {
return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT);
}
private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
boolean toDefaultTable, boolean exempt) {
RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
if (bestRoute == null) {
bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
} else {
String iface = bestRoute.getInterface();
if (bestRoute.getGateway().equals(addr)) {
// if there is no better route, add the implied hostroute for our gateway
bestRoute = RouteInfo.makeHostRoute(addr, iface);
} else {
// if we will connect to this through another route, add a direct route
// to it's gateway
bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
}
}
return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt);
}
private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
boolean toDefaultTable, boolean exempt) {
if ((lp == null) || (r == null)) {
if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
return false;
}
if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
loge("Error modifying route - too much recursion");
return false;
}
String ifaceName = r.getInterface();
if(ifaceName == null) {
loge("Error modifying route - no interface name");
return false;
}
if (r.hasGateway()) {
RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), 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(), ifaceName);
} 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(),
ifaceName);
}
modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt);
}
}
if (doAdd) {
if (VDBG) log("Adding " + r + " for interface " + ifaceName);
try {
if (toDefaultTable) {
synchronized (mRoutesLock) {
// only track default table - only one apps can effect
mAddedRoutes.add(r);
mNetd.addRoute(ifaceName, r);
if (exempt) {
LinkAddress dest = r.getDestination();
if (!mExemptAddresses.contains(dest)) {
mNetd.setHostExemption(dest);
mExemptAddresses.add(dest);
}
}
}
} else {
mNetd.addSecondaryRoute(ifaceName, r);
}
} catch (Exception e) {
// never crash - catch them all
if (DBG) 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
if (toDefaultTable) {
synchronized (mRoutesLock) {
mAddedRoutes.remove(r);
if (mAddedRoutes.contains(r) == false) {
if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
mNetd.removeRoute(ifaceName, r);
LinkAddress dest = r.getDestination();
if (mExemptAddresses.contains(dest)) {
mNetd.clearHostExemption(dest);
mExemptAddresses.remove(dest);
}
} 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");
}
}
} else {
if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
mNetd.removeSecondaryRoute(ifaceName, r);
} catch (Exception e) {
// never crash - catch them all
if (VDBG) loge("Exception trying to remove a route: " + e);
return false;
}
}
}
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.Global.getInt(mContext.getContentResolver(),
Settings.Global.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) {
// caller is NPMS, since we only register with them
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: notify UID when it has requested targeted updates
}
@Override
public void onMeteredIfacesChanged(String[] meteredIfaces) {
// caller is NPMS, since we only register with them
if (LOGD_RULES) {
log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
}
synchronized (mRulesLock) {
mMeteredIfaces.clear();
for (String iface : meteredIfaces) {
mMeteredIfaces.add(iface);
}
}
}
@Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
// caller is NPMS, since we only register with them
if (LOGD_RULES) {
log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
}
// kick off connectivity change broadcast for active network, since
// global background policy change is radical.
final int networkType = mActiveDefaultNetwork;
if (isNetworkTypeValid(networkType)) {
final NetworkStateTracker tracker = mNetTrackers[networkType];
if (tracker != null) {
final NetworkInfo info = tracker.getNetworkInfo();
if (info != null && info.isConnected()) {
sendConnectedBroadcast(info);
}
}
}
}
};
/**
* @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);
}
if (mNetTrackers[ConnectivityManager.TYPE_WIMAX] != null) {
if (VDBG) {
log(mNetTrackers[ConnectivityManager.TYPE_WIMAX].toString() + enabled);
}
mNetTrackers[ConnectivityManager.TYPE_WIMAX].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");
}
private void enforceMarkNetworkSocketPermission() {
//Media server special case
if (Binder.getCallingUid() == Process.MEDIA_UID) {
return;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MARK_NETWORK_SOCKET,
"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);
// Remove idletimer previously setup in {@code handleConnect}
removeDataActivityTracking(prevNetType);
/*
* 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<Integer> pids = mNetRequestersPids[prevNetType];
for (Integer pid : pids) {
// 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, new NetworkInfo(info));
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
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) {
if (DBG) {
log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + 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;
if (mNetTrackers[checkType] == null) 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());
}
}
}
public void sendConnectedBroadcast(NetworkInfo info) {
enforceConnectivityInternalPermission();
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) {
if (mLockdownTracker != null) {
info = mLockdownTracker.augmentNetworkInfo(info);
}
Intent intent = new Intent(bcastType);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
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);
}
private void sendDataActivityBroadcast(int deviceType, boolean active) {
Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL,
RECEIVE_DATA_ACTIVITY_CHANGE, null, null, 0, null, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
* 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, new NetworkInfo(info));
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
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 (VDBG) {
log("sendStickyBroadcast: action=" + intent.getAction());
}
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
private void sendStickyBroadcastDelayed(Intent intent, int delayMs) {
if (delayMs <= 0) {
sendStickyBroadcast(intent);
} else {
if (VDBG) {
log("sendStickyBroadcastDelayed: delayMs=" + delayMs + ", action="
+ intent.getAction());
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(
EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
}
}
void systemReady() {
mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
loadGlobalProxy();
synchronized(this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
mInitialBroadcast = null;
}
}
// load the global proxy at startup
mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
// Try bringing up tracker, but if KeyStore isn't ready yet, wait
// for user to unlock device.
if (!updateLockdownVpn()) {
final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
mContext.registerReceiver(mUserPresentReceiver, filter);
}
}
private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Try creating lockdown tracker, since user present usually means
// unlocked keystore.
if (updateLockdownVpn()) {
mContext.unregisterReceiver(this);
}
}
};
private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
if (((type != mNetworkPreference)
&& (mNetConfigs[mActiveDefaultNetwork].priority > mNetConfigs[type].priority))
|| (mNetworkPreference == mActiveDefaultNetwork)) {
return false;
}
return true;
}
private void handleConnect(NetworkInfo info) {
final int newNetType = info.getType();
setupDataActivityTracking(newNetType);
// snapshot isFailover, because sendConnectedBroadcast() resets it
boolean isFailover = info.isFailover();
final NetworkStateTracker thisNet = mNetTrackers[newNetType];
final String thisIface = thisNet.getLinkProperties().getInterfaceName();
if (VDBG) {
log("handleConnect: E newNetType=" + newNetType + " thisIface=" + thisIface
+ " isFailover" + isFailover);
}
// if this is a default net and other default is running
// kill the one not preferred
if (mNetConfigs[newNetType].isDefault()) {
if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
// 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;
}
} else {
// don't accept this one
if (VDBG) {
log("Not broadcasting CONNECT_ACTION " +
"to torn down network " + info.getTypeName());
}
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 = newNetType;
// 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);
updateMtuSizeSettings(thisNet);
handleConnectivityChange(newNetType, false);
sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
// notify battery stats service about this network
if (thisIface != null) {
try {
BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
}
}
private void handleCaptivePortalTrackerCheck(NetworkInfo info) {
if (DBG) log("Captive portal check " + info);
int type = info.getType();
final NetworkStateTracker thisNet = mNetTrackers[type];
if (mNetConfigs[type].isDefault()) {
if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
if (isNewNetTypePreferredOverCurrentNetType(type)) {
if (DBG) log("Captive check on " + info.getTypeName());
mCaptivePortalTracker.detectCaptivePortal(new NetworkInfo(info));
return;
} else {
if (DBG) log("Tear down low priority net " + info.getTypeName());
teardown(thisNet);
return;
}
}
}
if (DBG) log("handleCaptivePortalTrackerCheck: call captivePortalCheckComplete ni=" + info);
thisNet.captivePortalCheckComplete();
}
/** @hide */
@Override
public void captivePortalCheckComplete(NetworkInfo info) {
enforceConnectivityInternalPermission();
if (DBG) log("captivePortalCheckComplete: ni=" + info);
mNetTrackers[info.getType()].captivePortalCheckComplete();
}
/** @hide */
@Override
public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
enforceConnectivityInternalPermission();
if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal);
mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
}
/**
* Setup data activity tracking for the given network interface.
*
* Every {@code setupDataActivityTracking} should be paired with a
* {@link removeDataActivityTracking} for cleanup.
*/
private void setupDataActivityTracking(int type) {
final NetworkStateTracker thisNet = mNetTrackers[type];
final String iface = thisNet.getLinkProperties().getInterfaceName();
final int timeout;
if (ConnectivityManager.isNetworkTypeMobile(type)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
0);
// Canonicalize mobile network type
type = ConnectivityManager.TYPE_MOBILE;
} else if (ConnectivityManager.TYPE_WIFI == type) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
0);
} else {
// do not track any other networks
timeout = 0;
}
if (timeout > 0 && iface != null) {
try {
mNetd.addIdleTimer(iface, timeout, Integer.toString(type));
} catch (RemoteException e) {
}
}
}
/**
* Remove data activity tracking when network disconnects.
*/
private void removeDataActivityTracking(int type) {
final NetworkStateTracker net = mNetTrackers[type];
final String iface = net.getLinkProperties().getInterfaceName();
if (iface != null && (ConnectivityManager.isNetworkTypeMobile(type) ||
ConnectivityManager.TYPE_WIFI == type)) {
try {
// the call fails silently if no idletimer setup for this interface
mNetd.removeIdleTimer(iface);
} catch (RemoteException e) {
}
}
}
/**
* 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;
boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType);
if (VDBG) {
log("handleConnectivityChange: netType=" + netType + " doReset=" + doReset
+ " resetMask=" + resetMask);
}
/*
* 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(newLp.getHttpProxy());
}
} 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(), exempt);
if (resetMask != 0 || resetDns) {
if (VDBG) log("handleConnectivityChange: resetting");
if (curLp != null) {
if (VDBG) log("handleConnectivityChange: resetting curLp=" + curLp);
for (String iface : curLp.getAllInterfaceNames()) {
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) {
synchronized(mVpns) {
for (int i = 0; i < mVpns.size(); i++) {
mVpns.valueAt(i).interfaceStatusChanged(iface, false);
}
}
}
}
if (resetDns) {
flushVmDnsCache();
if (VDBG) log("resetting DNS cache for " + iface);
try {
mNetd.flushInterfaceDnsCache(iface);
} catch (Exception e) {
// never crash - catch them all
if (DBG) loge("Exception resetting dns cache: " + e);
}
}
} else {
loge("Can't reset connection for type "+netType);
}
}
}
}
// Update 464xlat state.
NetworkStateTracker tracker = mNetTrackers[netType];
if (mClat.requiresClat(netType, tracker)) {
// If the connection was previously using clat, but is not using it now, stop the clat
// daemon. Normally, this happens automatically when the connection disconnects, but if
// the disconnect is not reported, or if the connection's LinkProperties changed for
// some other reason (e.g., handoff changes the IP addresses on the link), it would
// still be running. If it's not running, then stopping it is a no-op.
if (Nat464Xlat.isRunningClat(curLp) && !Nat464Xlat.isRunningClat(newLp)) {
mClat.stopClat();
}
// If the link requires clat to be running, then start the daemon now.
if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
mClat.startClat(tracker);
} else {
mClat.stopClat();
}
}
// 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(),
PhoneConstants.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, boolean exempt) {
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.compareAllRoutes(newLp);
dnsDiff = curLp.compareDnses(newLp);
} else if (newLp != null) {
routeDiff.added = newLp.getAllRoutes();
dnsDiff.added = newLp.getDnses();
}
boolean routesChanged = (routeDiff.removed.size() != 0 || routeDiff.added.size() != 0);
for (RouteInfo r : routeDiff.removed) {
if (isLinkDefault || ! r.isDefaultRoute()) {
if (VDBG) log("updateRoutes: default remove route r=" + r);
removeRoute(curLp, r, TO_DEFAULT_TABLE);
}
if (isLinkDefault == false) {
// remove from a secondary route table
removeRoute(curLp, r, TO_SECONDARY_TABLE);
}
}
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, exempt);
}
}
} 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, exempt);
}
}
}
for (RouteInfo r : routeDiff.added) {
if (isLinkDefault || ! r.isDefaultRoute()) {
addRoute(newLp, r, TO_DEFAULT_TABLE, exempt);
} else {
// add to a secondary route table
addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT);
// 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();
synchronized (mRoutesLock) {
if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) {
if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
mNetd.removeRoute(ifaceName, r);
} catch (Exception e) {
// never crash - catch them all
if (DBG) loge("Exception trying to remove a route: " + e);
}
}
}
}
}
return routesChanged;
}
/**
* Reads the network specific MTU size from reources.
* and set it on it's iface.
*/
private void updateMtuSizeSettings(NetworkStateTracker nt) {
final String iface = nt.getLinkProperties().getInterfaceName();
final int mtu = nt.getLinkProperties().getMtu();
if (mtu < 68 || mtu > 10000) {
loge("Unexpected mtu value: " + nt);
return;
}
try {
if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
mNetd.setMtu(iface, mtu);
} catch (Exception e) {
Slog.e(TAG, "exception in setMtu()" + e);
}
}
/**
* Reads the network specific TCP buffer sizes from SystemProperties
* net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
* wide use
*/
private void updateNetworkSettings(NetworkStateTracker nt) {
String key = nt.getTcpBufferSizesPropName();
String bufferSizes = key == null ? null : SystemProperties.get(key);
if (TextUtils.isEmpty(bufferSizes)) {
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 pid, boolean doBump)
{
if (VDBG) log("reassessPidDns for pid " + pid);
Integer myPid = new Integer(pid);
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;
if (mNetRequestersPids[i].contains(myPid)) {
try {
mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
} catch (Exception e) {
Slog.e(TAG, "exception reasseses pid dns: " + e);
}
return;
}
}
}
// nothing found - delete
try {
mNetd.clearDnsInterfaceForPid(pid);
} catch (Exception e) {
Slog.e(TAG, "exception clear interface from pid: " + e);
}
}
private void flushVmDnsCache() {
/*
* 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);
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// Caller must grab mDnsLock.
private void updateDnsLocked(String network, String iface,
Collection<InetAddress> dnses, String domains, boolean defaultDns) {
int last = 0;
if (dnses.size() == 0 && mDefaultDns != null) {
dnses = new ArrayList();
dnses.add(mDefaultDns);
if (DBG) {
loge("no dns provided for " + network + " - using " + mDefaultDns.getHostAddress());
}
}
try {
mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
if (defaultDns) {
mNetd.setDefaultInterfaceForDns(iface);
}
for (InetAddress dns : dnses) {
++last;
String key = "net.dns" + last;
String value = dns.getHostAddress();
SystemProperties.set(key, value);
}
for (int i = last + 1; i <= mNumDnsEntries; ++i) {
String key = "net.dns" + i;
SystemProperties.set(key, "");
}
mNumDnsEntries = last;
} catch (Exception e) {
if (DBG) loge("exception setting default dns interface: " + e);
}
}
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();
if (mNetConfigs[netType].isDefault()) {
String network = nt.getNetworkInfo().getTypeName();
synchronized (mDnsLock) {
updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains(), true);
}
} else {
try {
mNetd.setDnsServersForInterface(p.getInterfaceName(),
NetworkUtils.makeStrings(dnses), p.getDomains());
} catch (Exception e) {
if (DBG) loge("exception setting dns servers: " + e);
}
// set per-pid dns for attached secondary nets
List<Integer> pids = mNetRequestersPids[netType];
for (Integer pid : pids) {
try {
mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
} catch (Exception e) {
Slog.e(TAG, "exception setting interface for pid: " + e);
}
}
}
flushVmDnsCache();
}
}
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 writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
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;
}
// TODO: add locking to get atomic snapshot
pw.println();
for (int i = 0; i < mNetTrackers.length; i++) {
final NetworkStateTracker nst = mNetTrackers[i];
if (nst != null) {
pw.println("NetworkStateTracker for " + getNetworkTypeName(i) + ":");
pw.increaseIndent();
if (nst.getNetworkInfo().isConnected()) {
pw.println("Active network: " + nst.getNetworkInfo().
getTypeName());
}
pw.println(nst.getNetworkInfo());
pw.println(nst.getLinkProperties());
pw.println(nst);
pw.println();
pw.decreaseIndent();
}
}
pw.println("Network Requester Pids:");
pw.increaseIndent();
for (int net : mPriorityList) {
String pidString = net + ": ";
for (Integer pid : mNetRequestersPids[net]) {
pidString = pidString + pid.toString() + ", ";
}
pw.println(pidString);
}
pw.println();
pw.decreaseIndent();
pw.println("FeatureUsers:");
pw.increaseIndent();
for (Object requester : mFeatureUsers) {
pw.println(requester.toString());
}
pw.println();
pw.decreaseIndent();
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:");
pw.increaseIndent();
for(int i = 0; i < mInetLog.size(); i++) {
pw.println(mInetLog.get(i));
}
pw.decreaseIndent();
}
}
// must be stateless - things change under us.
private class NetworkStateTrackerHandler extends Handler {
public NetworkStateTrackerHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
NetworkInfo info;
switch (msg.what) {
case NetworkStateTracker.EVENT_STATE_CHANGED: {
info = (NetworkInfo) msg.obj;
NetworkInfo.State state = info.getState();
if (VDBG || (state == NetworkInfo.State.CONNECTED) ||
(state == NetworkInfo.State.DISCONNECTED) ||
(state == NetworkInfo.State.SUSPENDED)) {
log("ConnectivityChange for " +
info.getTypeName() + ": " +
state + "/" + info.getDetailedState());
}
// Since mobile has the notion of a network/apn that can be used for
// provisioning we need to check every time we're connected as
// CaptiveProtalTracker won't detected it because DCT doesn't report it
// as connected as ACTION_ANY_DATA_CONNECTION_STATE_CHANGED instead its
// reported as ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN. Which
// is received by MDST and sent here as EVENT_STATE_CHANGED.
if (ConnectivityManager.isNetworkTypeMobile(info.getType())
&& (0 != Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0))
&& (((state == NetworkInfo.State.CONNECTED)
&& (info.getType() == ConnectivityManager.TYPE_MOBILE))
|| info.isConnectedToProvisioningNetwork())) {
log("ConnectivityChange checkMobileProvisioning for"
+ " TYPE_MOBILE or ProvisioningNetwork");
checkMobileProvisioning(CheckMp.MAX_TIMEOUT_MS);
}
EventLogTags.writeConnectivityStateChanged(
info.getType(), info.getSubtype(), info.getDetailedState().ordinal());
if (info.getDetailedState() ==
NetworkInfo.DetailedState.FAILED) {
handleConnectionFailure(info);
} else if (info.getDetailedState() ==
DetailedState.CAPTIVE_PORTAL_CHECK) {
handleCaptivePortalTrackerCheck(info);
} else if (info.isConnectedToProvisioningNetwork()) {
/**
* TODO: Create ConnectivityManager.TYPE_MOBILE_PROVISIONING
* for now its an in between network, its a network that
* is actually a default network but we don't want it to be
* announced as such to keep background applications from
* trying to use it. It turns out that some still try so we
* take the additional step of clearing any default routes
* to the link that may have incorrectly setup by the lower
* levels.
*/
LinkProperties lp = getLinkProperties(info.getType());
if (DBG) {
log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp);
}
// Clear any default routes setup by the radio so
// any activity by applications trying to use this
// connection will fail until the provisioning network
// is enabled.
for (RouteInfo r : lp.getRoutes()) {
removeRoute(lp, r, TO_DEFAULT_TABLE);
}
} 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);
}
if (mLockdownTracker != null) {
mLockdownTracker.onNetworkInfoChanged(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 NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
info = (NetworkInfo) msg.obj;
int type = info.getType();
updateNetworkSettings(mNetTrackers[type]);
break;
}
}
}
}
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
NetworkInfo info;
switch (msg.what) {
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_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);
break;
}
case EVENT_VPN_STATE_CHANGED: {
if (mLockdownTracker != null) {
mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
}
break;
}
case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: {
int tag = mEnableFailFastMobileDataTag.get();
if (msg.arg1 == tag) {
MobileDataStateTracker mobileDst =
(MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE];
if (mobileDst != null) {
mobileDst.setEnableFailFastMobileData(msg.arg2);
}
} else {
log("EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: stale arg1:" + msg.arg1
+ " != tag:" + tag);
}
break;
}
case EVENT_SAMPLE_INTERVAL_ELAPSED: {
handleNetworkSamplingTimeout();
break;
}
}
}
}
// 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) {
enforceTetherChangePermission();
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();
}
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.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0);
return tetherEnabledInSettings && ((mTethering.getTetherableUsbRegexs().length != 0 ||
mTethering.getTetherableWifiRegexs().length != 0 ||
mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceTypes().length != 0);
}
// 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 (mActiveDefaultNetwork == -1) {
if (DBG) log("handleInetConditionChange: no active default network - ignore");
return;
}
if (mActiveDefaultNetwork != netType) {
if (DBG) log("handleInetConditionChange: net=" + netType +
" != default=" + mActiveDefaultNetwork + " - ignore");
return;
}
if (VDBG) {
log("handleInetConditionChange: net=" +
netType + ", condition=" + condition +
",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
}
mDefaultInetCondition = condition;
int delay;
if (mInetConditionChangeInFlight == false) {
if (VDBG) log("handleInetConditionChange: starting a change hold");
// setup a new hold to debounce this
if (mDefaultInetCondition > 50) {
delay = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
} else {
delay = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.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("handleInetConditionChange: currently in hold - not setting new end evt");
}
}
private void handleInetConditionHoldEnd(int netType, int sequence) {
if (DBG) {
log("handleInetConditionHoldEnd: net=" + netType +
", condition=" + mDefaultInetCondition +
", published condition=" + mDefaultInetConditionPublished);
}
mInetConditionChangeInFlight = false;
if (mActiveDefaultNetwork == -1) {
if (DBG) log("handleInetConditionHoldEnd: no active default network - ignoring");
return;
}
if (mDefaultConnectionSequence != sequence) {
if (DBG) log("handleInetConditionHoldEnd: event hold for obsolete network - ignoring");
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("handleInetConditionHoldEnd: default network not connected - ignoring");
return;
}
mDefaultInetConditionPublished = mDefaultInetCondition;
sendInetConditionBroadcast(networkInfo);
return;
}
public ProxyProperties getProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
// of proxy info to all the JVMs.
// enforceAccessPermission();
synchronized (mProxyLock) {
ProxyProperties ret = mGlobalProxy;
if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy;
return ret;
}
}
public void setGlobalProxy(ProxyProperties proxyProperties) {
enforceConnectivityInternalPermission();
synchronized (mProxyLock) {
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 = "";
String pacFileUrl = "";
if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
!TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) {
mGlobalProxy = new ProxyProperties(proxyProperties);
host = mGlobalProxy.getHost();
port = mGlobalProxy.getPort();
exclList = mGlobalProxy.getExclusionList();
if (proxyProperties.getPacFileUrl() != null) {
pacFileUrl = proxyProperties.getPacFileUrl();
}
} else {
mGlobalProxy = null;
}
ContentResolver res = mContext.getContentResolver();
final long token = Binder.clearCallingIdentity();
try {
Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, host);
Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port);
Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
exclList);
Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
} finally {
Binder.restoreCallingIdentity(token);
}
}
if (mGlobalProxy == null) {
proxyProperties = mDefaultProxy;
}
sendProxyBroadcast(proxyProperties);
}
private void loadGlobalProxy() {
ContentResolver res = mContext.getContentResolver();
String host = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST);
int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0);
String exclList = Settings.Global.getString(res,
Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC);
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
ProxyProperties proxyProperties;
if (!TextUtils.isEmpty(pacFileUrl)) {
proxyProperties = new ProxyProperties(pacFileUrl);
} else {
proxyProperties = new ProxyProperties(host, port, exclList);
}
synchronized (mProxyLock) {
mGlobalProxy = proxyProperties;
}
}
}
public ProxyProperties getGlobalProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
// of proxy info to all the JVMs.
// enforceAccessPermission();
synchronized (mProxyLock) {
return mGlobalProxy;
}
}
private void handleApplyDefaultProxy(ProxyProperties proxy) {
if (proxy != null && TextUtils.isEmpty(proxy.getHost())
&& TextUtils.isEmpty(proxy.getPacFileUrl())) {
proxy = null;
}
synchronized (mProxyLock) {
if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
if (mDefaultProxy == proxy) return; // catches repeated nulls
mDefaultProxy = proxy;
if (mGlobalProxy != null) return;
if (!mDefaultProxyDisabled) {
sendProxyBroadcast(proxy);
}
}
}
private void handleDeprecatedGlobalHttpProxy() {
String proxy = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.HTTP_PROXY);
if (!TextUtils.isEmpty(proxy)) {
String data[] = proxy.split(":");
if (data.length == 0) {
return;
}
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 (mPacManager.setCurrentProxyScriptUrl(proxy)) return;
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);
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
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.Global.getUriFor(
Settings.Global.HTTP_PROXY), false, this);
}
@Override
public void onChange(boolean selfChange) {
mHandler.obtainMessage(mWhat).sendToTarget();
}
}
private static void log(String s) {
Slog.d(TAG, s);
}
private static 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) {
throwIfLockdownEnabled();
try {
int type = mActiveDefaultNetwork;
int user = UserHandle.getUserId(Binder.getCallingUid());
if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
synchronized(mVpns) {
mVpns.get(user).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) {
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
return mVpns.get(user).prepare(oldPackage, newPackage);
}
}
@Override
public void markSocketAsUser(ParcelFileDescriptor socket, int uid) {
enforceMarkNetworkSocketPermission();
final long token = Binder.clearCallingIdentity();
try {
int mark = mNetd.getMarkForUid(uid);
// Clear the mark on the socket if no mark is needed to prevent socket reuse issues
if (mark == -1) {
mark = 0;
}
NetworkUtils.markSocket(socket.getFd(), mark);
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* 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) {
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
return mVpns.get(user).establish(config);
}
}
/**
* Start legacy VPN, controlling native daemons as needed. Creates a
* secondary thread to perform connection work, returning quickly.
*/
@Override
public void startLegacyVpn(VpnProfile profile) {
throwIfLockdownEnabled();
final LinkProperties egress = getActiveLinkProperties();
if (egress == null) {
throw new IllegalStateException("Missing active network connection");
}
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
}
}
/**
* 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() {
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
return mVpns.get(user).getLegacyVpnInfo();
}
}
/**
* Returns the information of the ongoing VPN. This method is used by VpnDialogs and
* not available in ConnectivityManager.
* Permissions are checked in Vpn class.
* @hide
*/
@Override
public VpnConfig getVpnConfig() {
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
return mVpns.get(user).getVpnConfig();
}
}
/**
* 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 onStateChanged(NetworkInfo info) {
mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
}
public void override(String iface, 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.
synchronized (mDnsLock) {
updateDnsLocked("VPN", iface, addresses, domains, false);
}
// Temporarily disable the default proxy (not global).
synchronized (mProxyLock) {
mDefaultProxyDisabled = true;
if (mGlobalProxy == null && mDefaultProxy != null) {
sendProxyBroadcast(null);
}
}
// TODO: support proxy per network.
}
public void restore() {
synchronized (mProxyLock) {
mDefaultProxyDisabled = false;
if (mGlobalProxy == null && mDefaultProxy != null) {
sendProxyBroadcast(mDefaultProxy);
}
}
}
public void protect(ParcelFileDescriptor socket) {
try {
final int mark = mNetd.getMarkForProtect();
NetworkUtils.markSocket(socket.getFd(), mark);
} catch (RemoteException e) {
}
}
public void setRoutes(String interfaze, List<RouteInfo> routes) {
for (RouteInfo route : routes) {
try {
mNetd.setMarkedForwardingRoute(interfaze, route);
} catch (RemoteException e) {
}
}
}
public void setMarkedForwarding(String interfaze) {
try {
mNetd.setMarkedForwarding(interfaze);
} catch (RemoteException e) {
}
}
public void clearMarkedForwarding(String interfaze) {
try {
mNetd.clearMarkedForwarding(interfaze);
} catch (RemoteException e) {
}
}
public void addUserForwarding(String interfaze, int uid) {
int uidStart = uid * UserHandle.PER_USER_RANGE;
int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
addUidForwarding(interfaze, uidStart, uidEnd);
}
public void clearUserForwarding(String interfaze, int uid) {
int uidStart = uid * UserHandle.PER_USER_RANGE;
int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
clearUidForwarding(interfaze, uidStart, uidEnd);
}
public void addUidForwarding(String interfaze, int uidStart, int uidEnd) {
try {
mNetd.setUidRangeRoute(interfaze,uidStart, uidEnd);
mNetd.setDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
} catch (RemoteException e) {
}
}
public void clearUidForwarding(String interfaze, int uidStart, int uidEnd) {
try {
mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd);
} catch (RemoteException e) {
}
}
}
@Override
public boolean updateLockdownVpn() {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
Slog.w(TAG, "Lockdown VPN only available to AID_SYSTEM");
return false;
}
// Tear down existing lockdown if profile was removed
mLockdownEnabled = LockdownVpnTracker.isEnabled();
if (mLockdownEnabled) {
if (!mKeyStore.isUnlocked()) {
Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
return false;
}
final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
final VpnProfile profile = VpnProfile.decode(
profileName, mKeyStore.get(Credentials.VPN + profileName));
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
profile));
}
} else {
setLockdownTracker(null);
}
return true;
}
/**
* Internally set new {@link LockdownVpnTracker}, shutting down any existing
* {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
*/
private void setLockdownTracker(LockdownVpnTracker tracker) {
// Shutdown any existing tracker
final LockdownVpnTracker existing = mLockdownTracker;
mLockdownTracker = null;
if (existing != null) {
existing.shutdown();
}
try {
if (tracker != null) {
mNetd.setFirewallEnabled(true);
mNetd.setFirewallInterfaceRule("lo", true);
mLockdownTracker = tracker;
mLockdownTracker.init();
} else {
mNetd.setFirewallEnabled(false);
}
} catch (RemoteException e) {
// ignored; NMS lives inside system_server
}
}
private void throwIfLockdownEnabled() {
if (mLockdownEnabled) {
throw new IllegalStateException("Unavailable in lockdown mode");
}
}
public void supplyMessenger(int networkType, Messenger messenger) {
enforceConnectivityInternalPermission();
if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
mNetTrackers[networkType].supplyMessenger(messenger);
}
}
public int findConnectionTypeForIface(String iface) {
enforceConnectivityInternalPermission();
if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
for (NetworkStateTracker tracker : mNetTrackers) {
if (tracker != null) {
LinkProperties lp = tracker.getLinkProperties();
if (lp != null && iface.equals(lp.getInterfaceName())) {
return tracker.getNetworkInfo().getType();
}
}
}
return ConnectivityManager.TYPE_NONE;
}
/**
* Have mobile data fail fast if enabled.
*
* @param enabled DctConstants.ENABLED/DISABLED
*/
private void setEnableFailFastMobileData(int enabled) {
int tag;
if (enabled == DctConstants.ENABLED) {
tag = mEnableFailFastMobileDataTag.incrementAndGet();
} else {
tag = mEnableFailFastMobileDataTag.get();
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_ENABLE_FAIL_FAST_MOBILE_DATA, tag,
enabled));
}
private boolean isMobileDataStateTrackerReady() {
MobileDataStateTracker mdst =
(MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
return (mdst != null) && (mdst.isReady());
}
/**
* The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE)
*/
/**
* No connection was possible to the network.
* This is NOT a warm sim.
*/
private static final int CMP_RESULT_CODE_NO_CONNECTION = 0;
/**
* A connection was made to the internet, all is well.
* This is NOT a warm sim.
*/
private static final int CMP_RESULT_CODE_CONNECTABLE = 1;
/**
* A connection was made but no dns server was available to resolve a name to address.
* This is NOT a warm sim since provisioning network is supported.
*/
private static final int CMP_RESULT_CODE_NO_DNS = 2;
/**
* A connection was made but could not open a TCP connection.
* This is NOT a warm sim since provisioning network is supported.
*/
private static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 3;
/**
* A connection was made but there was a redirection, we appear to be in walled garden.
* This is an indication of a warm sim on a mobile network such as T-Mobile.
*/
private static final int CMP_RESULT_CODE_REDIRECTED = 4;
/**
* The mobile network is a provisioning network.
* This is an indication of a warm sim on a mobile network such as AT&T.
*/
private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5;
private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false);
@Override
public int checkMobileProvisioning(int suggestedTimeOutMs) {
int timeOutMs = -1;
if (DBG) log("checkMobileProvisioning: E suggestedTimeOutMs=" + suggestedTimeOutMs);
enforceConnectivityInternalPermission();
final long token = Binder.clearCallingIdentity();
try {
timeOutMs = suggestedTimeOutMs;
if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) {
timeOutMs = CheckMp.MAX_TIMEOUT_MS;
}
// Check that mobile networks are supported
if (!isNetworkSupported(ConnectivityManager.TYPE_MOBILE)
|| !isNetworkSupported(ConnectivityManager.TYPE_MOBILE_HIPRI)) {
if (DBG) log("checkMobileProvisioning: X no mobile network");
return timeOutMs;
}
// If we're already checking don't do it again
// TODO: Add a queue of results...
if (mIsCheckingMobileProvisioning.getAndSet(true)) {
if (DBG) log("checkMobileProvisioning: X already checking ignore for the moment");
return timeOutMs;
}
// Start off with notification off
setProvNotificationVisible(false, ConnectivityManager.TYPE_NONE, null, null);
CheckMp checkMp = new CheckMp(mContext, this);
CheckMp.CallBack cb = new CheckMp.CallBack() {
@Override
void onComplete(Integer result) {
if (DBG) log("CheckMp.onComplete: result=" + result);
NetworkInfo ni =
mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
switch(result) {
case CMP_RESULT_CODE_CONNECTABLE:
case CMP_RESULT_CODE_NO_CONNECTION:
case CMP_RESULT_CODE_NO_DNS:
case CMP_RESULT_CODE_NO_TCP_CONNECTION: {
if (DBG) log("CheckMp.onComplete: ignore, connected or no connection");
break;
}
case CMP_RESULT_CODE_REDIRECTED: {
if (DBG) log("CheckMp.onComplete: warm sim");
String url = getMobileProvisioningUrl();
if (TextUtils.isEmpty(url)) {
url = getMobileRedirectedProvisioningUrl();
}
if (TextUtils.isEmpty(url) == false) {
if (DBG) log("CheckMp.onComplete: warm (redirected), url=" + url);
setProvNotificationVisible(true, ni.getType(), ni.getExtraInfo(),
url);
} else {
if (DBG) log("CheckMp.onComplete: warm (redirected), no url");
}
break;
}
case CMP_RESULT_CODE_PROVISIONING_NETWORK: {
String url = getMobileProvisioningUrl();
if (TextUtils.isEmpty(url) == false) {
if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), url=" + url);
setProvNotificationVisible(true, ni.getType(), ni.getExtraInfo(),
url);
} else {
if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
}
break;
}
default: {
loge("CheckMp.onComplete: ignore unexpected result=" + result);
break;
}
}
mIsCheckingMobileProvisioning.set(false);
}
};
CheckMp.Params params =
new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
if (DBG) log("checkMobileProvisioning: params=" + params);
checkMp.execute(params);
} finally {
Binder.restoreCallingIdentity(token);
if (DBG) log("checkMobileProvisioning: X");
}
return timeOutMs;
}
static class CheckMp extends
AsyncTask<CheckMp.Params, Void, Integer> {
private static final String CHECKMP_TAG = "CheckMp";
public static final int MAX_TIMEOUT_MS = 60000;
private static final int SOCKET_TIMEOUT_MS = 5000;
private Context mContext;
private ConnectivityService mCs;
private TelephonyManager mTm;
private Params mParams;
/**
* Parameters for AsyncTask.execute
*/
static class Params {
private String mUrl;
private long mTimeOutMs;
private CallBack mCb;
Params(String url, long timeOutMs, CallBack cb) {
mUrl = url;
mTimeOutMs = timeOutMs;
mCb = cb;
}
@Override
public String toString() {
return "{" + " url=" + mUrl + " mTimeOutMs=" + mTimeOutMs + " mCb=" + mCb + "}";
}
}
/**
* The call back object passed in Params. onComplete will be called
* on the main thread.
*/
abstract static class CallBack {
// Called on the main thread.
abstract void onComplete(Integer result);
}
public CheckMp(Context context, ConnectivityService cs) {
mContext = context;
mCs = cs;
// Setup access to TelephonyService we'll be using.
mTm = (TelephonyManager) mContext.getSystemService(
Context.TELEPHONY_SERVICE);
}
/**
* Get the default url to use for the test.
*/
public String getDefaultUrl() {
// See http://go/clientsdns for usage approval
String server = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.CAPTIVE_PORTAL_SERVER);
if (server == null) {
server = "clients3.google.com";
}
return "http://" + server + "/generate_204";
}
/**
* Detect if its possible to connect to the http url. DNS based detection techniques
* do not work at all hotspots. The best way to check is to perform a request to
* a known address that fetches the data we expect.
*/
private synchronized Integer isMobileOk(Params params) {
Integer result = CMP_RESULT_CODE_NO_CONNECTION;
Uri orgUri = Uri.parse(params.mUrl);
Random rand = new Random();
mParams = params;
if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
result = CMP_RESULT_CODE_NO_CONNECTION;
log("isMobileOk: X not mobile capable result=" + result);
return result;
}
// See if we've already determined we've got a provisioning connection,
// if so we don't need to do anything active.
MobileDataStateTracker mdstDefault = (MobileDataStateTracker)
mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE];
boolean isDefaultProvisioning = mdstDefault.isProvisioningNetwork();
log("isMobileOk: isDefaultProvisioning=" + isDefaultProvisioning);
MobileDataStateTracker mdstHipri = (MobileDataStateTracker)
mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
boolean isHipriProvisioning = mdstHipri.isProvisioningNetwork();
log("isMobileOk: isHipriProvisioning=" + isHipriProvisioning);
if (isDefaultProvisioning || isHipriProvisioning) {
result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
log("isMobileOk: X default || hipri is provisioning result=" + result);
return result;
}
try {
// Continue trying to connect until time has run out
long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs;
if (!mCs.isMobileDataStateTrackerReady()) {
// Wait for MobileDataStateTracker to be ready.
if (DBG) log("isMobileOk: mdst is not ready");
while(SystemClock.elapsedRealtime() < endTime) {
if (mCs.isMobileDataStateTrackerReady()) {
// Enable fail fast as we'll do retries here and use a
// hipri connection so the default connection stays active.
if (DBG) log("isMobileOk: mdst ready, enable fail fast of mobile data");
mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
break;
}
sleep(1);
}
}
log("isMobileOk: start hipri url=" + params.mUrl);
// First wait until we can start using hipri
Binder binder = new Binder();
while(SystemClock.elapsedRealtime() < endTime) {
int ret = mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
Phone.FEATURE_ENABLE_HIPRI, binder);
if ((ret == PhoneConstants.APN_ALREADY_ACTIVE)
|| (ret == PhoneConstants.APN_REQUEST_STARTED)) {
log("isMobileOk: hipri started");
break;
}
if (VDBG) log("isMobileOk: hipri not started yet");
result = CMP_RESULT_CODE_NO_CONNECTION;
sleep(1);
}
// Continue trying to connect until time has run out
while(SystemClock.elapsedRealtime() < endTime) {
try {
// Wait for hipri to connect.
// TODO: Don't poll and handle situation where hipri fails
// because default is retrying. See b/9569540
NetworkInfo.State state = mCs
.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
if (state != NetworkInfo.State.CONNECTED) {
if (true/*VDBG*/) {
log("isMobileOk: not connected ni=" +
mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
}
sleep(1);
result = CMP_RESULT_CODE_NO_CONNECTION;
continue;
}
// Hipri has started check if this is a provisioning url
MobileDataStateTracker mdst = (MobileDataStateTracker)
mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
if (mdst.isProvisioningNetwork()) {
result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
if (DBG) log("isMobileOk: X isProvisioningNetwork result=" + result);
return result;
} else {
if (DBG) log("isMobileOk: isProvisioningNetwork is false, continue");
}
// Get of the addresses associated with the url host. We need to use the
// address otherwise HttpURLConnection object will use the name to get
// the addresses and is will try every address but that will bypass the
// route to host we setup and the connection could succeed as the default
// interface might be connected to the internet via wifi or other interface.
InetAddress[] addresses;
try {
addresses = InetAddress.getAllByName(orgUri.getHost());
} catch (UnknownHostException e) {
result = CMP_RESULT_CODE_NO_DNS;
log("isMobileOk: X UnknownHostException result=" + result);
return result;
}
log("isMobileOk: addresses=" + inetAddressesToString(addresses));
// Get the type of addresses supported by this link
LinkProperties lp = mCs.getLinkProperties(
ConnectivityManager.TYPE_MOBILE_HIPRI);
boolean linkHasIpv4 = lp.hasIPv4Address();
boolean linkHasIpv6 = lp.hasIPv6Address();
log("isMobileOk: linkHasIpv4=" + linkHasIpv4
+ " linkHasIpv6=" + linkHasIpv6);
// Loop through at most 3 valid addresses or all of the address or until
// we run out of time
int loops = Math.min(3, addresses.length);
for(int validAddr=0, addrTried=0;
(validAddr < loops) && (addrTried < addresses.length)
&& (SystemClock.elapsedRealtime() < endTime);
addrTried ++) {
// Choose the address at random but make sure its type is supported
InetAddress hostAddr = addresses[rand.nextInt(addresses.length)];
if (((hostAddr instanceof Inet4Address) && linkHasIpv4)
|| ((hostAddr instanceof Inet6Address) && linkHasIpv6)) {
// Valid address, so use it
validAddr += 1;
} else {
// Invalid address so try next address
continue;
}
// Make a route to host so we check the specific interface.
if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
hostAddr.getAddress())) {
// Wait a short time to be sure the route is established ??
log("isMobileOk:"
+ " wait to establish route to hostAddr=" + hostAddr);
sleep(3);
} else {
log("isMobileOk:"
+ " could not establish route to hostAddr=" + hostAddr);
continue;
}
// Rewrite the url to have numeric address to use the specific route.
// I also set the "Connection" to "Close" as by default "Keep-Alive"
// is used which is useless in this case.
URL newUrl = new URL(orgUri.getScheme() + "://"
+ hostAddr.getHostAddress() + orgUri.getPath());
log("isMobileOk: newUrl=" + newUrl);
HttpURLConnection urlConn = null;
try {
// Open the connection set the request header and get the response
urlConn = (HttpURLConnection) newUrl.openConnection(
java.net.Proxy.NO_PROXY);
urlConn.setInstanceFollowRedirects(false);
urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
urlConn.setUseCaches(false);
urlConn.setAllowUserInteraction(false);
urlConn.setRequestProperty("Connection", "close");
int responseCode = urlConn.getResponseCode();
// For debug display the headers
Map<String, List<String>> headers = urlConn.getHeaderFields();
log("isMobileOk: headers=" + headers);
// Close the connection
urlConn.disconnect();
urlConn = null;
if (responseCode == 204) {
// Return
result = CMP_RESULT_CODE_CONNECTABLE;
log("isMobileOk: X expected responseCode=" + responseCode
+ " result=" + result);
return result;
} else {
// Retry to be sure this was redirected, we've gotten
// occasions where a server returned 200 even though
// the device didn't have a "warm" sim.
log("isMobileOk: not expected responseCode=" + responseCode);
result = CMP_RESULT_CODE_REDIRECTED;
}
} catch (Exception e) {
log("isMobileOk: HttpURLConnection Exception e=" + e);
result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
if (urlConn != null) {
urlConn.disconnect();
urlConn = null;
}
}
}
log("isMobileOk: X loops|timed out result=" + result);
return result;
} catch (Exception e) {
log("isMobileOk: Exception e=" + e);
continue;
}
}
log("isMobileOk: timed out");
} finally {
log("isMobileOk: F stop hipri");
mCs.setEnableFailFastMobileData(DctConstants.DISABLED);
mCs.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
Phone.FEATURE_ENABLE_HIPRI);
// Wait for hipri to disconnect.
long endTime = SystemClock.elapsedRealtime() + 5000;
while(SystemClock.elapsedRealtime() < endTime) {
NetworkInfo.State state = mCs
.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
if (state != NetworkInfo.State.DISCONNECTED) {
if (VDBG) {
log("isMobileOk: connected ni=" +
mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
}
sleep(1);
continue;
}
}
log("isMobileOk: X result=" + result);
}
return result;
}
@Override
protected Integer doInBackground(Params... params) {
return isMobileOk(params[0]);
}
@Override
protected void onPostExecute(Integer result) {
log("onPostExecute: result=" + result);
if ((mParams != null) && (mParams.mCb != null)) {
mParams.mCb.onComplete(result);
}
}
private String inetAddressesToString(InetAddress[] addresses) {
StringBuffer sb = new StringBuffer();
boolean firstTime = true;
for(InetAddress addr : addresses) {
if (firstTime) {
firstTime = false;
} else {
sb.append(",");
}
sb.append(addr);
}
return sb.toString();
}
private void printNetworkInfo() {
boolean hasIccCard = mTm.hasIccCard();
int simState = mTm.getSimState();
log("hasIccCard=" + hasIccCard
+ " simState=" + simState);
NetworkInfo[] ni = mCs.getAllNetworkInfo();
if (ni != null) {
log("ni.length=" + ni.length);
for (NetworkInfo netInfo: ni) {
log("netInfo=" + netInfo.toString());
}
} else {
log("no network info ni=null");
}
}
/**
* Sleep for a few seconds then return.
* @param seconds
*/
private static void sleep(int seconds) {
try {
Thread.sleep(seconds * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void log(String s) {
Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
}
}
// TODO: Move to ConnectivityManager and make public?
private static final String CONNECTED_TO_PROVISIONING_NETWORK_ACTION =
"com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION";
private BroadcastReceiver mProvisioningReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(CONNECTED_TO_PROVISIONING_NETWORK_ACTION)) {
handleMobileProvisioningAction(intent.getStringExtra("EXTRA_URL"));
}
}
};
private void handleMobileProvisioningAction(String url) {
// Notication mark notification as not visible
setProvNotificationVisible(false, ConnectivityManager.TYPE_NONE, null, null);
// If provisioning network handle as a special case,
// otherwise launch browser with the intent directly.
NetworkInfo ni = getProvisioningNetworkInfo();
if ((ni != null) && ni.isConnectedToProvisioningNetwork()) {
if (DBG) log("handleMobileProvisioningAction: on provisioning network");
MobileDataStateTracker mdst = (MobileDataStateTracker)
mNetTrackers[ConnectivityManager.TYPE_MOBILE];
mdst.enableMobileProvisioning(url);
} else {
if (DBG) log("handleMobileProvisioningAction: on default network");
Intent newIntent =
new Intent(Intent.ACTION_VIEW, Uri.parse(url));
newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mContext.startActivity(newIntent);
} catch (ActivityNotFoundException e) {
loge("handleMobileProvisioningAction: startActivity failed" + e);
}
}
}
private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
private volatile boolean mIsNotificationVisible = false;
private void setProvNotificationVisible(boolean visible, int networkType, String extraInfo,
String url) {
if (DBG) {
log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType
+ " extraInfo=" + extraInfo + " url=" + url);
}
Resources r = Resources.getSystem();
NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
if (visible) {
CharSequence title;
CharSequence details;
int icon;
Intent intent;
Notification notification = new Notification();
switch (networkType) {
case ConnectivityManager.TYPE_WIFI:
title = r.getString(R.string.wifi_available_sign_in, 0);
details = r.getString(R.string.network_available_sign_in_detailed,
extraInfo);
icon = R.drawable.stat_notify_wifi_in_range;
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
Intent.FLAG_ACTIVITY_NEW_TASK);
notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
break;
case ConnectivityManager.TYPE_MOBILE:
case ConnectivityManager.TYPE_MOBILE_HIPRI:
title = r.getString(R.string.network_available_sign_in, 0);
// TODO: Change this to pull from NetworkInfo once a printable
// name has been added to it
details = mTelephonyManager.getNetworkOperatorName();
icon = R.drawable.stat_notify_rssi_in_range;
intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
intent.putExtra("EXTRA_URL", url);
intent.setFlags(0);
notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
break;
default:
title = r.getString(R.string.network_available_sign_in, 0);
details = r.getString(R.string.network_available_sign_in_detailed,
extraInfo);
icon = R.drawable.stat_notify_rssi_in_range;
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
Intent.FLAG_ACTIVITY_NEW_TASK);
notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
break;
}
notification.when = 0;
notification.icon = icon;
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.tickerText = title;
notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
try {
notificationManager.notify(NOTIFICATION_ID, 1, notification);
} catch (NullPointerException npe) {
loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
npe.printStackTrace();
}
} else {
try {
notificationManager.cancel(NOTIFICATION_ID, 1);
} catch (NullPointerException npe) {
loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
npe.printStackTrace();
}
}
mIsNotificationVisible = visible;
}
/** Location to an updatable file listing carrier provisioning urls.
* An example:
*
* <?xml version="1.0" encoding="utf-8"?>
* <provisioningUrls>
* <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&amp;iccid=%1$s&amp;imei=%2$s</provisioningUrl>
* <redirectedUrl mcc="310" mnc="4">http://www.google.com</redirectedUrl>
* </provisioningUrls>
*/
private static final String PROVISIONING_URL_PATH =
"/data/misc/radio/provisioning_urls.xml";
private final File mProvisioningUrlFile = new File(PROVISIONING_URL_PATH);
/** XML tag for root element. */
private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
/** XML tag for individual url */
private static final String TAG_PROVISIONING_URL = "provisioningUrl";
/** XML tag for redirected url */
private static final String TAG_REDIRECTED_URL = "redirectedUrl";
/** XML attribute for mcc */
private static final String ATTR_MCC = "mcc";
/** XML attribute for mnc */
private static final String ATTR_MNC = "mnc";
private static final int REDIRECTED_PROVISIONING = 1;
private static final int PROVISIONING = 2;
private String getProvisioningUrlBaseFromFile(int type) {
FileReader fileReader = null;
XmlPullParser parser = null;
Configuration config = mContext.getResources().getConfiguration();
String tagType;
switch (type) {
case PROVISIONING:
tagType = TAG_PROVISIONING_URL;
break;
case REDIRECTED_PROVISIONING:
tagType = TAG_REDIRECTED_URL;
break;
default:
throw new RuntimeException("getProvisioningUrlBaseFromFile: Unexpected parameter " +
type);
}
try {
fileReader = new FileReader(mProvisioningUrlFile);
parser = Xml.newPullParser();
parser.setInput(fileReader);
XmlUtils.beginDocument(parser, TAG_PROVISIONING_URLS);
while (true) {
XmlUtils.nextElement(parser);
String element = parser.getName();
if (element == null) break;
if (element.equals(tagType)) {
String mcc = parser.getAttributeValue(null, ATTR_MCC);
try {
if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
String mnc = parser.getAttributeValue(null, ATTR_MNC);
if (mnc != null && Integer.parseInt(mnc) == config.mnc) {
parser.next();
if (parser.getEventType() == XmlPullParser.TEXT) {
return parser.getText();
}
}
}
} catch (NumberFormatException e) {
loge("NumberFormatException in getProvisioningUrlBaseFromFile: " + e);
}
}
}
return null;
} catch (FileNotFoundException e) {
loge("Carrier Provisioning Urls file not found");
} catch (XmlPullParserException e) {
loge("Xml parser exception reading Carrier Provisioning Urls file: " + e);
} catch (IOException e) {
loge("I/O exception reading Carrier Provisioning Urls file: " + e);
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {}
}
}
return null;
}
@Override
public String getMobileRedirectedProvisioningUrl() {
enforceConnectivityInternalPermission();
String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
if (TextUtils.isEmpty(url)) {
url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
}
return url;
}
@Override
public String getMobileProvisioningUrl() {
enforceConnectivityInternalPermission();
String url = getProvisioningUrlBaseFromFile(PROVISIONING);
if (TextUtils.isEmpty(url)) {
url = mContext.getResources().getString(R.string.mobile_provisioning_url);
log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
} else {
log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url);
}
// populate the iccid, imei and phone number in the provisioning url.
if (!TextUtils.isEmpty(url)) {
String phoneNumber = mTelephonyManager.getLine1Number();
if (TextUtils.isEmpty(phoneNumber)) {
phoneNumber = "0000000000";
}
url = String.format(url,
mTelephonyManager.getSimSerialNumber() /* ICCID */,
mTelephonyManager.getDeviceId() /* IMEI */,
phoneNumber /* Phone numer */);
}
return url;
}
@Override
public void setProvisioningNotificationVisible(boolean visible, int networkType,
String extraInfo, String url) {
enforceConnectivityInternalPermission();
setProvNotificationVisible(visible, networkType, extraInfo, url);
}
@Override
public void setAirplaneMode(boolean enable) {
enforceConnectivityInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", enable);
mContext.sendBroadcast(intent);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void onUserStart(int userId) {
synchronized(mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn != null) {
loge("Starting user already has a VPN");
return;
}
userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId);
mVpns.put(userId, userVpn);
userVpn.startMonitoring(mContext, mTrackerHandler);
}
}
private void onUserStop(int userId) {
synchronized(mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn == null) {
loge("Stopping user has no VPN");
return;
}
mVpns.delete(userId);
}
}
private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId == UserHandle.USER_NULL) return;
if (Intent.ACTION_USER_STARTING.equals(action)) {
onUserStart(userId);
} else if (Intent.ACTION_USER_STOPPING.equals(action)) {
onUserStop(userId);
}
}
};
@Override
public LinkQualityInfo getLinkQualityInfo(int networkType) {
enforceAccessPermission();
if (isNetworkTypeValid(networkType)) {
return mNetTrackers[networkType].getLinkQualityInfo();
} else {
return null;
}
}
@Override
public LinkQualityInfo getActiveLinkQualityInfo() {
enforceAccessPermission();
if (isNetworkTypeValid(mActiveDefaultNetwork)) {
return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo();
} else {
return null;
}
}
@Override
public LinkQualityInfo[] getAllLinkQualityInfo() {
enforceAccessPermission();
final ArrayList<LinkQualityInfo> result = Lists.newArrayList();
for (NetworkStateTracker tracker : mNetTrackers) {
if (tracker != null) {
LinkQualityInfo li = tracker.getLinkQualityInfo();
if (li != null) {
result.add(li);
}
}
}
return result.toArray(new LinkQualityInfo[result.size()]);
}
/* Infrastructure for network sampling */
private void handleNetworkSamplingTimeout() {
log("Sampling interval elapsed, updating statistics ..");
// initialize list of interfaces ..
Map<String, SamplingDataTracker.SamplingSnapshot> mapIfaceToSample =
new HashMap<String, SamplingDataTracker.SamplingSnapshot>();
for (NetworkStateTracker tracker : mNetTrackers) {
if (tracker != null) {
String ifaceName = tracker.getNetworkInterfaceName();
if (ifaceName != null) {
mapIfaceToSample.put(ifaceName, null);
}
}
}
// Read samples for all interfaces
SamplingDataTracker.getSamplingSnapshots(mapIfaceToSample);
// process samples for all networks
for (NetworkStateTracker tracker : mNetTrackers) {
if (tracker != null) {
String ifaceName = tracker.getNetworkInterfaceName();
SamplingDataTracker.SamplingSnapshot ss = mapIfaceToSample.get(ifaceName);
if (ss != null) {
// end the previous sampling cycle
tracker.stopSampling(ss);
// start a new sampling cycle ..
tracker.startSampling(ss);
}
}
}
log("Done.");
int samplingIntervalInSeconds = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
DEFAULT_SAMPLING_INTERVAL_IN_SECONDS);
if (DBG) log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds");
setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent);
}
void setAlarm(int timeoutInMilliseconds, PendingIntent intent) {
long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
}
}