• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 com.android.mms.service;
18 
19 import android.content.Context;
20 import android.net.ConnectivityManager;
21 import android.net.Network;
22 import android.net.NetworkCapabilities;
23 import android.net.NetworkInfo;
24 import android.net.NetworkRequest;
25 import android.os.Handler;
26 import android.os.Looper;
27 
28 import com.android.mms.service.exception.MmsNetworkException;
29 
30 /**
31  * Manages the MMS network connectivity
32  */
33 public class MmsNetworkManager {
34     // Timeout used to call ConnectivityManager.requestNetwork
35     // Given that the telephony layer will retry on failures, this timeout should be high enough.
36     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 30 * 60 * 1000;
37     // Wait timeout for this class, a little bit longer than the above timeout
38     // to make sure we don't bail prematurely
39     private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS =
40             NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000);
41     // Waiting time used before releasing a network prematurely. This allows the MMS download
42     // acknowledgement messages to be sent using the same network that was used to download the data
43     private static final int NETWORK_RELEASE_TIMEOUT_MILLIS = 5 * 1000;
44 
45     private final Context mContext;
46 
47     // The requested MMS {@link android.net.Network} we are holding
48     // We need this when we unbind from it. This is also used to indicate if the
49     // MMS network is available.
50     private Network mNetwork;
51     // The current count of MMS requests that require the MMS network
52     // If mMmsRequestCount is 0, we should release the MMS network.
53     private int mMmsRequestCount;
54     // This is really just for using the capability
55     private final NetworkRequest mNetworkRequest;
56     // The callback to register when we request MMS network
57     private ConnectivityManager.NetworkCallback mNetworkCallback;
58 
59     private volatile ConnectivityManager mConnectivityManager;
60 
61     // The MMS HTTP client for this network
62     private MmsHttpClient mMmsHttpClient;
63 
64     // The handler used for delayed release of the network
65     private final Handler mReleaseHandler;
66 
67     // The task that does the delayed releasing of the network.
68     private final Runnable mNetworkReleaseTask;
69 
70     // The SIM ID which we use to connect
71     private final int mSubId;
72 
73     /**
74      * Network callback for our network request
75      */
76     private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback {
77         @Override
onAvailable(Network network)78         public void onAvailable(Network network) {
79             super.onAvailable(network);
80             LogUtil.i("NetworkCallbackListener.onAvailable: network=" + network);
81             synchronized (MmsNetworkManager.this) {
82                 mNetwork = network;
83                 MmsNetworkManager.this.notifyAll();
84             }
85         }
86 
87         @Override
onLost(Network network)88         public void onLost(Network network) {
89             super.onLost(network);
90             LogUtil.w("NetworkCallbackListener.onLost: network=" + network);
91             synchronized (MmsNetworkManager.this) {
92                 releaseRequestLocked(this);
93                 MmsNetworkManager.this.notifyAll();
94             }
95         }
96 
97         @Override
onUnavailable()98         public void onUnavailable() {
99             super.onUnavailable();
100             LogUtil.w("NetworkCallbackListener.onUnavailable");
101             synchronized (MmsNetworkManager.this) {
102                 releaseRequestLocked(this);
103                 MmsNetworkManager.this.notifyAll();
104             }
105         }
106     }
107 
MmsNetworkManager(Context context, int subId)108     public MmsNetworkManager(Context context, int subId) {
109         mContext = context;
110         mNetworkCallback = null;
111         mNetwork = null;
112         mMmsRequestCount = 0;
113         mConnectivityManager = null;
114         mMmsHttpClient = null;
115         mSubId = subId;
116         mReleaseHandler = new Handler(Looper.getMainLooper());
117         mNetworkRequest = new NetworkRequest.Builder()
118                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
119                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
120                 .setNetworkSpecifier(Integer.toString(mSubId))
121                 .build();
122 
123         mNetworkReleaseTask = new Runnable() {
124             @Override
125             public void run() {
126                 synchronized (this) {
127                     if (mMmsRequestCount < 1) {
128                         releaseRequestLocked(mNetworkCallback);
129                     }
130                 }
131             }
132         };
133     }
134 
135     /**
136      * Acquire the MMS network
137      *
138      * @param requestId request ID for logging
139      * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
140      */
acquireNetwork(final String requestId)141     public void acquireNetwork(final String requestId) throws MmsNetworkException {
142         synchronized (this) {
143             // Since we are acquiring the network, remove the network release task if exists.
144             mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
145             mMmsRequestCount += 1;
146             if (mNetwork != null) {
147                 // Already available
148                 LogUtil.d(requestId, "MmsNetworkManager: already available");
149                 return;
150             }
151             // Not available, so start a new request if not done yet
152             if (mNetworkCallback == null) {
153                 LogUtil.d(requestId, "MmsNetworkManager: start new network request");
154                 startNewNetworkRequestLocked();
155             }
156             try {
157                 this.wait(NETWORK_ACQUIRE_TIMEOUT_MILLIS);
158             } catch (InterruptedException e) {
159                 LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted");
160             }
161             if (mNetwork != null) {
162                 // Success
163                 return;
164             }
165 
166             // Timed out
167             LogUtil.e(requestId, "MmsNetworkManager: timed out");
168             if (mNetworkCallback != null) {
169                 // Release the network request and wake up all the MmsRequests for fast-fail
170                 // together.
171                 // TODO: Start new network request for remaining MmsRequests?
172                 releaseRequestLocked(mNetworkCallback);
173                 this.notifyAll();
174             }
175 
176             throw new MmsNetworkException("Acquiring network timed out");
177         }
178     }
179 
180     /**
181      * Release the MMS network when nobody is holding on to it.
182      *
183      * @param requestId request ID for logging
184      * @param shouldDelayRelease whether the release should be delayed for 5 seconds, the regular
185      *                           use case is to delay this for DownloadRequests to use the network
186      *                           for sending an acknowledgement on the same network
187      */
releaseNetwork(final String requestId, final boolean shouldDelayRelease)188     public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) {
189         synchronized (this) {
190             if (mMmsRequestCount > 0) {
191                 mMmsRequestCount -= 1;
192                 LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount);
193                 if (mMmsRequestCount < 1) {
194                     if (shouldDelayRelease) {
195                         // remove previously posted task and post a delayed task on the release
196                         // handler to release the network
197                         mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
198                         mReleaseHandler.postDelayed(mNetworkReleaseTask,
199                                 NETWORK_RELEASE_TIMEOUT_MILLIS);
200                     } else {
201                         releaseRequestLocked(mNetworkCallback);
202                     }
203                 }
204             }
205         }
206     }
207 
208     /**
209      * Start a new {@link android.net.NetworkRequest} for MMS
210      */
startNewNetworkRequestLocked()211     private void startNewNetworkRequestLocked() {
212         final ConnectivityManager connectivityManager = getConnectivityManager();
213         mNetworkCallback = new NetworkRequestCallback();
214         connectivityManager.requestNetwork(
215                 mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
216     }
217 
218     /**
219      * Release the current {@link android.net.NetworkRequest} for MMS
220      *
221      * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
222      */
releaseRequestLocked(ConnectivityManager.NetworkCallback callback)223     private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
224         if (callback != null) {
225             final ConnectivityManager connectivityManager = getConnectivityManager();
226             try {
227                 connectivityManager.unregisterNetworkCallback(callback);
228             } catch (IllegalArgumentException e) {
229                 // It is possible ConnectivityManager.requestNetwork may fail silently due
230                 // to RemoteException. When that happens, we may get an invalid
231                 // NetworkCallback, which causes an IllegalArgumentexception when we try to
232                 // unregisterNetworkCallback. This exception in turn causes
233                 // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service
234                 // would get stuck in the bad state until the device restarts. This fix
235                 // catches the exception so that state clean up can be executed.
236                 LogUtil.w("Unregister network callback exception", e);
237             }
238         }
239         resetLocked();
240     }
241 
242     /**
243      * Reset the state
244      */
resetLocked()245     private void resetLocked() {
246         mNetworkCallback = null;
247         mNetwork = null;
248         mMmsRequestCount = 0;
249         mMmsHttpClient = null;
250     }
251 
getConnectivityManager()252     private ConnectivityManager getConnectivityManager() {
253         if (mConnectivityManager == null) {
254             mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
255                     Context.CONNECTIVITY_SERVICE);
256         }
257         return mConnectivityManager;
258     }
259 
260     /**
261      * Get an MmsHttpClient for the current network
262      *
263      * @return The MmsHttpClient instance
264      */
getOrCreateHttpClient()265     public MmsHttpClient getOrCreateHttpClient() {
266         synchronized (this) {
267             if (mMmsHttpClient == null) {
268                 if (mNetwork != null) {
269                     // Create new MmsHttpClient for the current Network
270                     mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager);
271                 }
272             }
273             return mMmsHttpClient;
274         }
275     }
276 
277     /**
278      * Get the APN name for the active network
279      *
280      * @return The APN name if available, otherwise null
281      */
getApnName()282     public String getApnName() {
283         Network network = null;
284         synchronized (this) {
285             if (mNetwork == null) {
286                 return null;
287             }
288             network = mNetwork;
289         }
290         String apnName = null;
291         final ConnectivityManager connectivityManager = getConnectivityManager();
292         final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network);
293         if (mmsNetworkInfo != null) {
294             apnName = mmsNetworkInfo.getExtraInfo();
295         }
296         return apnName;
297     }
298 }
299