OSDN Git Service

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