• 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.annotations.VisibleForTesting;
36 import com.android.internal.telephony.PhoneConstants;
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     private final Dependencies mDeps;
94 
95     /**
96      * This receiver listens to ACTION_SIM_CARD_STATE_CHANGED after starting a new NetworkRequest.
97      * If ACTION_SIM_CARD_STATE_CHANGED with SIM_STATE_ABSENT for a SIM card corresponding to the
98      * current NetworkRequest is received, it just releases the NetworkRequest without waiting for
99      * timeout.
100      */
101     private final BroadcastReceiver mReceiver =
102             new BroadcastReceiver() {
103                 @Override
104                 public void onReceive(Context context, Intent intent) {
105                     final int simState =
106                             intent.getIntExtra(
107                                     TelephonyManager.EXTRA_SIM_STATE,
108                                     TelephonyManager.SIM_STATE_UNKNOWN);
109                     final int phoneId =
110                             intent.getIntExtra(
111                                     PhoneConstants.PHONE_KEY,
112                                     SubscriptionManager.INVALID_PHONE_INDEX);
113                     LogUtil.i("MmsNetworkManager: received ACTION_SIM_CARD_STATE_CHANGED"
114                             + ", state=" + simStateString(simState) + ", phoneId=" + phoneId);
115 
116                     if (mPhoneId == phoneId && simState == TelephonyManager.SIM_STATE_ABSENT) {
117                         synchronized (MmsNetworkManager.this) {
118                             releaseRequestLocked(mNetworkCallback);
119                             MmsNetworkManager.this.notifyAll();
120                         }
121                     }
122                 }
123             };
124 
simStateString(int state)125     private static String simStateString(int state) {
126         switch (state) {
127             case TelephonyManager.SIM_STATE_UNKNOWN:
128                 return "UNKNOWN";
129             case TelephonyManager.SIM_STATE_ABSENT:
130                 return "ABSENT";
131             case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
132                 return "CARD_IO_ERROR";
133             case TelephonyManager.SIM_STATE_CARD_RESTRICTED:
134                 return "CARD_RESTRICTED";
135             case TelephonyManager.SIM_STATE_PRESENT:
136                 return "PRESENT";
137             default:
138                 return "INVALID";
139         }
140     }
141 
142     /**
143      * Network callback for our network request
144      */
145     private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback {
146         @Override
onLost(Network network)147         public void onLost(Network network) {
148             super.onLost(network);
149             LogUtil.w("NetworkCallbackListener.onLost: network=" + network);
150             synchronized (MmsNetworkManager.this) {
151                 // Wait for other available network. Not notify.
152                 if (network.equals(mNetwork)) {
153                     mNetwork = null;
154                     mMmsHttpClient = null;
155                 }
156             }
157         }
158 
159         @Override
onUnavailable()160         public void onUnavailable() {
161             super.onUnavailable();
162             LogUtil.w("NetworkCallbackListener.onUnavailable");
163             synchronized (MmsNetworkManager.this) {
164                 releaseRequestLocked(this);
165                 MmsNetworkManager.this.notifyAll();
166             }
167         }
168 
169         @Override
onCapabilitiesChanged(Network network, NetworkCapabilities nc)170         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
171             // onAvailable will always immediately be followed by a onCapabilitiesChanged. Check
172             // network status here is enough.
173             super.onCapabilitiesChanged(network, nc);
174             LogUtil.w("NetworkCallbackListener.onCapabilitiesChanged: network="
175                     + network + ", nc=" + nc);
176             synchronized (MmsNetworkManager.this) {
177                 final boolean isAvailable =
178                         nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
179                 if (network.equals(mNetwork) && !isAvailable) {
180                     // Current network becomes suspended.
181                     mNetwork = null;
182                     mMmsHttpClient = null;
183                     // Not notify. Either wait for other available network or current network to
184                     // become available again.
185                     return;
186                 }
187 
188                 // New available network
189                 if (mNetwork == null && isAvailable) {
190                     mNetwork = network;
191                     MmsNetworkManager.this.notifyAll();
192                 }
193             }
194         }
195     }
196 
197     /**
198      * Dependencies of MmsNetworkManager, for injection in tests.
199      */
200     @VisibleForTesting
201     public static class Dependencies {
202         /** Get phone Id from the given subId */
getPhoneId(int subId)203         public int getPhoneId(int subId) {
204             return SubscriptionManager.getPhoneId(subId);
205         }
206 
207         // Timeout used to call ConnectivityManager.requestNetwork. Given that the telephony layer
208         // will retry on failures, this timeout should be high enough.
getNetworkRequestTimeoutMillis()209         public int getNetworkRequestTimeoutMillis() {
210             return DeviceConfig.getInt(
211                     DeviceConfig.NAMESPACE_TELEPHONY, MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS,
212                     DEFAULT_MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS);
213         }
214 
getAdditionalNetworkAcquireTimeoutMillis()215         public int getAdditionalNetworkAcquireTimeoutMillis() {
216             return ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS;
217         }
218     }
219 
220     @VisibleForTesting
MmsNetworkManager(Context context, int subId, Dependencies dependencies)221     protected MmsNetworkManager(Context context, int subId, Dependencies dependencies) {
222         mContext = context;
223         mDeps = dependencies;
224         mNetworkCallback = null;
225         mNetwork = null;
226         mMmsRequestCount = 0;
227         mConnectivityManager = null;
228         mMmsHttpClient = null;
229         mSubId = subId;
230         mReleaseHandler = new Handler(Looper.getMainLooper());
231         mNetworkRequest = new NetworkRequest.Builder()
232                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
233                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
234                 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
235                         .setSubscriptionId(mSubId).build())
236                 .build();
237 
238         mNetworkReleaseTask = new Runnable() {
239             @Override
240             public void run() {
241                 synchronized (this) {
242                     if (mMmsRequestCount < 1) {
243                         releaseRequestLocked(mNetworkCallback);
244                     }
245                 }
246             }
247         };
248     }
249 
MmsNetworkManager(Context context, int subId)250     public MmsNetworkManager(Context context, int subId) {
251         this(context, subId, new Dependencies());
252     }
253 
254     /**
255      * Acquire the MMS network
256      *
257      * @param requestId request ID for logging
258      * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
259      */
acquireNetwork(final String requestId)260     public void acquireNetwork(final String requestId) throws MmsNetworkException {
261         int networkRequestTimeoutMillis = mDeps.getNetworkRequestTimeoutMillis();
262 
263         synchronized (this) {
264             // Since we are acquiring the network, remove the network release task if exists.
265             mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
266             mMmsRequestCount += 1;
267             if (mNetwork != null) {
268                 // Already available
269                 LogUtil.d(requestId, "MmsNetworkManager: already available");
270                 return;
271             }
272 
273             if (!mReceiverRegistered) {
274                 mPhoneId = mDeps.getPhoneId(mSubId);
275                 if (mPhoneId == SubscriptionManager.INVALID_PHONE_INDEX
276                         || mPhoneId == SubscriptionManager.DEFAULT_PHONE_INDEX) {
277                     throw new MmsNetworkException("Invalid Phone Id: " + mPhoneId);
278                 }
279 
280                 // Register a receiver to listen to ACTION_SIM_CARD_STATE_CHANGED
281                 mContext.registerReceiver(
282                         mReceiver,
283                         new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED));
284                 mReceiverRegistered = true;
285             }
286 
287             // Not available, so start a new request if not done yet
288             if (mNetworkCallback == null) {
289                 LogUtil.d(requestId, "MmsNetworkManager: start new network request");
290                 startNewNetworkRequestLocked(networkRequestTimeoutMillis);
291             }
292 
293             try {
294                 this.wait(networkRequestTimeoutMillis
295                         + mDeps.getAdditionalNetworkAcquireTimeoutMillis());
296             } catch (InterruptedException e) {
297                 LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted");
298             }
299 
300             if (mReceiverRegistered) {
301                 // Unregister the receiver.
302                 mContext.unregisterReceiver(mReceiver);
303                 mReceiverRegistered = false;
304             }
305 
306             if (mNetwork != null) {
307                 // Success
308                 return;
309             }
310 
311             if (mNetworkCallback != null) { // Timed out
312                 LogUtil.e(requestId,
313                         "MmsNetworkManager: timed out with networkRequestTimeoutMillis="
314                                 + networkRequestTimeoutMillis
315                                 + " and ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS="
316                                 + mDeps.getAdditionalNetworkAcquireTimeoutMillis());
317                 // Release the network request and wake up all the MmsRequests for fast-fail
318                 // together.
319                 // TODO: Start new network request for remaining MmsRequests?
320                 releaseRequestLocked(mNetworkCallback);
321                 this.notifyAll();
322             }
323 
324             throw new MmsNetworkException("Acquiring network failed");
325         }
326     }
327 
328     /**
329      * Release the MMS network when nobody is holding on to it.
330      *
331      * @param requestId          request ID for logging
332      * @param shouldDelayRelease whether the release should be delayed for 5 seconds, the regular
333      *                           use case is to delay this for DownloadRequests to use the network
334      *                           for sending an acknowledgement on the same network
335      */
releaseNetwork(final String requestId, final boolean shouldDelayRelease)336     public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) {
337         synchronized (this) {
338             if (mMmsRequestCount > 0) {
339                 mMmsRequestCount -= 1;
340                 LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount);
341                 if (mMmsRequestCount < 1) {
342                     if (shouldDelayRelease) {
343                         // remove previously posted task and post a delayed task on the release
344                         // handler to release the network
345                         mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
346                         mReleaseHandler.postDelayed(mNetworkReleaseTask,
347                                 NETWORK_RELEASE_TIMEOUT_MILLIS);
348                     } else {
349                         releaseRequestLocked(mNetworkCallback);
350                     }
351                 }
352             }
353         }
354     }
355 
356     /**
357      * Start a new {@link android.net.NetworkRequest} for MMS
358      */
startNewNetworkRequestLocked(int networkRequestTimeoutMillis)359     private void startNewNetworkRequestLocked(int networkRequestTimeoutMillis) {
360         final ConnectivityManager connectivityManager = getConnectivityManager();
361         mNetworkCallback = new NetworkRequestCallback();
362         connectivityManager.requestNetwork(
363                 mNetworkRequest, mNetworkCallback, networkRequestTimeoutMillis);
364     }
365 
366     /**
367      * Release the current {@link android.net.NetworkRequest} for MMS
368      *
369      * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
370      */
releaseRequestLocked(ConnectivityManager.NetworkCallback callback)371     private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
372         if (callback != null) {
373             final ConnectivityManager connectivityManager = getConnectivityManager();
374             try {
375                 connectivityManager.unregisterNetworkCallback(callback);
376             } catch (IllegalArgumentException e) {
377                 // It is possible ConnectivityManager.requestNetwork may fail silently due
378                 // to RemoteException. When that happens, we may get an invalid
379                 // NetworkCallback, which causes an IllegalArgumentexception when we try to
380                 // unregisterNetworkCallback. This exception in turn causes
381                 // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service
382                 // would get stuck in the bad state until the device restarts. This fix
383                 // catches the exception so that state clean up can be executed.
384                 LogUtil.w("Unregister network callback exception", e);
385             }
386         }
387         resetLocked();
388     }
389 
390     /**
391      * Reset the state
392      */
resetLocked()393     private void resetLocked() {
394         mNetworkCallback = null;
395         mNetwork = null;
396         mMmsRequestCount = 0;
397         mMmsHttpClient = null;
398     }
399 
getConnectivityManager()400     private ConnectivityManager getConnectivityManager() {
401         if (mConnectivityManager == null) {
402             mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
403                     Context.CONNECTIVITY_SERVICE);
404         }
405         return mConnectivityManager;
406     }
407 
408     /**
409      * Get an MmsHttpClient for the current network
410      *
411      * @return The MmsHttpClient instance
412      */
getOrCreateHttpClient()413     public MmsHttpClient getOrCreateHttpClient() {
414         synchronized (this) {
415             if (mMmsHttpClient == null) {
416                 if (mNetwork != null) {
417                     // Create new MmsHttpClient for the current Network
418                     mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager);
419                 }
420             }
421             return mMmsHttpClient;
422         }
423     }
424 
425     /**
426      * Get the APN name for the active network
427      *
428      * @return The APN name if available, otherwise null
429      */
getApnName()430     public String getApnName() {
431         Network network = null;
432         synchronized (this) {
433             if (mNetwork == null) {
434                 return null;
435             }
436             network = mNetwork;
437         }
438         String apnName = null;
439         final ConnectivityManager connectivityManager = getConnectivityManager();
440         final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network);
441         if (mmsNetworkInfo != null) {
442             apnName = mmsNetworkInfo.getExtraInfo();
443         }
444         return apnName;
445     }
446 }
447