• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.os.RemoteException;
24 import android.os.Handler;
25 import android.os.ServiceManager;
26 import android.os.SystemProperties;
27 import com.android.internal.telephony.ITelephony;
28 import com.android.internal.telephony.Phone;
29 import com.android.internal.telephony.TelephonyIntents;
30 import android.net.NetworkInfo.DetailedState;
31 import android.telephony.TelephonyManager;
32 import android.util.Log;
33 import android.text.TextUtils;
34 
35 /**
36  * Track the state of mobile data connectivity. This is done by
37  * receiving broadcast intents from the Phone process whenever
38  * the state of data connectivity changes.
39  *
40  * {@hide}
41  */
42 public class MobileDataStateTracker extends NetworkStateTracker {
43 
44     private static final String TAG = "MobileDataStateTracker";
45     private static final boolean DBG = true;
46 
47     private Phone.DataState mMobileDataState;
48     private ITelephony mPhoneService;
49 
50     private String mApnType;
51     private String mApnName;
52     private boolean mEnabled;
53     private BroadcastReceiver mStateReceiver;
54 
55     /**
56      * Create a new MobileDataStateTracker
57      * @param context the application context of the caller
58      * @param target a message handler for getting callbacks about state changes
59      * @param netType the ConnectivityManager network type
60      * @param apnType the Phone apnType
61      * @param tag the name of this network
62      */
MobileDataStateTracker(Context context, Handler target, int netType, String apnType, String tag)63     public MobileDataStateTracker(Context context, Handler target,
64             int netType, String apnType, String tag) {
65         super(context, target, netType,
66                 TelephonyManager.getDefault().getNetworkType(), tag,
67                 TelephonyManager.getDefault().getNetworkTypeName());
68         mApnType = apnType;
69         mPhoneService = null;
70         if(netType == ConnectivityManager.TYPE_MOBILE) {
71             mEnabled = true;
72         } else {
73             mEnabled = false;
74         }
75 
76         mDnsPropNames = new String[] {
77                 "net.rmnet0.dns1",
78                 "net.rmnet0.dns2",
79                 "net.eth0.dns1",
80                 "net.eth0.dns2",
81                 "net.eth0.dns3",
82                 "net.eth0.dns4",
83                 "net.gprs.dns1",
84                 "net.gprs.dns2",
85                 "net.ppp0.dns1",
86                 "net.ppp0.dns2"};
87 
88     }
89 
90     /**
91      * Begin monitoring mobile data connectivity.
92      */
startMonitoring()93     public void startMonitoring() {
94         IntentFilter filter =
95                 new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
96         filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
97         filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
98 
99         mStateReceiver = new MobileDataStateReceiver();
100         Intent intent = mContext.registerReceiver(mStateReceiver, filter);
101         if (intent != null)
102             mMobileDataState = getMobileDataState(intent);
103         else
104             mMobileDataState = Phone.DataState.DISCONNECTED;
105     }
106 
getMobileDataState(Intent intent)107     private Phone.DataState getMobileDataState(Intent intent) {
108         String str = intent.getStringExtra(Phone.STATE_KEY);
109         if (str != null) {
110             String apnTypeList =
111                     intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
112             if (isApnTypeIncluded(apnTypeList)) {
113                 return Enum.valueOf(Phone.DataState.class, str);
114             }
115         }
116         return Phone.DataState.DISCONNECTED;
117     }
118 
isApnTypeIncluded(String typeList)119     private boolean isApnTypeIncluded(String typeList) {
120         /* comma seperated list - split and check */
121         if (typeList == null)
122             return false;
123 
124         String[] list = typeList.split(",");
125         for(int i=0; i< list.length; i++) {
126             if (TextUtils.equals(list[i], mApnType) ||
127                 TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) {
128                 return true;
129             }
130         }
131         return false;
132     }
133 
134     private class MobileDataStateReceiver extends BroadcastReceiver {
onReceive(Context context, Intent intent)135         public void onReceive(Context context, Intent intent) {
136             synchronized(this) {
137                 if (intent.getAction().equals(TelephonyIntents.
138                         ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
139                     Phone.DataState state = getMobileDataState(intent);
140                     String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
141                     String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
142                     String apnTypeList = intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
143                     mApnName = apnName;
144 
145                     boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
146                             false);
147 
148                     // set this regardless of the apnTypeList.  It's all the same radio/network
149                     // underneath
150                     mNetworkInfo.setIsAvailable(!unavailable);
151 
152                     if (isApnTypeIncluded(apnTypeList)) {
153                         if (mEnabled == false) {
154                             // if we're not enabled but the APN Type is supported by this connection
155                             // we should record the interface name if one's provided.  If the user
156                             // turns on this network we will need the interfacename but won't get
157                             // a fresh connected message - TODO fix this when we get per-APN
158                             // notifications
159                             if (state == Phone.DataState.CONNECTED) {
160                                 if (DBG) Log.d(TAG, "replacing old mInterfaceName (" +
161                                         mInterfaceName + ") with " +
162                                         intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY) +
163                                         " for " + mApnType);
164                                 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
165                             }
166                             return;
167                         }
168                     } else {
169                         return;
170                     }
171 
172                     if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
173                             mMobileDataState + ", reason= " +
174                             (reason == null ? "(unspecified)" : reason) +
175                             ", apnTypeList= " + apnTypeList);
176 
177                     if (mMobileDataState != state) {
178                         mMobileDataState = state;
179                         switch (state) {
180                             case DISCONNECTED:
181                                 if(isTeardownRequested()) {
182                                     mEnabled = false;
183                                     setTeardownRequested(false);
184                                 }
185 
186                                 setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
187                                 if (mInterfaceName != null) {
188                                     NetworkUtils.resetConnections(mInterfaceName);
189                                 }
190                                 // can't do this here - ConnectivityService needs it to clear stuff
191                                 // it's ok though - just leave it to be refreshed next time
192                                 // we connect.
193                                 //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType +
194                                 //        " as it DISCONNECTED");
195                                 //mInterfaceName = null;
196                                 //mDefaultGatewayAddr = 0;
197                                 break;
198                             case CONNECTING:
199                                 setDetailedState(DetailedState.CONNECTING, reason, apnName);
200                                 break;
201                             case SUSPENDED:
202                                 setDetailedState(DetailedState.SUSPENDED, reason, apnName);
203                                 break;
204                             case CONNECTED:
205                                 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
206                                 if (mInterfaceName == null) {
207                                     Log.d(TAG, "CONNECTED event did not supply interface name.");
208                                 }
209                                 setDetailedState(DetailedState.CONNECTED, reason, apnName);
210                                 break;
211                         }
212                     }
213                 } else if (intent.getAction().
214                         equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
215                     mEnabled = false;
216                     String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
217                     String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
218                     if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" +
219                             reason == null ? "" : "(" + reason + ")");
220                     setDetailedState(DetailedState.FAILED, reason, apnName);
221                 }
222                 TelephonyManager tm = TelephonyManager.getDefault();
223                 setRoamingStatus(tm.isNetworkRoaming());
224                 setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
225             }
226         }
227     }
228 
getPhoneService(boolean forceRefresh)229     private void getPhoneService(boolean forceRefresh) {
230         if ((mPhoneService == null) || forceRefresh) {
231             mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
232         }
233     }
234 
235     /**
236      * Report whether data connectivity is possible.
237      */
isAvailable()238     public boolean isAvailable() {
239         getPhoneService(false);
240 
241         /*
242          * If the phone process has crashed in the past, we'll get a
243          * RemoteException and need to re-reference the service.
244          */
245         for (int retry = 0; retry < 2; retry++) {
246             if (mPhoneService == null) break;
247 
248             try {
249                 return mPhoneService.isDataConnectivityPossible();
250             } catch (RemoteException e) {
251                 // First-time failed, get the phone service again
252                 if (retry == 0) getPhoneService(true);
253             }
254         }
255 
256         return false;
257     }
258 
259     /**
260      * {@inheritDoc}
261      * The mobile data network subtype indicates what generation network technology is in effect,
262      * e.g., GPRS, EDGE, UMTS, etc.
263      */
getNetworkSubtype()264     public int getNetworkSubtype() {
265         return TelephonyManager.getDefault().getNetworkType();
266     }
267 
268     /**
269      * Return the system properties name associated with the tcp buffer sizes
270      * for this network.
271      */
getTcpBufferSizesPropName()272     public String getTcpBufferSizesPropName() {
273         String networkTypeStr = "unknown";
274         TelephonyManager tm = new TelephonyManager(mContext);
275         //TODO We have to edit the parameter for getNetworkType regarding CDMA
276         switch(tm.getNetworkType()) {
277         case TelephonyManager.NETWORK_TYPE_GPRS:
278             networkTypeStr = "gprs";
279             break;
280         case TelephonyManager.NETWORK_TYPE_EDGE:
281             networkTypeStr = "edge";
282             break;
283         case TelephonyManager.NETWORK_TYPE_UMTS:
284             networkTypeStr = "umts";
285             break;
286         case TelephonyManager.NETWORK_TYPE_HSDPA:
287             networkTypeStr = "hsdpa";
288             break;
289         case TelephonyManager.NETWORK_TYPE_HSUPA:
290             networkTypeStr = "hsupa";
291             break;
292         case TelephonyManager.NETWORK_TYPE_HSPA:
293             networkTypeStr = "hspa";
294             break;
295         case TelephonyManager.NETWORK_TYPE_CDMA:
296             networkTypeStr = "cdma";
297             break;
298         case TelephonyManager.NETWORK_TYPE_1xRTT:
299             networkTypeStr = "1xrtt";
300             break;
301         case TelephonyManager.NETWORK_TYPE_EVDO_0:
302             networkTypeStr = "evdo";
303             break;
304         case TelephonyManager.NETWORK_TYPE_EVDO_A:
305             networkTypeStr = "evdo";
306             break;
307         }
308         return "net.tcp.buffersize." + networkTypeStr;
309     }
310 
311     /**
312      * Tear down mobile data connectivity, i.e., disable the ability to create
313      * mobile data connections.
314      */
315     @Override
teardown()316     public boolean teardown() {
317         // since we won't get a notification currently (TODO - per APN notifications)
318         // we won't get a disconnect message until all APN's on the current connection's
319         // APN list are disabled.  That means privateRoutes for DNS and such will remain on -
320         // not a problem since that's all shared with whatever other APN is still on, but
321         // ugly.
322         setTeardownRequested(true);
323         return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
324     }
325 
326     /**
327      * Re-enable mobile data connectivity after a {@link #teardown()}.
328      */
reconnect()329     public boolean reconnect() {
330         setTeardownRequested(false);
331         switch (setEnableApn(mApnType, true)) {
332             case Phone.APN_ALREADY_ACTIVE:
333                 // TODO - remove this when we get per-apn notifications
334                 mEnabled = true;
335                 // need to set self to CONNECTING so the below message is handled.
336                 mMobileDataState = Phone.DataState.CONNECTING;
337                 setDetailedState(DetailedState.CONNECTING, Phone.REASON_APN_CHANGED, null);
338                 //send out a connected message
339                 Intent intent = new Intent(TelephonyIntents.
340                         ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
341                 intent.putExtra(Phone.STATE_KEY, Phone.DataState.CONNECTED.toString());
342                 intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, Phone.REASON_APN_CHANGED);
343                 intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnType);
344                 intent.putExtra(Phone.DATA_APN_KEY, mApnName);
345                 intent.putExtra(Phone.DATA_IFACE_NAME_KEY, mInterfaceName);
346                 intent.putExtra(Phone.NETWORK_UNAVAILABLE_KEY, false);
347                 if (mStateReceiver != null) mStateReceiver.onReceive(mContext, intent);
348                 break;
349             case Phone.APN_REQUEST_STARTED:
350                 mEnabled = true;
351                 // no need to do anything - we're already due some status update intents
352                 break;
353             case Phone.APN_REQUEST_FAILED:
354                 if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) {
355                     // on startup we may try to talk to the phone before it's ready
356                     // just leave mEnabled as it is for the default apn.
357                     return false;
358                 }
359                 // else fall through
360             case Phone.APN_TYPE_NOT_AVAILABLE:
361                 mEnabled = false;
362                 break;
363             default:
364                 Log.e(TAG, "Error in reconnect - unexpected response.");
365                 mEnabled = false;
366                 break;
367         }
368         return mEnabled;
369     }
370 
371     /**
372      * Turn on or off the mobile radio. No connectivity will be possible while the
373      * radio is off. The operation is a no-op if the radio is already in the desired state.
374      * @param turnOn {@code true} if the radio should be turned on, {@code false} if
375      */
setRadio(boolean turnOn)376     public boolean setRadio(boolean turnOn) {
377         getPhoneService(false);
378         /*
379          * If the phone process has crashed in the past, we'll get a
380          * RemoteException and need to re-reference the service.
381          */
382         for (int retry = 0; retry < 2; retry++) {
383             if (mPhoneService == null) {
384                 Log.w(TAG,
385                     "Ignoring mobile radio request because could not acquire PhoneService");
386                 break;
387             }
388 
389             try {
390                 return mPhoneService.setRadio(turnOn);
391             } catch (RemoteException e) {
392                 if (retry == 0) getPhoneService(true);
393             }
394         }
395 
396         Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
397         return false;
398     }
399 
400     /**
401      * Tells the phone sub-system that the caller wants to
402      * begin using the named feature. The only supported features at
403      * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
404      * to specify that it wants to send and/or receive MMS data, and
405      * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS.
406      * @param feature the name of the feature to be used
407      * @param callingPid the process ID of the process that is issuing this request
408      * @param callingUid the user ID of the process that is issuing this request
409      * @return an integer value representing the outcome of the request.
410      * The interpretation of this value is feature-specific.
411      * specific, except that the value {@code -1}
412      * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
413      * the other possible return values are
414      * <ul>
415      * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
416      * <li>{@code Phone.APN_REQUEST_STARTED}</li>
417      * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
418      * <li>{@code Phone.APN_REQUEST_FAILED}</li>
419      * </ul>
420      */
startUsingNetworkFeature(String feature, int callingPid, int callingUid)421     public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
422         return -1;
423     }
424 
425     /**
426      * Tells the phone sub-system that the caller is finished
427      * using the named feature. The only supported feature at
428      * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
429      * to specify that it wants to send and/or receive MMS data.
430      * @param feature the name of the feature that is no longer needed
431      * @param callingPid the process ID of the process that is issuing this request
432      * @param callingUid the user ID of the process that is issuing this request
433      * @return an integer value representing the outcome of the request.
434      * The interpretation of this value is feature-specific, except that
435      * the value {@code -1} always indicates failure.
436      */
stopUsingNetworkFeature(String feature, int callingPid, int callingUid)437     public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
438         return -1;
439     }
440 
441     /**
442      * Ensure that a network route exists to deliver traffic to the specified
443      * host via the mobile data network.
444      * @param hostAddress the IP address of the host to which the route is desired,
445      * in network byte order.
446      * @return {@code true} on success, {@code false} on failure
447      */
448     @Override
requestRouteToHost(int hostAddress)449     public boolean requestRouteToHost(int hostAddress) {
450         if (DBG) {
451             Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress) +
452                     " for " + mApnType + "(" + mInterfaceName + ")");
453         }
454         if (mInterfaceName != null && hostAddress != -1) {
455             return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0;
456         } else {
457             return false;
458         }
459     }
460 
461     @Override
toString()462     public String toString() {
463         StringBuffer sb = new StringBuffer("Mobile data state: ");
464 
465         sb.append(mMobileDataState);
466         return sb.toString();
467     }
468 
469    /**
470      * Internal method supporting the ENABLE_MMS feature.
471      * @param apnType the type of APN to be enabled or disabled (e.g., mms)
472      * @param enable {@code true} to enable the specified APN type,
473      * {@code false} to disable it.
474      * @return an integer value representing the outcome of the request.
475      */
setEnableApn(String apnType, boolean enable)476     private int setEnableApn(String apnType, boolean enable) {
477         getPhoneService(false);
478         /*
479          * If the phone process has crashed in the past, we'll get a
480          * RemoteException and need to re-reference the service.
481          */
482         for (int retry = 0; retry < 2; retry++) {
483             if (mPhoneService == null) {
484                 Log.w(TAG,
485                     "Ignoring feature request because could not acquire PhoneService");
486                 break;
487             }
488 
489             try {
490                 if (enable) {
491                     return mPhoneService.enableApnType(apnType);
492                 } else {
493                     return mPhoneService.disableApnType(apnType);
494                 }
495             } catch (RemoteException e) {
496                 if (retry == 0) getPhoneService(true);
497             }
498         }
499 
500         Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
501                 + " APN type \"" + apnType + "\"");
502         return Phone.APN_REQUEST_FAILED;
503     }
504 }
505