• 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.annotation.NonNull;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.net.ConnectivityManager;
25 import android.net.Network;
26 import android.net.NetworkCapabilities;
27 import android.net.NetworkInfo;
28 import android.net.NetworkRequest;
29 import android.net.TelephonyNetworkSpecifier;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.PersistableBundle;
34 import android.provider.DeviceConfig;
35 import android.telephony.CarrierConfigManager;
36 import android.telephony.SubscriptionManager;
37 import android.telephony.TelephonyManager;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.telephony.PhoneConstants;
41 import com.android.mms.service.exception.MmsNetworkException;
42 
43 /**
44  * Manages the MMS network connectivity
45  */
46 public class MmsNetworkManager {
47     private static final String MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS =
48             "mms_service_network_request_timeout_millis";
49 
50     // Default timeout used to call ConnectivityManager.requestNetwork if the
51     // MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS flag is not set.
52     // Given that the telephony layer will retry on failures, this timeout should be high enough.
53     private static final int DEFAULT_MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS = 30 * 60 * 1000;
54 
55     // Wait timeout for this class, this is an additional delay after waiting the network request
56     // timeout to make sure we don't bail prematurely.
57     private static final int ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS = (5 * 1000);
58 
59     /* Event created when receiving ACTION_CARRIER_CONFIG_CHANGED */
60     private static final int EVENT_CARRIER_CONFIG_CHANGED = 1;
61 
62     private final Context mContext;
63 
64     // The requested MMS {@link android.net.Network} we are holding
65     // We need this when we unbind from it. This is also used to indicate if the
66     // MMS network is available.
67     private Network mNetwork;
68     // The current count of MMS requests that require the MMS network
69     // If mMmsRequestCount is 0, we should release the MMS network.
70     private int mMmsRequestCount;
71     // This is really just for using the capability
72     private final NetworkRequest mNetworkRequest;
73     // The callback to register when we request MMS network
74     private ConnectivityManager.NetworkCallback mNetworkCallback;
75 
76     private volatile ConnectivityManager mConnectivityManager;
77 
78     // The MMS HTTP client for this network
79     private MmsHttpClient mMmsHttpClient;
80 
81     // The handler used for delayed release of the network
82     private final Handler mReleaseHandler;
83 
84     // The task that does the delayed releasing of the network.
85     private final Runnable mNetworkReleaseTask;
86 
87     // The SIM ID which we use to connect
88     private final int mSubId;
89 
90     // The current Phone ID for this MmsNetworkManager
91     private int mPhoneId;
92 
93     // If ACTION_SIM_CARD_STATE_CHANGED intent receiver is registered
94     private boolean mSimCardStateChangedReceiverRegistered;
95 
96     private final Dependencies mDeps;
97 
98     private int mNetworkReleaseTimeoutMillis;
99 
100     // satellite transport status of associated mms active network
101     private boolean  mIsSatelliteTransport;
102 
103     private EventHandler mEventHandler;
104 
105     private final class EventHandler extends Handler {
EventHandler()106         EventHandler() {
107             super(Looper.getMainLooper());
108         }
109 
110         /**
111          * Handles events coming from the phone stack. Overridden from handler.
112          *
113          * @param msg the message to handle
114          */
115         @Override
handleMessage(Message msg)116         public void handleMessage(Message msg) {
117             switch (msg.what) {
118                 case EVENT_CARRIER_CONFIG_CHANGED:
119                     // Reload mNetworkReleaseTimeoutMillis from CarrierConfigManager.
120                     handleCarrierConfigChanged();
121                     break;
122                 default:
123                     LogUtil.e("MmsNetworkManager: ignoring message of unexpected type " + msg.what);
124             }
125         }
126     }
127 
128     /**
129      * This receiver listens to ACTION_SIM_CARD_STATE_CHANGED after starting a new NetworkRequest.
130      * If ACTION_SIM_CARD_STATE_CHANGED with SIM_STATE_ABSENT for a SIM card corresponding to the
131      * current NetworkRequest is received, it just releases the NetworkRequest without waiting for
132      * timeout.
133      */
134     private final BroadcastReceiver mSimCardStateChangedReceiver =
135             new BroadcastReceiver() {
136                 @Override
137                 public void onReceive(Context context, Intent intent) {
138                     final int simState =
139                             intent.getIntExtra(
140                                     TelephonyManager.EXTRA_SIM_STATE,
141                                     TelephonyManager.SIM_STATE_UNKNOWN);
142                     final int phoneId =
143                             intent.getIntExtra(
144                                     PhoneConstants.PHONE_KEY,
145                                     SubscriptionManager.INVALID_PHONE_INDEX);
146                     LogUtil.i("MmsNetworkManager: received ACTION_SIM_CARD_STATE_CHANGED"
147                             + ", state=" + simStateString(simState) + ", phoneId=" + phoneId);
148 
149                     if (mPhoneId == phoneId && simState == TelephonyManager.SIM_STATE_ABSENT) {
150                         synchronized (MmsNetworkManager.this) {
151                             releaseRequestLocked(mNetworkCallback);
152                             MmsNetworkManager.this.notifyAll();
153                         }
154                     }
155                 }
156             };
157 
simStateString(int state)158     private static String simStateString(int state) {
159         switch (state) {
160             case TelephonyManager.SIM_STATE_UNKNOWN:
161                 return "UNKNOWN";
162             case TelephonyManager.SIM_STATE_ABSENT:
163                 return "ABSENT";
164             case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
165                 return "CARD_IO_ERROR";
166             case TelephonyManager.SIM_STATE_CARD_RESTRICTED:
167                 return "CARD_RESTRICTED";
168             case TelephonyManager.SIM_STATE_PRESENT:
169                 return "PRESENT";
170             default:
171                 return "INVALID";
172         }
173     }
174 
175     /**
176      * This receiver listens to ACTION_CARRIER_CONFIG_CHANGED. Whenever receiving this event,
177      * mNetworkReleaseTimeoutMillis needs to be reloaded from CarrierConfigManager.
178      */
179     private final BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
180         @Override
181         public void onReceive(Context context, Intent intent) {
182             final String action = intent.getAction();
183             if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
184                     && mSubId == intent.getIntExtra(
185                             CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
186                             SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
187                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
188                         EVENT_CARRIER_CONFIG_CHANGED));
189             }
190         }
191     };
192 
handleCarrierConfigChanged()193     private void handleCarrierConfigChanged() {
194         final CarrierConfigManager configManager =
195                 (CarrierConfigManager)
196                         mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
197         final PersistableBundle config = configManager.getConfigForSubId(mSubId);
198         mNetworkReleaseTimeoutMillis =
199                 config.getInt(CarrierConfigManager.KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT);
200         LogUtil.d("MmsNetworkManager: handleCarrierConfigChanged() mNetworkReleaseTimeoutMillis "
201                 + mNetworkReleaseTimeoutMillis);
202     }
203 
204     /**
205      * Network callback for our network request
206      */
207     private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback {
208         @Override
onLost(Network network)209         public void onLost(Network network) {
210             super.onLost(network);
211             LogUtil.w("NetworkCallbackListener.onLost: network=" + network);
212             synchronized (MmsNetworkManager.this) {
213                 // Wait for other available network. Not notify.
214                 if (network.equals(mNetwork)) {
215                     mNetwork = null;
216                     mMmsHttpClient = null;
217                 }
218             }
219         }
220 
221         @Override
onUnavailable()222         public void onUnavailable() {
223             super.onUnavailable();
224             LogUtil.w("NetworkCallbackListener.onUnavailable");
225             synchronized (MmsNetworkManager.this) {
226                 releaseRequestLocked(this);
227                 MmsNetworkManager.this.notifyAll();
228             }
229         }
230 
231         @Override
onCapabilitiesChanged(Network network, NetworkCapabilities nc)232         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
233             // onAvailable will always immediately be followed by a onCapabilitiesChanged. Check
234             // network status here is enough.
235             super.onCapabilitiesChanged(network, nc);
236             LogUtil.w("NetworkCallbackListener.onCapabilitiesChanged: network="
237                     + network + ", nc=" + nc);
238             synchronized (MmsNetworkManager.this) {
239                 final boolean isAvailable =
240                         nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
241                 if (network.equals(mNetwork) && !isAvailable) {
242                     // Current network becomes suspended.
243                     mNetwork = null;
244                     mMmsHttpClient = null;
245                     // Not notify. Either wait for other available network or current network to
246                     // become available again.
247                     return;
248                 }
249 
250                 // New available network
251                 if (mNetwork == null && isAvailable) {
252                     mIsSatelliteTransport = nc.hasTransport(
253                             NetworkCapabilities.TRANSPORT_SATELLITE);
254                     mNetwork = network;
255                     MmsNetworkManager.this.notifyAll();
256                 }
257             }
258         }
259     }
260 
261     /**
262      * Dependencies of MmsNetworkManager, for injection in tests.
263      */
264     @VisibleForTesting
265     public static class Dependencies {
266         /** Get phone Id from the given subId */
getPhoneId(int subId)267         public int getPhoneId(int subId) {
268             return SubscriptionManager.getPhoneId(subId);
269         }
270 
271         // Timeout used to call ConnectivityManager.requestNetwork. Given that the telephony layer
272         // will retry on failures, this timeout should be high enough.
getNetworkRequestTimeoutMillis()273         public int getNetworkRequestTimeoutMillis() {
274             return DeviceConfig.getInt(
275                     DeviceConfig.NAMESPACE_TELEPHONY, MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS,
276                     DEFAULT_MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS);
277         }
278 
getAdditionalNetworkAcquireTimeoutMillis()279         public int getAdditionalNetworkAcquireTimeoutMillis() {
280             return ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS;
281         }
282     }
283 
284     @VisibleForTesting
MmsNetworkManager(Context context, int subId, Dependencies dependencies)285     protected MmsNetworkManager(Context context, int subId, Dependencies dependencies) {
286         mContext = context;
287         mDeps = dependencies;
288         mNetworkCallback = null;
289         mNetwork = null;
290         mMmsRequestCount = 0;
291         mConnectivityManager = null;
292         mMmsHttpClient = null;
293         mSubId = subId;
294         mReleaseHandler = new Handler(Looper.getMainLooper());
295 
296         NetworkRequest.Builder builder = new NetworkRequest.Builder()
297                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
298                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
299                 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
300                         .setSubscriptionId(mSubId).build());
301 
302         // With Satellite internet support, add satellite transport with restricted capability to
303         // support mms over satellite network
304         builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
305         try {
306             // TODO: b/331622062 remove the try/catch
307             builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
308             builder.removeCapability(NetworkCapabilities
309                     .NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
310         } catch (IllegalArgumentException exception) {
311             LogUtil.e("TRANSPORT_SATELLITE or NOT_BANDWIDTH_CONSTRAINED is not supported.");
312         }
313         mNetworkRequest = builder.build();
314 
315         mNetworkReleaseTask = new Runnable() {
316             @Override
317             public void run() {
318                 synchronized (this) {
319                     if (mMmsRequestCount < 1) {
320                         releaseRequestLocked(mNetworkCallback);
321                     }
322                 }
323             }
324         };
325 
326         mEventHandler = new EventHandler();
327         // Register a receiver to listen to ACTION_CARRIER_CONFIG_CHANGED
328         mContext.registerReceiver(
329                 mCarrierConfigChangedReceiver,
330                 new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
331         handleCarrierConfigChanged();
332     }
333 
MmsNetworkManager(Context context, int subId)334     public MmsNetworkManager(Context context, int subId) {
335         this(context, subId, new Dependencies());
336     }
337 
338     /**
339      * Acquire the MMS network
340      *
341      * @param requestId request ID for logging
342      * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
343      * @return The net Id of the acquired network.
344      */
acquireNetwork(final String requestId)345     public int acquireNetwork(final String requestId) throws MmsNetworkException {
346         int networkRequestTimeoutMillis = mDeps.getNetworkRequestTimeoutMillis();
347 
348         synchronized (this) {
349             // Since we are acquiring the network, remove the network release task if exists.
350             mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
351             mMmsRequestCount += 1;
352             if (mNetwork != null) {
353                 // Already available
354                 LogUtil.d(requestId, "MmsNetworkManager: already available");
355                 return mNetwork.getNetId();
356             }
357 
358             if (!mSimCardStateChangedReceiverRegistered) {
359                 mPhoneId = mDeps.getPhoneId(mSubId);
360                 if (mPhoneId == SubscriptionManager.INVALID_PHONE_INDEX
361                         || mPhoneId == SubscriptionManager.DEFAULT_PHONE_INDEX) {
362                     throw new MmsNetworkException("Invalid Phone Id: " + mPhoneId);
363                 }
364 
365                 // Register a receiver to listen to ACTION_SIM_CARD_STATE_CHANGED
366                 mContext.registerReceiver(
367                         mSimCardStateChangedReceiver,
368                         new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED));
369                 mSimCardStateChangedReceiverRegistered = true;
370             }
371 
372             // Not available, so start a new request if not done yet
373             if (mNetworkCallback == null) {
374                 LogUtil.d(requestId, "MmsNetworkManager: start new network request");
375                 startNewNetworkRequestLocked(networkRequestTimeoutMillis);
376             }
377 
378             try {
379                 this.wait(networkRequestTimeoutMillis
380                         + mDeps.getAdditionalNetworkAcquireTimeoutMillis());
381             } catch (InterruptedException e) {
382                 LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted");
383             }
384 
385             if (mSimCardStateChangedReceiverRegistered) {
386                 // Unregister the receiver.
387                 mContext.unregisterReceiver(mSimCardStateChangedReceiver);
388                 mSimCardStateChangedReceiverRegistered = false;
389             }
390 
391             if (mNetwork != null) {
392                 // Success
393                 return mNetwork.getNetId();
394             }
395 
396             if (mNetworkCallback != null) { // Timed out
397                 LogUtil.e(requestId,
398                         "MmsNetworkManager: timed out with networkRequestTimeoutMillis="
399                                 + networkRequestTimeoutMillis
400                                 + " and ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS="
401                                 + mDeps.getAdditionalNetworkAcquireTimeoutMillis());
402                 // Release the network request and wake up all the MmsRequests for fast-fail
403                 // together.
404                 // TODO: Start new network request for remaining MmsRequests?
405                 releaseRequestLocked(mNetworkCallback);
406                 this.notifyAll();
407             }
408 
409             throw new MmsNetworkException("Acquiring network failed");
410         }
411     }
412 
413     /**
414      * Release the MMS network when nobody is holding on to it.
415      *
416      * @param requestId          request ID for logging.
417      * @param shouldDelayRelease whether the release should be delayed for a carrier-configured
418      *                           timeout (default 5 seconds), the regular use case is to delay this
419      *                           for DownloadRequests to use the network for sending an
420      *                           acknowledgement on the same network.
421      */
releaseNetwork(final String requestId, final boolean shouldDelayRelease)422     public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) {
423         synchronized (this) {
424             if (mMmsRequestCount > 0) {
425                 mMmsRequestCount -= 1;
426                 LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount);
427                 if (mMmsRequestCount < 1) {
428                     if (shouldDelayRelease) {
429                         // remove previously posted task and post a delayed task on the release
430                         // handler to release the network
431                         mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
432                         mReleaseHandler.postDelayed(mNetworkReleaseTask,
433                                 mNetworkReleaseTimeoutMillis);
434                     } else {
435                         releaseRequestLocked(mNetworkCallback);
436                     }
437                 }
438             }
439         }
440     }
441 
442     /**
443      * Start a new {@link android.net.NetworkRequest} for MMS
444      */
startNewNetworkRequestLocked(int networkRequestTimeoutMillis)445     private void startNewNetworkRequestLocked(int networkRequestTimeoutMillis) {
446         final ConnectivityManager connectivityManager = getConnectivityManager();
447         mNetworkCallback = new NetworkRequestCallback();
448         connectivityManager.requestNetwork(
449                 mNetworkRequest, mNetworkCallback, networkRequestTimeoutMillis);
450     }
451 
452     /**
453      * Release the current {@link android.net.NetworkRequest} for MMS
454      *
455      * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
456      */
releaseRequestLocked(ConnectivityManager.NetworkCallback callback)457     private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
458         if (callback != null) {
459             final ConnectivityManager connectivityManager = getConnectivityManager();
460             try {
461                 connectivityManager.unregisterNetworkCallback(callback);
462             } catch (IllegalArgumentException e) {
463                 // It is possible ConnectivityManager.requestNetwork may fail silently due
464                 // to RemoteException. When that happens, we may get an invalid
465                 // NetworkCallback, which causes an IllegalArgumentexception when we try to
466                 // unregisterNetworkCallback. This exception in turn causes
467                 // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service
468                 // would get stuck in the bad state until the device restarts. This fix
469                 // catches the exception so that state clean up can be executed.
470                 LogUtil.w("Unregister network callback exception", e);
471             }
472         }
473         resetLocked();
474     }
475 
476     /**
477      * Reset the state
478      */
resetLocked()479     private void resetLocked() {
480         mNetworkCallback = null;
481         mNetwork = null;
482         mMmsRequestCount = 0;
483         mMmsHttpClient = null;
484     }
485 
getConnectivityManager()486     private @NonNull ConnectivityManager getConnectivityManager() {
487         if (mConnectivityManager == null) {
488             mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
489                     Context.CONNECTIVITY_SERVICE);
490         }
491         return mConnectivityManager;
492     }
493 
494     /**
495      * Get an MmsHttpClient for the current network
496      *
497      * @return The MmsHttpClient instance
498      */
getOrCreateHttpClient()499     public MmsHttpClient getOrCreateHttpClient() {
500         synchronized (this) {
501             if (mMmsHttpClient == null) {
502                 if (mNetwork != null) {
503                     // Create new MmsHttpClient for the current Network
504                     mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager);
505                 }
506             }
507             return mMmsHttpClient;
508         }
509     }
510 
511     /**
512      * Get the APN name for the active network
513      *
514      * @return The APN name if available, otherwise null
515      */
getApnName()516     public String getApnName() {
517         Network network = null;
518         synchronized (this) {
519             if (mNetwork == null) {
520                 return null;
521             }
522             network = mNetwork;
523         }
524         String apnName = null;
525         final ConnectivityManager connectivityManager = getConnectivityManager();
526         final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network);
527         if (mmsNetworkInfo != null) {
528             apnName = mmsNetworkInfo.getExtraInfo();
529         }
530         return apnName;
531     }
532 
533     @VisibleForTesting
getNetworkReleaseTimeoutMillis()534     protected int getNetworkReleaseTimeoutMillis() {
535         return mNetworkReleaseTimeoutMillis;
536     }
537 
538     /**
539      * Indicates satellite transport status for active network
540      *
541      * @return {@code true} if satellite transport, otherwise {@code false}
542      */
isSatelliteTransport()543     public boolean isSatelliteTransport() {
544         LogUtil.w("satellite transport status: " + mIsSatelliteTransport);
545         return mIsSatelliteTransport;
546     }
547 
548 }
549