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