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