| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.mms.service; |
| |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.Network; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkInfo; |
| import android.net.NetworkRequest; |
| import android.os.Handler; |
| import android.os.Looper; |
| |
| import com.android.mms.service.exception.MmsNetworkException; |
| |
| /** |
| * Manages the MMS network connectivity |
| */ |
| public class MmsNetworkManager { |
| // Timeout used to call ConnectivityManager.requestNetwork |
| // Given that the telephony layer will retry on failures, this timeout should be high enough. |
| private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 30 * 60 * 1000; |
| // Wait timeout for this class, a little bit longer than the above timeout |
| // to make sure we don't bail prematurely |
| private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS = |
| NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000); |
| // Waiting time used before releasing a network prematurely. This allows the MMS download |
| // acknowledgement messages to be sent using the same network that was used to download the data |
| private static final int NETWORK_RELEASE_TIMEOUT_MILLIS = 5 * 1000; |
| |
| private final Context mContext; |
| |
| // The requested MMS {@link android.net.Network} we are holding |
| // We need this when we unbind from it. This is also used to indicate if the |
| // MMS network is available. |
| private Network mNetwork; |
| // The current count of MMS requests that require the MMS network |
| // If mMmsRequestCount is 0, we should release the MMS network. |
| private int mMmsRequestCount; |
| // This is really just for using the capability |
| private final NetworkRequest mNetworkRequest; |
| // The callback to register when we request MMS network |
| private ConnectivityManager.NetworkCallback mNetworkCallback; |
| |
| private volatile ConnectivityManager mConnectivityManager; |
| |
| // The MMS HTTP client for this network |
| private MmsHttpClient mMmsHttpClient; |
| |
| // The handler used for delayed release of the network |
| private final Handler mReleaseHandler; |
| |
| // The task that does the delayed releasing of the network. |
| private final Runnable mNetworkReleaseTask; |
| |
| // The SIM ID which we use to connect |
| private final int mSubId; |
| |
| /** |
| * Network callback for our network request |
| */ |
| private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback { |
| @Override |
| public void onAvailable(Network network) { |
| super.onAvailable(network); |
| LogUtil.i("NetworkCallbackListener.onAvailable: network=" + network); |
| synchronized (MmsNetworkManager.this) { |
| mNetwork = network; |
| MmsNetworkManager.this.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void onLost(Network network) { |
| super.onLost(network); |
| LogUtil.w("NetworkCallbackListener.onLost: network=" + network); |
| synchronized (MmsNetworkManager.this) { |
| releaseRequestLocked(this); |
| MmsNetworkManager.this.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void onUnavailable() { |
| super.onUnavailable(); |
| LogUtil.w("NetworkCallbackListener.onUnavailable"); |
| synchronized (MmsNetworkManager.this) { |
| releaseRequestLocked(this); |
| MmsNetworkManager.this.notifyAll(); |
| } |
| } |
| } |
| |
| public MmsNetworkManager(Context context, int subId) { |
| mContext = context; |
| mNetworkCallback = null; |
| mNetwork = null; |
| mMmsRequestCount = 0; |
| mConnectivityManager = null; |
| mMmsHttpClient = null; |
| mSubId = subId; |
| mReleaseHandler = new Handler(Looper.getMainLooper()); |
| mNetworkRequest = new NetworkRequest.Builder() |
| .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) |
| .setNetworkSpecifier(Integer.toString(mSubId)) |
| .build(); |
| |
| mNetworkReleaseTask = new Runnable() { |
| @Override |
| public void run() { |
| synchronized (this) { |
| if (mMmsRequestCount < 1) { |
| releaseRequestLocked(mNetworkCallback); |
| } |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Acquire the MMS network |
| * |
| * @param requestId request ID for logging |
| * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it |
| */ |
| public void acquireNetwork(final String requestId) throws MmsNetworkException { |
| synchronized (this) { |
| // Since we are acquiring the network, remove the network release task if exists. |
| mReleaseHandler.removeCallbacks(mNetworkReleaseTask); |
| mMmsRequestCount += 1; |
| if (mNetwork != null) { |
| // Already available |
| LogUtil.d(requestId, "MmsNetworkManager: already available"); |
| return; |
| } |
| // Not available, so start a new request if not done yet |
| if (mNetworkCallback == null) { |
| LogUtil.d(requestId, "MmsNetworkManager: start new network request"); |
| startNewNetworkRequestLocked(); |
| } |
| try { |
| this.wait(NETWORK_ACQUIRE_TIMEOUT_MILLIS); |
| } catch (InterruptedException e) { |
| LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted"); |
| } |
| if (mNetwork != null) { |
| // Success |
| return; |
| } |
| |
| // Timed out |
| LogUtil.e(requestId, "MmsNetworkManager: timed out"); |
| if (mNetworkCallback != null) { |
| // Release the network request and wake up all the MmsRequests for fast-fail |
| // together. |
| // TODO: Start new network request for remaining MmsRequests? |
| releaseRequestLocked(mNetworkCallback); |
| this.notifyAll(); |
| } |
| |
| throw new MmsNetworkException("Acquiring network timed out"); |
| } |
| } |
| |
| /** |
| * Release the MMS network when nobody is holding on to it. |
| * |
| * @param requestId request ID for logging |
| * @param shouldDelayRelease whether the release should be delayed for 5 seconds, the regular |
| * use case is to delay this for DownloadRequests to use the network |
| * for sending an acknowledgement on the same network |
| */ |
| public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) { |
| synchronized (this) { |
| if (mMmsRequestCount > 0) { |
| mMmsRequestCount -= 1; |
| LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount); |
| if (mMmsRequestCount < 1) { |
| if (shouldDelayRelease) { |
| // remove previously posted task and post a delayed task on the release |
| // handler to release the network |
| mReleaseHandler.removeCallbacks(mNetworkReleaseTask); |
| mReleaseHandler.postDelayed(mNetworkReleaseTask, |
| NETWORK_RELEASE_TIMEOUT_MILLIS); |
| } else { |
| releaseRequestLocked(mNetworkCallback); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Start a new {@link android.net.NetworkRequest} for MMS |
| */ |
| private void startNewNetworkRequestLocked() { |
| final ConnectivityManager connectivityManager = getConnectivityManager(); |
| mNetworkCallback = new NetworkRequestCallback(); |
| connectivityManager.requestNetwork( |
| mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS); |
| } |
| |
| /** |
| * Release the current {@link android.net.NetworkRequest} for MMS |
| * |
| * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister |
| */ |
| private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) { |
| if (callback != null) { |
| final ConnectivityManager connectivityManager = getConnectivityManager(); |
| try { |
| connectivityManager.unregisterNetworkCallback(callback); |
| } catch (IllegalArgumentException e) { |
| // It is possible ConnectivityManager.requestNetwork may fail silently due |
| // to RemoteException. When that happens, we may get an invalid |
| // NetworkCallback, which causes an IllegalArgumentexception when we try to |
| // unregisterNetworkCallback. This exception in turn causes |
| // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service |
| // would get stuck in the bad state until the device restarts. This fix |
| // catches the exception so that state clean up can be executed. |
| LogUtil.w("Unregister network callback exception", e); |
| } |
| } |
| resetLocked(); |
| } |
| |
| /** |
| * Reset the state |
| */ |
| private void resetLocked() { |
| mNetworkCallback = null; |
| mNetwork = null; |
| mMmsRequestCount = 0; |
| mMmsHttpClient = null; |
| } |
| |
| private ConnectivityManager getConnectivityManager() { |
| if (mConnectivityManager == null) { |
| mConnectivityManager = (ConnectivityManager) mContext.getSystemService( |
| Context.CONNECTIVITY_SERVICE); |
| } |
| return mConnectivityManager; |
| } |
| |
| /** |
| * Get an MmsHttpClient for the current network |
| * |
| * @return The MmsHttpClient instance |
| */ |
| public MmsHttpClient getOrCreateHttpClient() { |
| synchronized (this) { |
| if (mMmsHttpClient == null) { |
| if (mNetwork != null) { |
| // Create new MmsHttpClient for the current Network |
| mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager); |
| } |
| } |
| return mMmsHttpClient; |
| } |
| } |
| |
| /** |
| * Get the APN name for the active network |
| * |
| * @return The APN name if available, otherwise null |
| */ |
| public String getApnName() { |
| Network network = null; |
| synchronized (this) { |
| if (mNetwork == null) { |
| return null; |
| } |
| network = mNetwork; |
| } |
| String apnName = null; |
| final ConnectivityManager connectivityManager = getConnectivityManager(); |
| final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network); |
| if (mmsNetworkInfo != null) { |
| apnName = mmsNetworkInfo.getExtraInfo(); |
| } |
| return apnName; |
| } |
| } |