Merge commit '3463b3ff' into fix-merge-conflict

* commit '3463b3ff':
  Add checkMobileProvisioning to ConnectivityService.

Conflicts:
	services/java/com/android/server/ConnectivityService.java

Change-Id: Ieceb9c8f4aa4fa99258e56a720b4c74512369458
This commit is contained in:
Wink Saville
2013-07-01 16:12:31 -07:00
3 changed files with 575 additions and 3 deletions

View File

@@ -31,6 +31,9 @@ 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.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -52,11 +55,13 @@ import android.net.INetworkPolicyManager;
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;
@@ -66,6 +71,7 @@ import android.net.ProxyProperties;
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;
@@ -79,6 +85,7 @@ 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;
@@ -86,13 +93,16 @@ 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.SparseIntArray;
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;
@@ -112,9 +122,11 @@ import java.io.FileDescriptor;
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;
@@ -122,6 +134,8 @@ import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
@@ -142,6 +156,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
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;
@@ -293,6 +313,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
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. */
@@ -348,6 +373,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
List mProtectedNetworks;
private DataConnectionStats mDataConnectionStats;
private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
TelephonyManager mTelephonyManager;
public ConnectivityService(Context context, INetworkManagementService netd,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
@@ -397,6 +425,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
mKeyStore = KeyStore.getInstance();
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
try {
mPolicyManager.registerListener(mPolicyListener);
@@ -1414,8 +1443,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
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;
}
@@ -1423,12 +1456,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
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;
}
@@ -2830,6 +2866,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
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);
}
}
}
}
}
@@ -3484,4 +3533,460 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
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();
int timeOutMs = suggestedTimeOutMs;
if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) {
timeOutMs = CheckMp.MAX_TIMEOUT_MS;
}
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 = getProvisioningUrl();
if (TextUtils.isEmpty(url)) {
url = mContext.getResources()
.getString(R.string.mobile_redirected_provisioning_url);
}
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 = getProvisioningUrl();
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);
}
private String getProvisioningUrl() {
String url = mContext.getResources().getString(R.string.mobile_provisioning_url);
log("getProvisioningUrl: resource url=" + url);
// populate the iccid and imei in the provisioning url.
if (!TextUtils.isEmpty(url)) {
url = String.format(url,
mTelephonyManager.getSimSerialNumber() /* ICCID */,
mTelephonyManager.getDeviceId() /* IMEI */,
mTelephonyManager.getLine1Number() /* Phone numer */);
}
log("getProvisioningUrl: url=" + url);
return url;
}
}