OSDN Git Service

770f152f1b7680b137cd6509612dfe195bcc1575
[android-x86/frameworks-base.git] / core / java / android / net / MobileDataStateTracker.java
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.Bundle;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Messenger;
27 import android.os.RemoteException;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Message;
31 import android.os.ServiceManager;
32
33 import com.android.internal.telephony.DataConnectionTracker;
34 import com.android.internal.telephony.ITelephony;
35 import com.android.internal.telephony.Phone;
36 import com.android.internal.telephony.TelephonyIntents;
37 import com.android.internal.util.AsyncChannel;
38
39 import android.net.NetworkInfo.DetailedState;
40 import android.net.NetworkInfo;
41 import android.net.LinkProperties;
42 import android.telephony.TelephonyManager;
43 import android.util.Slog;
44 import android.text.TextUtils;
45
46 /**
47  * Track the state of mobile data connectivity. This is done by
48  * receiving broadcast intents from the Phone process whenever
49  * the state of data connectivity changes.
50  *
51  * {@hide}
52  */
53 public class MobileDataStateTracker implements NetworkStateTracker {
54
55     private static final String TAG = "MobileDataStateTracker";
56     private static final boolean DBG = true;
57     private static final boolean VDBG = false;
58
59     private Phone.DataState mMobileDataState;
60     private ITelephony mPhoneService;
61
62     private String mApnType;
63     private NetworkInfo mNetworkInfo;
64     private boolean mTeardownRequested = false;
65     private Handler mTarget;
66     private Context mContext;
67     private LinkProperties mLinkProperties;
68     private LinkCapabilities mLinkCapabilities;
69     private boolean mPrivateDnsRouteSet = false;
70     private boolean mDefaultRouteSet = false;
71
72     // DEFAULT and HIPRI are the same connection.  If we're one of these we need to check if
73     // the other is also disconnected before we reset sockets
74     private boolean mIsDefaultOrHipri = false;
75
76     private Handler mHandler;
77     private AsyncChannel mDataConnectionTrackerAc;
78     private Messenger mMessenger;
79
80     /**
81      * Create a new MobileDataStateTracker
82      * @param netType the ConnectivityManager network type
83      * @param tag the name of this network
84      */
85     public MobileDataStateTracker(int netType, String tag) {
86         mNetworkInfo = new NetworkInfo(netType,
87                 TelephonyManager.getDefault().getNetworkType(), tag,
88                 TelephonyManager.getDefault().getNetworkTypeName());
89         mApnType = networkTypeToApnType(netType);
90         if (netType == ConnectivityManager.TYPE_MOBILE ||
91                 netType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
92             mIsDefaultOrHipri = true;
93         }
94
95         mPhoneService = null;
96     }
97
98     /**
99      * Begin monitoring data connectivity.
100      *
101      * @param context is the current Android context
102      * @param target is the Hander to which to return the events.
103      */
104     public void startMonitoring(Context context, Handler target) {
105         mTarget = target;
106         mContext = context;
107
108         HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
109         handlerThread.start();
110         mHandler = new MdstHandler(handlerThread.getLooper(), this);
111
112         IntentFilter filter = new IntentFilter();
113         filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
114         filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
115         filter.addAction(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
116
117         mContext.registerReceiver(new MobileDataStateReceiver(), filter);
118         mMobileDataState = Phone.DataState.DISCONNECTED;
119     }
120
121     static class MdstHandler extends Handler {
122         private MobileDataStateTracker mMdst;
123
124         MdstHandler(Looper looper, MobileDataStateTracker mdst) {
125             super(looper);
126             mMdst = mdst;
127         }
128
129         @Override
130         public void handleMessage(Message msg) {
131             switch (msg.what) {
132                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
133                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
134                         if (DBG) {
135                             mMdst.log("MdstHandler connected");
136                         }
137                         mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
138                     } else {
139                         if (DBG) {
140                             mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
141                         }
142                     }
143                     break;
144                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
145                     mMdst.log("Disconnected from DataStateTracker");
146                     mMdst.mDataConnectionTrackerAc = null;
147                     break;
148                 default: {
149                     mMdst.log("Ignorning unknown message=" + msg);
150                     break;
151                 }
152             }
153         }
154     }
155
156     public boolean isPrivateDnsRouteSet() {
157         return mPrivateDnsRouteSet;
158     }
159
160     public void privateDnsRouteSet(boolean enabled) {
161         mPrivateDnsRouteSet = enabled;
162     }
163
164     public NetworkInfo getNetworkInfo() {
165         return mNetworkInfo;
166     }
167
168     public boolean isDefaultRouteSet() {
169         return mDefaultRouteSet;
170     }
171
172     public void defaultRouteSet(boolean enabled) {
173         mDefaultRouteSet = enabled;
174     }
175
176     /**
177      * This is not implemented.
178      */
179     public void releaseWakeLock() {
180     }
181
182     private class MobileDataStateReceiver extends BroadcastReceiver {
183         IConnectivityManager mConnectivityManager;
184
185         @Override
186         public void onReceive(Context context, Intent intent) {
187             if (intent.getAction().equals(TelephonyIntents.
188                     ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
189                 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
190                 if (VDBG) {
191                     log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
192                         + "mApnType=%s %s received apnType=%s", mApnType,
193                         TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
194                 }
195                 if (!TextUtils.equals(apnType, mApnType)) {
196                     return;
197                 }
198                 mNetworkInfo.setSubtype(TelephonyManager.getDefault().getNetworkType(),
199                         TelephonyManager.getDefault().getNetworkTypeName());
200                 Phone.DataState state = Enum.valueOf(Phone.DataState.class,
201                         intent.getStringExtra(Phone.STATE_KEY));
202                 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
203                 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
204
205                 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
206                         false));
207
208                 if (DBG) {
209                     log("Received state=" + state + ", old=" + mMobileDataState +
210                         ", reason=" + (reason == null ? "(unspecified)" : reason));
211                 }
212                 if (mMobileDataState != state) {
213                     mMobileDataState = state;
214                     switch (state) {
215                         case DISCONNECTED:
216                             if(isTeardownRequested()) {
217                                 setTeardownRequested(false);
218                             }
219
220                             setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
221                             boolean doReset = true;
222                             if (mIsDefaultOrHipri == true) {
223                                 // both default and hipri must go down before we reset
224                                 int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ?
225                                     ConnectivityManager.TYPE_MOBILE_HIPRI :
226                                     ConnectivityManager.TYPE_MOBILE);
227                                 if (mConnectivityManager == null) {
228                                     IBinder b = ServiceManager.getService(
229                                             Context.CONNECTIVITY_SERVICE);
230                                     mConnectivityManager = IConnectivityManager.Stub.asInterface(b);
231                                 }
232                                 try {
233                                     if (mConnectivityManager != null) {
234                                         NetworkInfo info = mConnectivityManager.getNetworkInfo(
235                                                 typeToCheck);
236                                         if (info.isConnected() == true) {
237                                             doReset = false;
238                                         }
239                                     }
240                                 } catch (RemoteException e) {
241                                     // just go ahead with the reset
242                                     loge("Exception trying to contact ConnService: " + e);
243                                 }
244                             }
245                             if (doReset && mLinkProperties != null) {
246                                 String iface = mLinkProperties.getInterfaceName();
247                                 if (iface != null) NetworkUtils.resetConnections(iface);
248                             }
249                             // TODO - check this
250                             // can't do this here - ConnectivityService needs it to clear stuff
251                             // it's ok though - just leave it to be refreshed next time
252                             // we connect.
253                             //if (DBG) log("clearing mInterfaceName for "+ mApnType +
254                             //        " as it DISCONNECTED");
255                             //mInterfaceName = null;
256                             break;
257                         case CONNECTING:
258                             setDetailedState(DetailedState.CONNECTING, reason, apnName);
259                             break;
260                         case SUSPENDED:
261                             setDetailedState(DetailedState.SUSPENDED, reason, apnName);
262                             break;
263                         case CONNECTED:
264                             mLinkProperties = intent.getParcelableExtra(
265                                     Phone.DATA_LINK_PROPERTIES_KEY);
266                             if (mLinkProperties == null) {
267                                 log("CONNECTED event did not supply link properties.");
268                                 mLinkProperties = new LinkProperties();
269                             }
270                             mLinkCapabilities = intent.getParcelableExtra(
271                                     Phone.DATA_LINK_CAPABILITIES_KEY);
272                             if (mLinkCapabilities == null) {
273                                 log("CONNECTED event did not supply link capabilities.");
274                                 mLinkCapabilities = new LinkCapabilities();
275                             }
276                             setDetailedState(DetailedState.CONNECTED, reason, apnName);
277                             break;
278                     }
279                 } else {
280                     // There was no state change. Check if LinkProperties has been updated.
281                     if (TextUtils.equals(reason, Phone.REASON_LINK_PROPERTIES_CHANGED)) {
282                         mLinkProperties = intent.getParcelableExtra(Phone.DATA_LINK_PROPERTIES_KEY);
283                         if (mLinkProperties == null) {
284                             log("No link property in LINK_PROPERTIES change event.");
285                             mLinkProperties = new LinkProperties();
286                         }
287                         // Just update reason field in this NetworkInfo
288                         mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
289                                                       mNetworkInfo.getExtraInfo());
290                         Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
291                                                             mNetworkInfo);
292                         msg.sendToTarget();
293                     }
294                 }
295             } else if (intent.getAction().
296                     equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
297                 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
298                 if (!TextUtils.equals(apnType, mApnType)) {
299                     if (DBG) {
300                         log(String.format(
301                                 "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
302                                 "mApnType=%s != received apnType=%s", mApnType, apnType));
303                     }
304                     return;
305                 }
306                 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
307                 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
308                 if (DBG) {
309                     log("Received " + intent.getAction() +
310                                 " broadcast" + reason == null ? "" : "(" + reason + ")");
311                 }
312                 setDetailedState(DetailedState.FAILED, reason, apnName);
313             } else if (intent.getAction().
314                     equals(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) {
315                 if (DBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER");
316                 mMessenger = intent.getParcelableExtra(DataConnectionTracker.EXTRA_MESSENGER);
317                 AsyncChannel ac = new AsyncChannel();
318                 ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger);
319             } else {
320                 if (DBG) log("Broadcast received: ignore " + intent.getAction());
321             }
322         }
323     }
324
325     private void getPhoneService(boolean forceRefresh) {
326         if ((mPhoneService == null) || forceRefresh) {
327             mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
328         }
329     }
330
331     /**
332      * Report whether data connectivity is possible.
333      */
334     public boolean isAvailable() {
335         return mNetworkInfo.isAvailable();
336     }
337
338     /**
339      * Return the system properties name associated with the tcp buffer sizes
340      * for this network.
341      */
342     public String getTcpBufferSizesPropName() {
343         String networkTypeStr = "unknown";
344         TelephonyManager tm = new TelephonyManager(mContext);
345         //TODO We have to edit the parameter for getNetworkType regarding CDMA
346         switch(tm.getNetworkType()) {
347         case TelephonyManager.NETWORK_TYPE_GPRS:
348             networkTypeStr = "gprs";
349             break;
350         case TelephonyManager.NETWORK_TYPE_EDGE:
351             networkTypeStr = "edge";
352             break;
353         case TelephonyManager.NETWORK_TYPE_UMTS:
354             networkTypeStr = "umts";
355             break;
356         case TelephonyManager.NETWORK_TYPE_HSDPA:
357             networkTypeStr = "hsdpa";
358             break;
359         case TelephonyManager.NETWORK_TYPE_HSUPA:
360             networkTypeStr = "hsupa";
361             break;
362         case TelephonyManager.NETWORK_TYPE_HSPA:
363             networkTypeStr = "hspa";
364             break;
365         case TelephonyManager.NETWORK_TYPE_CDMA:
366             networkTypeStr = "cdma";
367             break;
368         case TelephonyManager.NETWORK_TYPE_1xRTT:
369             networkTypeStr = "1xrtt";
370             break;
371         case TelephonyManager.NETWORK_TYPE_EVDO_0:
372             networkTypeStr = "evdo";
373             break;
374         case TelephonyManager.NETWORK_TYPE_EVDO_A:
375             networkTypeStr = "evdo";
376             break;
377         case TelephonyManager.NETWORK_TYPE_EVDO_B:
378             networkTypeStr = "evdo";
379             break;
380         case TelephonyManager.NETWORK_TYPE_IDEN:
381             networkTypeStr = "iden";
382             break;
383         case TelephonyManager.NETWORK_TYPE_LTE:
384             networkTypeStr = "lte";
385             break;
386         case TelephonyManager.NETWORK_TYPE_EHRPD:
387             networkTypeStr = "ehrpd";
388             break;
389         default:
390             loge("unknown network type: " + tm.getNetworkType());
391         }
392         return "net.tcp.buffersize." + networkTypeStr;
393     }
394
395     /**
396      * Tear down mobile data connectivity, i.e., disable the ability to create
397      * mobile data connections.
398      * TODO - make async and return nothing?
399      */
400     public boolean teardown() {
401         setTeardownRequested(true);
402         return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
403     }
404
405     /**
406      * Record the detailed state of a network, and if it is a
407      * change from the previous state, send a notification to
408      * any listeners.
409      * @param state the new @{code DetailedState}
410      * @param reason a {@code String} indicating a reason for the state change,
411      * if one was supplied. May be {@code null}.
412      * @param extraInfo optional {@code String} providing extra information about the state change
413      */
414     private void setDetailedState(NetworkInfo.DetailedState state, String reason,
415             String extraInfo) {
416         if (DBG) log("setDetailed state, old ="
417                 + mNetworkInfo.getDetailedState() + " and new state=" + state);
418         if (state != mNetworkInfo.getDetailedState()) {
419             boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
420             String lastReason = mNetworkInfo.getReason();
421             /*
422              * If a reason was supplied when the CONNECTING state was entered, and no
423              * reason was supplied for entering the CONNECTED state, then retain the
424              * reason that was supplied when going to CONNECTING.
425              */
426             if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
427                     && lastReason != null)
428                 reason = lastReason;
429             mNetworkInfo.setDetailedState(state, reason, extraInfo);
430             Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
431             msg.sendToTarget();
432         }
433     }
434
435     public void setTeardownRequested(boolean isRequested) {
436         mTeardownRequested = isRequested;
437     }
438
439     public boolean isTeardownRequested() {
440         return mTeardownRequested;
441     }
442
443     /**
444      * Re-enable mobile data connectivity after a {@link #teardown()}.
445      * TODO - make async and always get a notification?
446      */
447     public boolean reconnect() {
448         boolean retValue = false; //connected or expect to be?
449         setTeardownRequested(false);
450         switch (setEnableApn(mApnType, true)) {
451             case Phone.APN_ALREADY_ACTIVE:
452                 // need to set self to CONNECTING so the below message is handled.
453                 retValue = true;
454                 break;
455             case Phone.APN_REQUEST_STARTED:
456                 // set IDLE here , avoid the following second FAILED not sent out
457                 mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
458                 retValue = true;
459                 break;
460             case Phone.APN_REQUEST_FAILED:
461             case Phone.APN_TYPE_NOT_AVAILABLE:
462                 break;
463             default:
464                 loge("Error in reconnect - unexpected response.");
465                 break;
466         }
467         return retValue;
468     }
469
470     /**
471      * Turn on or off the mobile radio. No connectivity will be possible while the
472      * radio is off. The operation is a no-op if the radio is already in the desired state.
473      * @param turnOn {@code true} if the radio should be turned on, {@code false} if
474      */
475     public boolean setRadio(boolean turnOn) {
476         getPhoneService(false);
477         /*
478          * If the phone process has crashed in the past, we'll get a
479          * RemoteException and need to re-reference the service.
480          */
481         for (int retry = 0; retry < 2; retry++) {
482             if (mPhoneService == null) {
483                 log("Ignoring mobile radio request because could not acquire PhoneService");
484                 break;
485             }
486
487             try {
488                 return mPhoneService.setRadio(turnOn);
489             } catch (RemoteException e) {
490                 if (retry == 0) getPhoneService(true);
491             }
492         }
493
494         log("Could not set radio power to " + (turnOn ? "on" : "off"));
495         return false;
496     }
497
498     /**
499      * @param enabled
500      */
501     public void setDataEnable(boolean enabled) {
502         try {
503             log("setDataEnable: E enabled=" + enabled);
504             mDataConnectionTrackerAc.sendMessage(DataConnectionTracker.CMD_SET_DATA_ENABLE,
505                     enabled ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
506             log("setDataEnable: X enabled=" + enabled);
507         } catch (Exception e) {
508             log("setDataEnable: X mAc was null" + e);
509         }
510     }
511
512     /**
513      * carrier dependency is met/unmet
514      * @param met
515      */
516     public void setDependencyMet(boolean met) {
517         Bundle bundle = Bundle.forPair(DataConnectionTracker.APN_TYPE_KEY, mApnType);
518         try {
519             log("setDependencyMet: E met=" + met);
520             Message msg = Message.obtain();
521             msg.what = DataConnectionTracker.CMD_SET_DEPENDENCY_MET;
522             msg.arg1 = (met ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
523             msg.setData(bundle);
524             mDataConnectionTrackerAc.sendMessage(msg);
525             log("setDependencyMet: X met=" + met);
526         } catch (NullPointerException e) {
527             log("setDependencyMet: X mAc was null" + e);
528         }
529     }
530
531     @Override
532     public String toString() {
533         StringBuffer sb = new StringBuffer("Mobile data state: ");
534
535         sb.append(mMobileDataState);
536         return sb.toString();
537     }
538
539    /**
540      * Internal method supporting the ENABLE_MMS feature.
541      * @param apnType the type of APN to be enabled or disabled (e.g., mms)
542      * @param enable {@code true} to enable the specified APN type,
543      * {@code false} to disable it.
544      * @return an integer value representing the outcome of the request.
545      */
546     private int setEnableApn(String apnType, boolean enable) {
547         getPhoneService(false);
548         /*
549          * If the phone process has crashed in the past, we'll get a
550          * RemoteException and need to re-reference the service.
551          */
552         for (int retry = 0; retry < 2; retry++) {
553             if (mPhoneService == null) {
554                 log("Ignoring feature request because could not acquire PhoneService");
555                 break;
556             }
557
558             try {
559                 if (enable) {
560                     return mPhoneService.enableApnType(apnType);
561                 } else {
562                     return mPhoneService.disableApnType(apnType);
563                 }
564             } catch (RemoteException e) {
565                 if (retry == 0) getPhoneService(true);
566             }
567         }
568
569         log("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
570         return Phone.APN_REQUEST_FAILED;
571     }
572
573     public static String networkTypeToApnType(int netType) {
574         switch(netType) {
575             case ConnectivityManager.TYPE_MOBILE:
576                 return Phone.APN_TYPE_DEFAULT;  // TODO - use just one of these
577             case ConnectivityManager.TYPE_MOBILE_MMS:
578                 return Phone.APN_TYPE_MMS;
579             case ConnectivityManager.TYPE_MOBILE_SUPL:
580                 return Phone.APN_TYPE_SUPL;
581             case ConnectivityManager.TYPE_MOBILE_DUN:
582                 return Phone.APN_TYPE_DUN;
583             case ConnectivityManager.TYPE_MOBILE_HIPRI:
584                 return Phone.APN_TYPE_HIPRI;
585             case ConnectivityManager.TYPE_MOBILE_FOTA:
586                 return Phone.APN_TYPE_FOTA;
587             case ConnectivityManager.TYPE_MOBILE_IMS:
588                 return Phone.APN_TYPE_IMS;
589             case ConnectivityManager.TYPE_MOBILE_CBS:
590                 return Phone.APN_TYPE_CBS;
591             default:
592                 sloge("Error mapping networkType " + netType + " to apnType.");
593                 return null;
594         }
595     }
596
597     /**
598      * @see android.net.NetworkStateTracker#getLinkProperties()
599      */
600     public LinkProperties getLinkProperties() {
601         return new LinkProperties(mLinkProperties);
602     }
603
604     /**
605      * @see android.net.NetworkStateTracker#getLinkCapabilities()
606      */
607     public LinkCapabilities getLinkCapabilities() {
608         return new LinkCapabilities(mLinkCapabilities);
609     }
610
611     private void log(String s) {
612         Slog.d(TAG, mApnType + ": " + s);
613     }
614
615     private void loge(String s) {
616         Slog.e(TAG, mApnType + ": " + s);
617     }
618
619     static private void sloge(String s) {
620         Slog.e(TAG, s);
621     }
622 }