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