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