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.NetworkRequest;
24 import android.net.NetworkInfo;
25 import android.os.SystemClock;
26 import android.provider.Settings;
27 import android.util.Log;
28 
29 import com.android.mms.service.exception.MmsNetworkException;
30 import com.android.okhttp.ConnectionPool;
31 import com.android.okhttp.HostResolver;
32 
33 import java.net.InetAddress;
34 import java.net.UnknownHostException;
35 
36 /**
37  * Manages the MMS network connectivity
38  */
39 public class MmsNetworkManager implements HostResolver {
40     // Timeout used to call ConnectivityManager.requestNetwork
41     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
42     // Wait timeout for this class, a little bit longer than the above timeout
43     // to make sure we don't bail prematurely
44     private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS =
45             NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000);
46 
47     // Borrowed from {@link android.net.Network}
48     private static final boolean httpKeepAlive =
49             Boolean.parseBoolean(System.getProperty("http.keepAlive", "true"));
50     private static final int httpMaxConnections =
51             httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0;
52     private static final long httpKeepAliveDurationMs =
53             Long.parseLong(System.getProperty("http.keepAliveDuration", "300000"));  // 5 minutes.
54 
55     private final Context mContext;
56 
57     // The requested MMS {@link android.net.Network} we are holding
58     // We need this when we unbind from it. This is also used to indicate if the
59     // MMS network is available.
60     private Network mNetwork;
61     // The current count of MMS requests that require the MMS network
62     // If mMmsRequestCount is 0, we should release the MMS network.
63     private int mMmsRequestCount;
64     // This is really just for using the capability
65     private final NetworkRequest mNetworkRequest;
66     // The callback to register when we request MMS network
67     private ConnectivityManager.NetworkCallback mNetworkCallback;
68 
69     private volatile ConnectivityManager mConnectivityManager;
70 
71     // The OkHttp's ConnectionPool used by the HTTP client associated with this network manager
72     private ConnectionPool mConnectionPool;
73 
74     // The MMS HTTP client for this network
75     private MmsHttpClient mMmsHttpClient;
76 
77     // The SIM ID which we use to connect
78     private final int mSubId;
79 
MmsNetworkManager(Context context, int subId)80     public MmsNetworkManager(Context context, int subId) {
81         mContext = context;
82         mNetworkCallback = null;
83         mNetwork = null;
84         mMmsRequestCount = 0;
85         mConnectivityManager = null;
86         mConnectionPool = null;
87         mMmsHttpClient = null;
88         mSubId = subId;
89         mNetworkRequest = new NetworkRequest.Builder()
90                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
91                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
92                 .setNetworkSpecifier(Integer.toString(mSubId))
93                 .build();
94     }
95 
96     /**
97      * Acquire the MMS network
98      *
99      * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
100      */
acquireNetwork()101     public void acquireNetwork() throws MmsNetworkException {
102         synchronized (this) {
103             mMmsRequestCount += 1;
104             if (mNetwork != null) {
105                 // Already available
106                 Log.d(MmsService.TAG, "MmsNetworkManager: already available");
107                 return;
108             }
109             Log.d(MmsService.TAG, "MmsNetworkManager: start new network request");
110             // Not available, so start a new request
111             newRequest();
112             final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS;
113             long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS;
114             while (waitTime > 0) {
115                 try {
116                     this.wait(waitTime);
117                 } catch (InterruptedException e) {
118                     Log.w(MmsService.TAG, "MmsNetworkManager: acquire network wait interrupted");
119                 }
120                 if (mNetwork != null) {
121                     // Success
122                     return;
123                 }
124                 // Calculate remaining waiting time to make sure we wait the full timeout period
125                 waitTime = shouldEnd - SystemClock.elapsedRealtime();
126             }
127             // Timed out, so release the request and fail
128             Log.d(MmsService.TAG, "MmsNetworkManager: timed out");
129             releaseRequestLocked(mNetworkCallback);
130             throw new MmsNetworkException("Acquiring network timed out");
131         }
132     }
133 
134     /**
135      * Release the MMS network when nobody is holding on to it.
136      */
releaseNetwork()137     public void releaseNetwork() {
138         synchronized (this) {
139             if (mMmsRequestCount > 0) {
140                 mMmsRequestCount -= 1;
141                 Log.d(MmsService.TAG, "MmsNetworkManager: release, count=" + mMmsRequestCount);
142                 if (mMmsRequestCount < 1) {
143                     releaseRequestLocked(mNetworkCallback);
144                 }
145             }
146         }
147     }
148 
149     /**
150      * Start a new {@link android.net.NetworkRequest} for MMS
151      */
newRequest()152     private void newRequest() {
153         final ConnectivityManager connectivityManager = getConnectivityManager();
154         mNetworkCallback = new ConnectivityManager.NetworkCallback() {
155             @Override
156             public void onAvailable(Network network) {
157                 super.onAvailable(network);
158                 Log.d(MmsService.TAG, "NetworkCallbackListener.onAvailable: network=" + network);
159                 synchronized (MmsNetworkManager.this) {
160                     mNetwork = network;
161                     MmsNetworkManager.this.notifyAll();
162                 }
163             }
164 
165             @Override
166             public void onLost(Network network) {
167                 super.onLost(network);
168                 Log.d(MmsService.TAG, "NetworkCallbackListener.onLost: network=" + network);
169                 synchronized (MmsNetworkManager.this) {
170                     releaseRequestLocked(this);
171                     MmsNetworkManager.this.notifyAll();
172                 }
173             }
174 
175             @Override
176             public void onUnavailable() {
177                 super.onUnavailable();
178                 Log.d(MmsService.TAG, "NetworkCallbackListener.onUnavailable");
179                 synchronized (MmsNetworkManager.this) {
180                     releaseRequestLocked(this);
181                     MmsNetworkManager.this.notifyAll();
182                 }
183             }
184         };
185         connectivityManager.requestNetwork(
186                 mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
187     }
188 
189     /**
190      * Release the current {@link android.net.NetworkRequest} for MMS
191      *
192      * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
193      */
releaseRequestLocked(ConnectivityManager.NetworkCallback callback)194     private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
195         if (callback != null) {
196             final ConnectivityManager connectivityManager = getConnectivityManager();
197             connectivityManager.unregisterNetworkCallback(callback);
198         }
199         resetLocked();
200     }
201 
202     /**
203      * Reset the state
204      */
resetLocked()205     private void resetLocked() {
206         mNetworkCallback = null;
207         mNetwork = null;
208         mMmsRequestCount = 0;
209         // Currently we follow what android.net.Network does with ConnectionPool,
210         // which is per Network object. So if Network changes, we should clear
211         // out the ConnectionPool and thus the MmsHttpClient (since it is linked
212         // to a specific ConnectionPool).
213         mConnectionPool = null;
214         mMmsHttpClient = null;
215     }
216 
217     private static final InetAddress[] EMPTY_ADDRESS_ARRAY = new InetAddress[0];
218     @Override
getAllByName(String host)219     public InetAddress[] getAllByName(String host) throws UnknownHostException {
220         Network network = null;
221         synchronized (this) {
222             if (mNetwork == null) {
223                 return EMPTY_ADDRESS_ARRAY;
224             }
225             network = mNetwork;
226         }
227         return network.getAllByName(host);
228     }
229 
getConnectivityManager()230     private ConnectivityManager getConnectivityManager() {
231         if (mConnectivityManager == null) {
232             mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
233                     Context.CONNECTIVITY_SERVICE);
234         }
235         return mConnectivityManager;
236     }
237 
getOrCreateConnectionPoolLocked()238     private ConnectionPool getOrCreateConnectionPoolLocked() {
239         if (mConnectionPool == null) {
240             mConnectionPool = new ConnectionPool(httpMaxConnections, httpKeepAliveDurationMs);
241         }
242         return mConnectionPool;
243     }
244 
245     /**
246      * Get an MmsHttpClient for the current network
247      *
248      * @return The MmsHttpClient instance
249      */
getOrCreateHttpClient()250     public MmsHttpClient getOrCreateHttpClient() {
251         synchronized (this) {
252             if (mMmsHttpClient == null) {
253                 if (mNetwork != null) {
254                     // Create new MmsHttpClient for the current Network
255                     mMmsHttpClient = new MmsHttpClient(
256                             mContext,
257                             mNetwork.getSocketFactory(),
258                             MmsNetworkManager.this,
259                             getOrCreateConnectionPoolLocked());
260                 }
261             }
262             return mMmsHttpClient;
263         }
264     }
265 
266     /**
267      * Get the APN name for the active network
268      *
269      * @return The APN name if available, otherwise null
270      */
getApnName()271     public String getApnName() {
272         Network network = null;
273         synchronized (this) {
274             if (mNetwork == null) {
275                 Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: network not available");
276                 return null;
277             }
278             network = mNetwork;
279         }
280         String apnName = null;
281         final ConnectivityManager connectivityManager = getConnectivityManager();
282         NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network);
283         if (mmsNetworkInfo != null) {
284             apnName = mmsNetworkInfo.getExtraInfo();
285         }
286         Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: " + apnName);
287         return apnName;
288     }
289 }
290