127beffb05
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
4805 lines
193 KiB
Java
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&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;
|
|
}
|
|
|
|
@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);
|
|
}
|
|
}
|