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.Context; 20 import android.net.ConnectivityManager; 21 import android.net.Network; 22 import android.net.NetworkCapabilities; 23 import android.net.NetworkInfo; 24 import android.net.NetworkRequest; 25 import android.os.SystemClock; 26 27 import com.android.mms.service.exception.MmsNetworkException; 28 29 /** 30 * Manages the MMS network connectivity 31 */ 32 public class MmsNetworkManager { 33 // Timeout used to call ConnectivityManager.requestNetwork 34 private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000; 35 // Wait timeout for this class, a little bit longer than the above timeout 36 // to make sure we don't bail prematurely 37 private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS = 38 NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000); 39 40 private final Context mContext; 41 42 // The requested MMS {@link android.net.Network} we are holding 43 // We need this when we unbind from it. This is also used to indicate if the 44 // MMS network is available. 45 private Network mNetwork; 46 // The current count of MMS requests that require the MMS network 47 // If mMmsRequestCount is 0, we should release the MMS network. 48 private int mMmsRequestCount; 49 // This is really just for using the capability 50 private final NetworkRequest mNetworkRequest; 51 // The callback to register when we request MMS network 52 private ConnectivityManager.NetworkCallback mNetworkCallback; 53 54 private volatile ConnectivityManager mConnectivityManager; 55 56 // The MMS HTTP client for this network 57 private MmsHttpClient mMmsHttpClient; 58 59 // The SIM ID which we use to connect 60 private final int mSubId; 61 62 /** 63 * Network callback for our network request 64 */ 65 private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback { 66 @Override onAvailable(Network network)67 public void onAvailable(Network network) { 68 super.onAvailable(network); 69 LogUtil.i("NetworkCallbackListener.onAvailable: network=" + network); 70 synchronized (MmsNetworkManager.this) { 71 mNetwork = network; 72 MmsNetworkManager.this.notifyAll(); 73 } 74 } 75 76 @Override onLost(Network network)77 public void onLost(Network network) { 78 super.onLost(network); 79 LogUtil.w("NetworkCallbackListener.onLost: network=" + network); 80 synchronized (MmsNetworkManager.this) { 81 releaseRequestLocked(this); 82 MmsNetworkManager.this.notifyAll(); 83 } 84 } 85 86 @Override onUnavailable()87 public void onUnavailable() { 88 super.onUnavailable(); 89 LogUtil.w("NetworkCallbackListener.onUnavailable"); 90 synchronized (MmsNetworkManager.this) { 91 releaseRequestLocked(this); 92 MmsNetworkManager.this.notifyAll(); 93 } 94 } 95 } 96 MmsNetworkManager(Context context, int subId)97 public MmsNetworkManager(Context context, int subId) { 98 mContext = context; 99 mNetworkCallback = null; 100 mNetwork = null; 101 mMmsRequestCount = 0; 102 mConnectivityManager = null; 103 mMmsHttpClient = null; 104 mSubId = subId; 105 mNetworkRequest = new NetworkRequest.Builder() 106 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 107 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) 108 .setNetworkSpecifier(Integer.toString(mSubId)) 109 .build(); 110 } 111 112 /** 113 * Acquire the MMS network 114 * 115 * @param requestId request ID for logging 116 * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it 117 */ acquireNetwork(final String requestId)118 public void acquireNetwork(final String requestId) throws MmsNetworkException { 119 synchronized (this) { 120 mMmsRequestCount += 1; 121 if (mNetwork != null) { 122 // Already available 123 LogUtil.d(requestId, "MmsNetworkManager: already available"); 124 return; 125 } 126 // Not available, so start a new request if not done yet 127 if (mNetworkCallback == null) { 128 LogUtil.d(requestId, "MmsNetworkManager: start new network request"); 129 startNewNetworkRequestLocked(); 130 } 131 final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS; 132 long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS; 133 while (waitTime > 0) { 134 try { 135 this.wait(waitTime); 136 } catch (InterruptedException e) { 137 LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted"); 138 } 139 if (mNetwork != null) { 140 // Success 141 return; 142 } 143 // Calculate remaining waiting time to make sure we wait the full timeout period 144 waitTime = shouldEnd - SystemClock.elapsedRealtime(); 145 } 146 // Timed out, so release the request and fail 147 LogUtil.e(requestId, "MmsNetworkManager: timed out"); 148 releaseRequestLocked(mNetworkCallback); 149 throw new MmsNetworkException("Acquiring network timed out"); 150 } 151 } 152 153 /** 154 * Release the MMS network when nobody is holding on to it. 155 * 156 * @param requestId request ID for logging 157 */ releaseNetwork(final String requestId)158 public void releaseNetwork(final String requestId) { 159 synchronized (this) { 160 if (mMmsRequestCount > 0) { 161 mMmsRequestCount -= 1; 162 LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount); 163 if (mMmsRequestCount < 1) { 164 releaseRequestLocked(mNetworkCallback); 165 } 166 } 167 } 168 } 169 170 /** 171 * Start a new {@link android.net.NetworkRequest} for MMS 172 */ startNewNetworkRequestLocked()173 private void startNewNetworkRequestLocked() { 174 final ConnectivityManager connectivityManager = getConnectivityManager(); 175 mNetworkCallback = new NetworkRequestCallback(); 176 connectivityManager.requestNetwork( 177 mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS); 178 } 179 180 /** 181 * Release the current {@link android.net.NetworkRequest} for MMS 182 * 183 * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister 184 */ releaseRequestLocked(ConnectivityManager.NetworkCallback callback)185 private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) { 186 if (callback != null) { 187 final ConnectivityManager connectivityManager = getConnectivityManager(); 188 try { 189 connectivityManager.unregisterNetworkCallback(callback); 190 } catch (IllegalArgumentException e) { 191 // It is possible ConnectivityManager.requestNetwork may fail silently due 192 // to RemoteException. When that happens, we may get an invalid 193 // NetworkCallback, which causes an IllegalArgumentexception when we try to 194 // unregisterNetworkCallback. This exception in turn causes 195 // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service 196 // would get stuck in the bad state until the device restarts. This fix 197 // catches the exception so that state clean up can be executed. 198 LogUtil.w("Unregister network callback exception", e); 199 } 200 } 201 resetLocked(); 202 } 203 204 /** 205 * Reset the state 206 */ resetLocked()207 private void resetLocked() { 208 mNetworkCallback = null; 209 mNetwork = null; 210 mMmsRequestCount = 0; 211 mMmsHttpClient = null; 212 } 213 getConnectivityManager()214 private ConnectivityManager getConnectivityManager() { 215 if (mConnectivityManager == null) { 216 mConnectivityManager = (ConnectivityManager) mContext.getSystemService( 217 Context.CONNECTIVITY_SERVICE); 218 } 219 return mConnectivityManager; 220 } 221 222 /** 223 * Get an MmsHttpClient for the current network 224 * 225 * @return The MmsHttpClient instance 226 */ getOrCreateHttpClient()227 public MmsHttpClient getOrCreateHttpClient() { 228 synchronized (this) { 229 if (mMmsHttpClient == null) { 230 if (mNetwork != null) { 231 // Create new MmsHttpClient for the current Network 232 mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager); 233 } 234 } 235 return mMmsHttpClient; 236 } 237 } 238 239 /** 240 * Get the APN name for the active network 241 * 242 * @return The APN name if available, otherwise null 243 */ getApnName()244 public String getApnName() { 245 Network network = null; 246 synchronized (this) { 247 if (mNetwork == null) { 248 return null; 249 } 250 network = mNetwork; 251 } 252 String apnName = null; 253 final ConnectivityManager connectivityManager = getConnectivityManager(); 254 final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network); 255 if (mmsNetworkInfo != null) { 256 apnName = mmsNetworkInfo.getExtraInfo(); 257 } 258 return apnName; 259 } 260 } 261