package com.android.server;
-import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
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.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
-import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
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.INetworkStatsService;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.Uri;
import android.net.LinkProperties.CompareResult;
import android.net.MobileDataStateTracker;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
import android.net.NetworkQuotaInfo;
import android.net.NetworkState;
import android.net.NetworkStateTracker;
import android.net.RouteInfo;
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.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.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.EventLog;
import android.util.Slog;
import android.util.SparseIntArray;
+import android.util.Xml;
+import com.android.internal.R;
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.Nat464Xlat;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
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.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
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";
+
// used in recursive route setting to add gateways for the host for which
// a host route was requested.
private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
private Tethering mTethering;
- private boolean mTetheringConfigValid = false;
private KeyStore mKeyStore;
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. */
* A per Net list of the PID's that requested access to the net
* used both as a refcount and for per-PID DNS selection
*/
- private List mNetRequestersPids[];
+ private List<Integer> mNetRequestersPids[];
// priority order of the nettrackers
// (excluding dynamically set mNetworkPreference)
private static final int EVENT_VPN_STATE_CHANGED = 14;
+ /**
+ * Used internally to disable fail fast of mobile data
+ */
+ private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 15;
+
/** Handler used for internal events. */
private InternalHandler mHandler;
/** Handler used for incoming {@link NetworkStateTracker} events. */
// track the current default http proxy - tell the world if we get a new one (real change)
private ProxyProperties mDefaultProxy = null;
- private Object mDefaultProxyLock = new Object();
+ private Object mProxyLock = new Object();
private boolean mDefaultProxyDisabled = false;
// track the global proxy.
private ProxyProperties mGlobalProxy = null;
- private final Object mGlobalProxyLock = new Object();
private SettingsObserver mSettingsObserver;
// the set of network types that can only be enabled by system/sig apps
List mProtectedNetworks;
+ private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
+
+ TelephonyManager mTelephonyManager;
+
+ // We only want one checkMobileProvisioning after booting.
+ volatile boolean mFirstProvisioningCheckStarted = false;
+
public ConnectivityService(Context context, INetworkManagementService netd,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
// Currently, omitting a NetworkFactory will create one internally
mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
mKeyStore = KeyStore.getInstance();
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
try {
mPolicyManager.registerListener(mPolicyListener);
ConnectivityManager.MAX_NETWORK_TYPE+1];
mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
- mNetworkPreference = getPersistedNetworkPreference();
-
mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
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) {
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);
}
}
- mNetRequestersPids = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
+ // 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();
+ mNetRequestersPids[i] = new ArrayList<Integer>();
}
mFeatureUsers = new ArrayList<FeatureUser>();
- mNumDnsEntries = 0;
-
mTestMode = SystemProperties.get("cm.test.mode").equals("true")
&& SystemProperties.get("ro.build.type").equals("eng");
}
mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
- mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
- mTethering.getTetherableWifiRegexs().length != 0 ||
- mTethering.getTetherableBluetoothRegexs().length != 0) &&
- mTethering.getUpstreamIfaceTypes().length != 0);
- mVpn = new Vpn(mContext, mVpnCallback, mNetd);
+ mVpn = new Vpn(mContext, mVpnCallback, mNetd, this);
mVpn.startMonitoring(mContext, mTrackerHandler);
+ 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);
}
final int networkPrefSetting = Settings.Global
.getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1);
- if (networkPrefSetting != -1) {
- return networkPrefSetting;
- }
- return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE;
+ return networkPrefSetting;
}
/**
if (usedNetworkType != networkType) {
Integer currentPid = new Integer(pid);
mNetRequestersPids[usedNetworkType].remove(currentPid);
- reassessPidDns(pid, true);
+
+ 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 +
netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
tracker.isTeardownRequested()) {
if (VDBG) {
- log("requestRouteToHostAddress on down network " +
- "(" + networkType + ") - dropped");
+ log("requestRouteToHostAddress on down network "
+ + "(" + networkType + ") - dropped"
+ + " tracker=" + tracker
+ + " netState=" + netState
+ + " isTeardownRequested="
+ + ((tracker != null) ? tracker.isTeardownRequested() : "tracker:null"));
}
return false;
}
try {
InetAddress addr = InetAddress.getByAddress(hostAddress);
LinkProperties lp = tracker.getLinkProperties();
- return addRouteToAddress(lp, addr);
+ boolean ok = addRouteToAddress(lp, addr);
+ 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) {
- return modifyRoute(p.getInterfaceName(), p, r, 0, ADD, toDefaultTable);
+ return modifyRoute(p, r, 0, ADD, toDefaultTable);
}
private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
- return modifyRoute(p.getInterfaceName(), p, r, 0, REMOVE, toDefaultTable);
+ return modifyRoute(p, r, 0, REMOVE, toDefaultTable);
}
private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) {
private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
boolean toDefaultTable) {
- RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr);
+ RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
if (bestRoute == null) {
- bestRoute = RouteInfo.makeHostRoute(addr);
+ 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);
+ 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());
+ bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
}
}
- return modifyRoute(lp.getInterfaceName(), lp, bestRoute, 0, doAdd, toDefaultTable);
+ return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable);
}
- private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount,
- boolean doAdd, boolean toDefaultTable) {
- if ((ifaceName == null) || (lp == null) || (r == null)) {
- if (DBG) log("modifyRoute got unexpected null: " + ifaceName + ", " + lp + ", " + r);
+ private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
+ boolean toDefaultTable) {
+ if ((lp == null) || (r == null)) {
+ if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
return false;
}
return false;
}
- if (r.isHostRoute() == false) {
- RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), r.getGateway());
+ 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());
+ 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());
+ bestRoute = RouteInfo.makeHostRoute(r.getGateway(),
+ bestRoute.getGateway(),
+ ifaceName);
}
- modifyRoute(ifaceName, lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
+ modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
}
}
if (doAdd) {
* in accordance with network preference policies.
*/
if (!mNetConfigs[prevNetType].isDefault()) {
- List pids = mNetRequestersPids[prevNetType];
- for (int i = 0; i<pids.size(); i++) {
- Integer pid = (Integer)pids.get(i);
+ 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.
}
public void sendConnectedBroadcast(NetworkInfo info) {
+ enforceConnectivityInternalPermission();
sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
/** @hide */
public void captivePortalCheckComplete(NetworkInfo info) {
+ enforceConnectivityInternalPermission();
mNetTrackers[info.getType()].captivePortalCheckComplete();
}
boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
if (resetMask != 0 || resetDns) {
- LinkProperties linkProperties = mNetTrackers[netType].getLinkProperties();
- if (linkProperties != null) {
- String iface = linkProperties.getInterfaceName();
- if (TextUtils.isEmpty(iface) == false) {
- if (resetMask != 0) {
- if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
- NetworkUtils.resetConnections(iface, resetMask);
-
- // Tell VPN the interface is down. It is a temporary
- // but effective fix to make VPN aware of the change.
- if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
- mVpn.interfaceStatusChanged(iface, false);
+ if (curLp != null) {
+ 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) {
+ mVpn.interfaceStatusChanged(iface, false);
+ }
}
- }
- if (resetDns) {
- 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);
+ 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. */
routeDiff = curLp.compareRoutes(newLp);
dnsDiff = curLp.compareDnses(newLp);
} else if (newLp != null) {
- routeDiff.added = newLp.getRoutes();
+ routeDiff.added = newLp.getAllRoutes();
dnsDiff.added = newLp.getDnses();
}
}
}
- for (RouteInfo r : routeDiff.added) {
- if (isLinkDefault || ! r.isDefaultRoute()) {
- addRoute(newLp, r, TO_DEFAULT_TABLE);
- } else {
- // add to a secondary route table
- addRoute(newLp, r, TO_SECONDARY_TABLE);
-
- // many radios add a default route even when we don't want one.
- // remove the default route unless somebody else has asked for it
- String ifaceName = newLp.getInterfaceName();
- if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
- if (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);
- }
- }
- }
- }
-
if (!isLinkDefault) {
// handle DNS routes
if (routesChanged) {
}
}
}
+
+ for (RouteInfo r : routeDiff.added) {
+ if (isLinkDefault || ! r.isDefaultRoute()) {
+ addRoute(newLp, r, TO_DEFAULT_TABLE);
+ } else {
+ // add to a secondary route table
+ addRoute(newLp, r, TO_SECONDARY_TABLE);
+
+ // many radios add a default route even when we don't want one.
+ // remove the default route unless somebody else has asked for it
+ String ifaceName = newLp.getInterfaceName();
+ if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
+ if (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;
}
* net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
* wide use
*/
- public void updateNetworkSettings(NetworkStateTracker nt) {
+ private void updateNetworkSettings(NetworkStateTracker nt) {
String key = nt.getTcpBufferSizesPropName();
String bufferSizes = key == null ? null : SystemProperties.get(key);
* on the highest priority active net which this process requested.
* If there aren't any, clear it out
*/
- private void reassessPidDns(int myPid, boolean doBump)
+ private void reassessPidDns(int pid, boolean doBump)
{
- if (VDBG) log("reassessPidDns for pid " + myPid);
+ if (VDBG) log("reassessPidDns for pid " + pid);
+ Integer myPid = new Integer(pid);
for(int i : mPriorityList) {
if (mNetConfigs[i].isDefault()) {
continue;
!nt.isTeardownRequested()) {
LinkProperties p = nt.getLinkProperties();
if (p == null) continue;
- List pids = mNetRequestersPids[i];
- for (int j=0; j<pids.size(); j++) {
- Integer pid = (Integer)pids.get(j);
- if (pid.intValue() == myPid) {
- Collection<InetAddress> dnses = p.getDnses();
- writePidDns(dnses, myPid);
- if (doBump) {
- bumpDns();
- }
- return;
+ 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
- for (int i = 1; ; i++) {
- String prop = "net.dns" + i + "." + myPid;
- if (SystemProperties.get(prop).length() == 0) {
- if (doBump) {
- bumpDns();
- }
- return;
- }
- SystemProperties.set(prop, "");
- }
- }
-
- // return true if results in a change
- private boolean writePidDns(Collection <InetAddress> dnses, int pid) {
- int j = 1;
- boolean changed = false;
- for (InetAddress dns : dnses) {
- String dnsString = dns.getHostAddress();
- if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) {
- changed = true;
- SystemProperties.set("net.dns" + j + "." + pid, dns.getHostAddress());
- }
- j++;
+ try {
+ mNetd.clearDnsInterfaceForPid(pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception clear interface from pid: " + e);
}
- return changed;
}
- private void bumpDns() {
- /*
- * Bump the property that tells the name resolver library to reread
- * the DNS server list from the properties.
- */
- String propVal = SystemProperties.get("net.dnschange");
- int n = 0;
- if (propVal.length() != 0) {
- try {
- n = Integer.parseInt(propVal);
- } catch (NumberFormatException e) {}
- }
- SystemProperties.set("net.dnschange", "" + (n+1));
+ private void flushVmDnsCache() {
/*
* Tell the VMs to toss their DNS caches
*/
}
// Caller must grab mDnsLock.
- private boolean updateDns(String network, String iface,
+ private void updateDnsLocked(String network, String iface,
Collection<InetAddress> dnses, String domains) {
- boolean changed = false;
int last = 0;
if (dnses.size() == 0 && mDefaultDns != null) {
- ++last;
- String value = mDefaultDns.getHostAddress();
- if (!value.equals(SystemProperties.get("net.dns1"))) {
- if (DBG) {
- loge("no dns provided for " + network + " - using " + value);
- }
- changed = true;
- SystemProperties.set("net.dns1", value);
+ dnses = new ArrayList();
+ dnses.add(mDefaultDns);
+ if (DBG) {
+ loge("no dns provided for " + network + " - using " + mDefaultDns.getHostAddress());
}
- } else {
+ }
+
+ try {
+ mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
+ mNetd.setDefaultInterfaceForDns(iface);
for (InetAddress dns : dnses) {
++last;
String key = "net.dns" + last;
String value = dns.getHostAddress();
- if (!changed && value.equals(SystemProperties.get(key))) {
- continue;
- }
- if (VDBG) {
- log("adding dns " + value + " for " + network);
- }
- changed = true;
SystemProperties.set(key, value);
}
- }
- for (int i = last + 1; i <= mNumDnsEntries; ++i) {
- String key = "net.dns" + i;
- if (VDBG) log("erasing " + key);
- changed = true;
- SystemProperties.set(key, "");
- }
- mNumDnsEntries = last;
-
- if (changed) {
- try {
- mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses));
- mNetd.setDefaultInterfaceForDns(iface);
- } catch (Exception e) {
- if (DBG) loge("exception setting default dns interface: " + e);
+ 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);
}
- if (!domains.equals(SystemProperties.get("net.dns.search"))) {
- SystemProperties.set("net.dns.search", domains);
- changed = true;
- }
- return changed;
}
private void handleDnsConfigurationChange(int netType) {
LinkProperties p = nt.getLinkProperties();
if (p == null) return;
Collection<InetAddress> dnses = p.getDnses();
- boolean changed = false;
if (mNetConfigs[netType].isDefault()) {
String network = nt.getNetworkInfo().getTypeName();
synchronized (mDnsLock) {
if (!mDnsOverridden) {
- changed = updateDns(network, p.getInterfaceName(), dnses, "");
+ updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains());
}
}
} else {
try {
mNetd.setDnsServersForInterface(p.getInterfaceName(),
- NetworkUtils.makeStrings(dnses));
+ 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 pids = mNetRequestersPids[netType];
- for (int y=0; y< pids.size(); y++) {
- Integer pid = (Integer)pids.get(y);
- changed = writePidDns(dnses, pid.intValue());
+ 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);
+ }
}
}
- if (changed) bumpDns();
+ flushVmDnsCache();
}
}
pw.increaseIndent();
for (int net : mPriorityList) {
String pidString = net + ": ";
- for (Object pid : mNetRequestersPids[net]) {
+ for (Integer pid : mNetRequestersPids[net]) {
pidString = pidString + pid.toString() + ", ";
}
pw.println(pidString);
state + "/" + info.getDetailedState());
}
+ // After booting we'll check once for mobile provisioning
+ // if we've provisioned by and connected.
+ if (!mFirstProvisioningCheckStarted
+ && (0 != Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0))
+ && (state == NetworkInfo.State.CONNECTED)) {
+ log("check provisioning after booting");
+ mFirstProvisioningCheckStarted = true;
+ checkMobileProvisioning(true, CheckMp.MAX_TIMEOUT_MS, null);
+ }
+
EventLogTags.writeConnectivityStateChanged(
info.getType(), info.getSubtype(), info.getDetailedState().ordinal());
}
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);
+ }
+ }
}
}
}
}
public int setUsbTethering(boolean enable) {
- enforceTetherAccessPermission();
+ enforceTetherChangePermission();
if (isTetheringSupported()) {
return mTethering.setUsbTethering(enable);
} else {
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 && mTetheringConfigValid;
+ 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.
}
public ProxyProperties getProxy() {
- synchronized (mDefaultProxyLock) {
- return mDefaultProxyDisabled ? null : mDefaultProxy;
+ // 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) {
+ if (mGlobalProxy != null) return mGlobalProxy;
+ return (mDefaultProxyDisabled ? null : mDefaultProxy);
}
}
public void setGlobalProxy(ProxyProperties proxyProperties) {
- enforceChangePermission();
- synchronized (mGlobalProxyLock) {
+ enforceConnectivityInternalPermission();
+ synchronized (mProxyLock) {
if (proxyProperties == mGlobalProxy) return;
if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
mGlobalProxy = null;
}
ContentResolver res = mContext.getContentResolver();
- 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);
+ 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);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
if (mGlobalProxy == null) {
proxyProperties = mDefaultProxy;
}
- //sendProxyBroadcast(proxyProperties);
+ sendProxyBroadcast(proxyProperties);
}
private void loadGlobalProxy() {
Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
if (!TextUtils.isEmpty(host)) {
ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList);
- synchronized (mGlobalProxyLock) {
+ synchronized (mProxyLock) {
mGlobalProxy = proxyProperties;
}
}
}
public ProxyProperties getGlobalProxy() {
- synchronized (mGlobalProxyLock) {
+ // 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;
}
}
if (proxy != null && TextUtils.isEmpty(proxy.getHost())) {
proxy = null;
}
- synchronized (mDefaultProxyLock) {
+ synchronized (mProxyLock) {
if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
- if (mDefaultProxy == proxy) return;
+ if (mDefaultProxy == proxy) return; // catches repeated nulls
mDefaultProxy = proxy;
+ if (mGlobalProxy != null) return;
if (!mDefaultProxyDisabled) {
sendProxyBroadcast(proxy);
}
throwIfLockdownEnabled();
try {
int type = mActiveDefaultNetwork;
- if (ConnectivityManager.isNetworkTypeValid(type)) {
+ if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName());
return true;
}
String domains = buffer.toString().trim();
// Apply DNS changes.
- boolean changed = false;
synchronized (mDnsLock) {
- changed = updateDns("VPN", "VPN", addresses, domains);
+ updateDnsLocked("VPN", "VPN", addresses, domains);
mDnsOverridden = true;
}
- if (changed) {
- bumpDns();
- }
- // Temporarily disable the default proxy.
- synchronized (mDefaultProxyLock) {
+ // Temporarily disable the default proxy (not global).
+ synchronized (mProxyLock) {
mDefaultProxyDisabled = true;
- if (mDefaultProxy != null) {
+ if (mGlobalProxy == null && mDefaultProxy != null) {
sendProxyBroadcast(null);
}
}
mHandler.sendEmptyMessage(EVENT_RESTORE_DNS);
}
}
- synchronized (mDefaultProxyLock) {
+ synchronized (mProxyLock) {
mDefaultProxyDisabled = false;
- if (mDefaultProxy != null) {
+ if (mGlobalProxy == null && mDefaultProxy != null) {
sendProxyBroadcast(mDefaultProxy);
}
}
@Override
public boolean updateLockdownVpn() {
- enforceSystemUid();
+ 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();
try {
if (tracker != null) {
mNetd.setFirewallEnabled(true);
+ mNetd.setFirewallInterfaceRule("lo", true);
mLockdownTracker = tracker;
mLockdownTracker.init();
} else {
}
}
- private static void enforceSystemUid() {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID) {
- throw new SecurityException("Only available to AID_SYSTEM");
+ 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));
+ }
+
+ @Override
+ public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs,
+ final ResultReceiver resultReceiver) {
+ log("checkMobileProvisioning: E sendNotification=" + sendNotification
+ + " suggestedTimeOutMs=" + suggestedTimeOutMs
+ + " resultReceiver=" + resultReceiver);
+ enforceChangePermission();
+
+ mFirstProvisioningCheckStarted = true;
+
+ int 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)) {
+ log("checkMobileProvisioning: X no mobile network");
+ if (resultReceiver != null) {
+ resultReceiver.send(ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION, null);
+ }
+ return timeOutMs;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ CheckMp checkMp = new CheckMp(mContext, this);
+ CheckMp.CallBack cb = new CheckMp.CallBack() {
+ @Override
+ void onComplete(Integer result) {
+ log("CheckMp.onComplete: result=" + result);
+ if (resultReceiver != null) {
+ log("CheckMp.onComplete: send result");
+ resultReceiver.send(result, null);
+ }
+ NetworkInfo ni =
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
+ switch(result) {
+ case ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE:
+ case ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION: {
+ log("CheckMp.onComplete: ignore, connected or no connection");
+ break;
+ }
+ case ConnectivityManager.CMP_RESULT_CODE_REDIRECTED: {
+ log("CheckMp.onComplete: warm sim");
+ String url = getMobileProvisioningUrl();
+ if (TextUtils.isEmpty(url)) {
+ url = getMobileRedirectedProvisioningUrl();
+ }
+ if (TextUtils.isEmpty(url) == false) {
+ log("CheckMp.onComplete: warm sim (redirected), url=" + url);
+ setNotificationVisible(true, ni, url);
+ } else {
+ log("CheckMp.onComplete: warm sim (redirected), no url");
+ }
+ break;
+ }
+ case ConnectivityManager.CMP_RESULT_CODE_NO_DNS:
+ case ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION: {
+ String url = getMobileProvisioningUrl();
+ if (TextUtils.isEmpty(url) == false) {
+ log("CheckMp.onComplete: warm sim (no dns/tcp), url=" + url);
+ setNotificationVisible(true, ni, url);
+ } else {
+ log("CheckMp.onComplete: warm sim (no dns/tcp), no url");
+ }
+ break;
+ }
+ default: {
+ loge("CheckMp.onComplete: ignore unexpected result=" + result);
+ break;
+ }
+ }
+ }
+ };
+ CheckMp.Params params =
+ new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
+ log("checkMobileProvisioning: params=" + params);
+ setNotificationVisible(false, null, null);
+ checkMp.execute(params);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ 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 = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+ Uri orgUri = Uri.parse(params.mUrl);
+ Random rand = new Random();
+ mParams = params;
+
+ try {
+ if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+ log("isMobileOk: not mobile capable");
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+ return result;
+ }
+
+ // Enable fail fast as we'll do retries here and use a
+ // hipri connection so the default connection stays active.
+ log("isMobileOk: start hipri url=" + params.mUrl);
+ mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
+ mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_HIPRI, new Binder());
+
+ // Continue trying to connect until time has run out
+ long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs;
+ 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) {
+ log("isMobileOk: not connected ni=" +
+ mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+ sleep(1);
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+ 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) {
+ log("isMobileOk: UnknownHostException");
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_DNS;
+ 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 = hasIPv4Address(lp);
+ boolean linkHasIpv6 = hasIPv6Address(lp);
+ 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();
+ if (responseCode == 204) {
+ result = ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE;
+ } else {
+ result = ConnectivityManager.CMP_RESULT_CODE_REDIRECTED;
+ }
+ log("isMobileOk: connected responseCode=" + responseCode);
+ urlConn.disconnect();
+ urlConn = null;
+ return result;
+ } catch (Exception e) {
+ log("isMobileOk: HttpURLConnection Exception e=" + e);
+ if (urlConn != null) {
+ urlConn.disconnect();
+ urlConn = null;
+ }
+ }
+ }
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION;
+ log("isMobileOk: loops|timed out");
+ 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);
+ 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();
+ }
+ }
+
+ public boolean hasIPv4Address(LinkProperties lp) {
+ return lp.hasIPv4Address();
+ }
+
+ // Not implemented in LinkProperties, do it here.
+ public boolean hasIPv6Address(LinkProperties lp) {
+ for (LinkAddress address : lp.getLinkAddresses()) {
+ if (address.getAddress() instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void log(String s) {
+ Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
+ }
+ }
+
+ private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+
+ private void setNotificationVisible(boolean visible, NetworkInfo networkInfo, String url) {
+ log("setNotificationVisible: E visible=" + visible + " ni=" + networkInfo + " url=" + url);
+
+ Resources r = Resources.getSystem();
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (visible) {
+ CharSequence title;
+ CharSequence details;
+ int icon;
+ switch (networkInfo.getType()) {
+ case ConnectivityManager.TYPE_WIFI:
+ log("setNotificationVisible: TYPE_WIFI");
+ title = r.getString(R.string.wifi_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ networkInfo.getExtraInfo());
+ icon = R.drawable.stat_notify_wifi_in_range;
+ break;
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ log("setNotificationVisible: 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;
+ break;
+ default:
+ log("setNotificationVisible: other type=" + networkInfo.getType());
+ title = r.getString(R.string.network_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ networkInfo.getExtraInfo());
+ icon = R.drawable.stat_notify_rssi_in_range;
+ break;
+ }
+
+ Notification notification = new Notification();
+ notification.when = 0;
+ notification.icon = icon;
+ notification.flags = Notification.FLAG_AUTO_CANCEL;
+ Intent 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);
+ notification.tickerText = title;
+ notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+
+ log("setNotificaitionVisible: notify notificaiton=" + notification);
+ notificationManager.notify(NOTIFICATION_ID, 1, notification);
+ } else {
+ log("setNotificaitionVisible: cancel");
+ notificationManager.cancel(NOTIFICATION_ID, 1);
+ }
+ log("setNotificationVisible: X visible=" + visible + " ni=" + networkInfo + " url=" + url);
+ }
+
+ /** 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&iccid=%1$s&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;
+ }
+
+ private String getMobileRedirectedProvisioningUrl() {
+ String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
+ if (TextUtils.isEmpty(url)) {
+ url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
+ }
+ return url;
+ }
+
+ public String getMobileProvisioningUrl() {
+ enforceConnectivityInternalPermission();
+ String url = getProvisioningUrlBaseFromFile(PROVISIONING);
+ if (TextUtils.isEmpty(url)) {
+ url = mContext.getResources().getString(R.string.mobile_provisioning_url);
+ log("getProvisioningUrl: mobile_provisioining_url from resource =" + url);
+ } else {
+ log("getProvisioningUrl: 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;
+ }
}