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.omadm.service;
18
19import android.app.Service;
20import android.content.Context;
21import android.content.Intent;
22import android.net.ConnectivityManager;
23import android.net.Network;
24import android.net.NetworkCapabilities;
25import android.net.NetworkInfo;
26import android.net.NetworkRequest;
27import android.os.IBinder;
28import android.util.Log;
29
30/**
31 * This service monitors the data connection and call state and brings up the FOTA APN when
32 * started by an {@link DMIntent#ACTION_START_DATA_CONNECTION_SERVICE} intent.
33 *
34 * TODO: handle operators which disallow OMA DM sessions over Wi-Fi.
35 * TODO: handle mobile data disabled and roaming disabled cases.
36 */
37public class DMDataConnectionService extends Service {
38    private static final String TAG = "DMDataConnectionService";
39    private static final boolean DBG = true;
40
41    private ConnectivityManager mConnectivityManager;
42
43    /** Start ID of current network request. */
44    private int mCurrentStartId;
45
46    /** The active NetworkCallback for the FOTA APN, or null. */
47    private ConnectivityManager.NetworkCallback mCellNetworkCallback;
48
49    /** The active NetworkCallback for WiFi or Ethernet, or null. */
50    private ConnectivityManager.NetworkCallback mWiFiNetworkCallback;
51
52    /** The active FOTA APN network, or null. */
53    private Network mActiveCellNetwork;
54
55    /** The active WiFi or Ethernet network, or null. */
56    private Network mActiveWiFiNetwork;
57
58    /** Wait for 120 seconds after requesting the desired network types. */
59    private static final int WAIT_FOR_NETWORK_TIMEOUT_MS = 120 * 1000;
60
61    @Override
62    public void onCreate() {
63        if (DBG) logd("onCreate()");
64        mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
65    }
66
67    @Override
68    public void onDestroy() {
69        if (DBG) logd("onDestroy()");
70
71        mCurrentStartId = 0;
72
73        if (mCellNetworkCallback != null) {
74            mConnectivityManager.unregisterNetworkCallback(mCellNetworkCallback);
75            if (DBG) logd("unregistered cell network callback");
76            mCellNetworkCallback = null;
77        }
78
79        if (mWiFiNetworkCallback != null) {
80            mConnectivityManager.unregisterNetworkCallback(mWiFiNetworkCallback);
81            if (DBG) logd("unregistered WiFi network callback");
82            mWiFiNetworkCallback = null;
83        }
84        // Unbind from any network.
85        ConnectivityManager.setProcessDefaultNetwork(null);
86    }
87
88    private boolean isWifiConnected() {
89        NetworkInfo ni = mConnectivityManager.getActiveNetworkInfo();
90        return ni != null && ni.isConnected()
91                && (ni.getType() == ConnectivityManager.TYPE_WIFI
92                        || ni.getType() == ConnectivityManager.TYPE_ETHERNET);
93    }
94
95    /**
96     * Request route using FOTA APN on the cell network and/or Wi-Fi or Ethernet transport.
97     * @param wifiOrEthernet true if Wi-Fi transport type is allowed; false for mobile data only
98     */
99    private void requestNetworks(boolean wifiOrEthernet) {
100        if (wifiOrEthernet && isWifiConnected()) {
101            if (DBG) logd("requesting WiFi or Ethernet network transport");
102            NetworkRequest request = new NetworkRequest.Builder()
103                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
104                    .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET).build();
105
106            mWiFiNetworkCallback = new WiFiCallback();
107            mConnectivityManager.requestNetwork(request, mWiFiNetworkCallback,
108                    WAIT_FOR_NETWORK_TIMEOUT_MS);
109        } else {
110            if (DBG) logd("requesting cell network with FOTA capability");
111            NetworkRequest request = new NetworkRequest.Builder()
112                    .addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA).build();
113
114            mCellNetworkCallback = new CellCallback();
115            mConnectivityManager.requestNetwork(request, mCellNetworkCallback,
116                    WAIT_FOR_NETWORK_TIMEOUT_MS);
117        }
118    }
119
120    @Override
121    public int onStartCommand(Intent intent, int flags, int startId) {
122        if (DBG) logd("onStartCommand: startID " + startId);
123
124        if (intent != null) {
125            String action = intent.getAction();
126            if (DMIntent.ACTION_START_DATA_CONNECTION_SERVICE.equals(action)) {
127                int lastStartId = mCurrentStartId;
128                mCurrentStartId = startId;
129
130                if (lastStartId == 0) {
131                    requestNetworks(true);    // request FOTA APN or Wi-Fi/Ethernet networks
132                }
133            } else {
134                if (DBG) loge("unexpected intent: " + action);
135                stopSelf(startId);
136            }
137        } else {
138            if (DBG) logd("unexpected null intent");
139            stopSelf(startId);
140        }
141        return Service.START_REDELIVER_INTENT;
142    }
143
144    private void sendDataConnectionReady() {
145        Intent intent = new Intent(this, DMIntentReceiver.class);
146        intent.setAction(DMIntent.ACTION_DATA_CONNECTION_READY);
147        sendBroadcast(intent);
148    }
149
150    // Callback for FOTA APN of cellular network.
151    private class CellCallback extends ConnectivityManager.NetworkCallback {
152        @Override
153        public void onAvailable(Network network) {
154            if (mCurrentStartId != 0) {
155                if (DBG) logd("CellCallback.onAvailable() for network: " + network);
156                mActiveCellNetwork = network;
157                if (mActiveWiFiNetwork == null) {
158                    if (DBG) logd("calling setProcessDefaultNetwork() for cell network");
159                    ConnectivityManager.setProcessDefaultNetwork(network);
160                }
161                sendDataConnectionReady();
162            } else {
163                if (DBG) loge("CellCallback: ignoring onAvailable() after service quit");
164            }
165        }
166
167        @Override
168        public void onUnavailable() {
169            if (mCurrentStartId != 0) {
170                if (DBG) loge("CellCallback.onUnavailable() called (timeout)");
171                mActiveCellNetwork = null;
172                if (mActiveWiFiNetwork == null) {
173                    if (DBG) logd("clearing setProcessDefaultNetwork() binding");
174                    ConnectivityManager.setProcessDefaultNetwork(null);
175                }
176            } else {
177                if (DBG) loge("CellCallback: ignoring onUnavailable() after service quit");
178            }
179        }
180    }
181
182    // Callback for WiFi connectivity.
183    private class WiFiCallback extends ConnectivityManager.NetworkCallback {
184        @Override
185        public void onAvailable(Network network) {
186            if (mCurrentStartId != 0) {
187                if (DBG) logd("WiFiCallback.onAvailable() for network: " + network);
188                mActiveWiFiNetwork = network;
189                ConnectivityManager.setProcessDefaultNetwork(network);
190                sendDataConnectionReady();
191            } else {
192                if (DBG) loge("WiFiCallback: ignoring onAvailable() after service quit");
193            }
194        }
195
196        @Override
197        public void onUnavailable() {
198            if (mCurrentStartId != 0) {
199                if (DBG) loge("WiFi.onUnavailable() called (timeout)");
200                mActiveWiFiNetwork = null;
201                if (mActiveCellNetwork == null) {
202                    if (DBG) logd("clearing setProcessDefaultNetwork() binding");
203                    ConnectivityManager.setProcessDefaultNetwork(null);
204                    requestNetworks(false);   // request FOTA APN only
205                }
206            } else {
207                if (DBG) loge("WiFiCallback: ignoring onUnavailable() after service quit");
208            }
209        }
210    }
211
212    @Override
213    public IBinder onBind(Intent intent) {
214        return null;
215    }
216
217    private static void logd(String msg) {
218        Log.d(TAG, msg);
219    }
220
221    private static void loge(String msg) {
222        Log.e(TAG, msg);
223    }
224}
225