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