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
17package com.android.mms.service;
18
19import com.android.mms.service.exception.MmsNetworkException;
20import com.android.mms.service.http.NameResolver;
21
22import android.content.Context;
23import android.net.ConnectivityManager;
24import android.net.Network;
25import android.net.NetworkCapabilities;
26import android.net.NetworkRequest;
27import android.os.SystemClock;
28import android.provider.Settings;
29import android.util.Log;
30
31import java.net.InetAddress;
32import java.net.UnknownHostException;
33
34/**
35 * Manages the MMS network connectivity
36 */
37public class MmsNetworkManager implements NameResolver {
38    // Timeout used to call ConnectivityManager.requestNetwork
39    private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
40    // Wait timeout for this class, a little bit longer than the above timeout
41    // to make sure we don't bail prematurely
42    private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS =
43            NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000);
44
45    private Context mContext;
46    // The requested MMS {@link android.net.Network} we are holding
47    // We need this when we unbind from it. This is also used to indicate if the
48    // MMS network is available.
49    private Network mNetwork;
50    // The current count of MMS requests that require the MMS network
51    // If mMmsRequestCount is 0, we should release the MMS network.
52    private int mMmsRequestCount;
53
54    // This is really just for using the capability
55    private NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
56            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
57            .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
58            .build();
59
60    // The callback to register when we request MMS network
61    private ConnectivityManager.NetworkCallback mNetworkCallback;
62
63    private ConnectivityManager mConnectivityManager;
64
65    // TODO: we need to re-architect this when we support MSIM, like maybe one manager for each SIM?
66    public MmsNetworkManager(Context context) {
67        mContext = context;
68        mNetworkCallback = null;
69        mNetwork = null;
70        mMmsRequestCount = 0;
71        mConnectivityManager = null;
72    }
73
74    public Network getNetwork() {
75        synchronized (this) {
76            return mNetwork;
77        }
78    }
79
80    /**
81     * Acquire the MMS network
82     *
83     * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
84     */
85    public void acquireNetwork() throws MmsNetworkException {
86        if (inAirplaneMode()) {
87            // Fast fail airplane mode
88            throw new MmsNetworkException("In airplane mode");
89        }
90        synchronized (this) {
91            mMmsRequestCount += 1;
92            if (mNetwork != null) {
93                // Already available
94                Log.d(MmsService.TAG, "MmsNetworkManager: already available");
95                return;
96            }
97            Log.d(MmsService.TAG, "MmsNetworkManager: start new network request");
98            // Not available, so start a new request
99            newRequest();
100            final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS;
101            long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS;
102            while (waitTime > 0) {
103                try {
104                    this.wait(waitTime);
105                } catch (InterruptedException e) {
106                    Log.w(MmsService.TAG, "MmsNetworkManager: acquire network wait interrupted");
107                }
108                if (mNetwork != null) {
109                    // Success
110                    return;
111                }
112                // Calculate remaining waiting time to make sure we wait the full timeout period
113                waitTime = shouldEnd - SystemClock.elapsedRealtime();
114            }
115            // Timed out, so release the request and fail
116            Log.d(MmsService.TAG, "MmsNetworkManager: timed out");
117            releaseRequest(mNetworkCallback);
118            resetLocked();
119            throw new MmsNetworkException("Acquiring network timed out");
120        }
121    }
122
123    /**
124     * Release the MMS network when nobody is holding on to it.
125     */
126    public void releaseNetwork() {
127        synchronized (this) {
128            if (mMmsRequestCount > 0) {
129                mMmsRequestCount -= 1;
130                Log.d(MmsService.TAG, "MmsNetworkManager: release, count=" + mMmsRequestCount);
131                if (mMmsRequestCount < 1) {
132                    releaseRequest(mNetworkCallback);
133                    resetLocked();
134                }
135            }
136        }
137    }
138
139    /**
140     * Start a new {@link android.net.NetworkRequest} for MMS
141     */
142    private void newRequest() {
143        final ConnectivityManager connectivityManager = getConnectivityManager();
144        mNetworkCallback = new ConnectivityManager.NetworkCallback() {
145            @Override
146            public void onAvailable(Network network) {
147                super.onAvailable(network);
148                Log.d(MmsService.TAG, "NetworkCallbackListener.onAvailable: network=" + network);
149                synchronized (MmsNetworkManager.this) {
150                    mNetwork = network;
151                    MmsNetworkManager.this.notifyAll();
152                }
153            }
154
155            @Override
156            public void onLost(Network network) {
157                super.onLost(network);
158                Log.d(MmsService.TAG, "NetworkCallbackListener.onLost: network=" + network);
159                synchronized (MmsNetworkManager.this) {
160                    releaseRequest(this);
161                    if (mNetworkCallback == this) {
162                        resetLocked();
163                    }
164                    MmsNetworkManager.this.notifyAll();
165                }
166            }
167
168            @Override
169            public void onUnavailable() {
170                super.onUnavailable();
171                Log.d(MmsService.TAG, "NetworkCallbackListener.onUnavailable");
172                synchronized (MmsNetworkManager.this) {
173                    releaseRequest(this);
174                    if (mNetworkCallback == this) {
175                        resetLocked();
176                    }
177                    MmsNetworkManager.this.notifyAll();
178                }
179            }
180        };
181        connectivityManager.requestNetwork(
182                mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
183    }
184
185    /**
186     * Release the current {@link android.net.NetworkRequest} for MMS
187     *
188     * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
189     */
190    private void releaseRequest(ConnectivityManager.NetworkCallback callback) {
191        if (callback != null) {
192            final ConnectivityManager connectivityManager = getConnectivityManager();
193            connectivityManager.unregisterNetworkCallback(callback);
194        }
195    }
196
197    /**
198     * Reset the state
199     */
200    private void resetLocked() {
201        mNetworkCallback = null;
202        mNetwork = null;
203        mMmsRequestCount = 0;
204    }
205
206    @Override
207    public InetAddress[] getAllByName(String host) throws UnknownHostException {
208        synchronized (this) {
209            if (mNetwork != null) {
210                return mNetwork.getAllByName(host);
211            }
212            return new InetAddress[0];
213        }
214    }
215
216    private ConnectivityManager getConnectivityManager() {
217        if (mConnectivityManager == null) {
218            mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
219                    Context.CONNECTIVITY_SERVICE);
220        }
221        return mConnectivityManager;
222    }
223
224    private boolean inAirplaneMode() {
225        return Settings.System.getInt(mContext.getContentResolver(),
226                Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
227    }
228}
229