1 /*
2  * Copyright (C) 2008 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.settings;
18 
19 import android.app.Activity;
20 import android.app.AlarmManager;
21 import android.app.PendingIntent;
22 import android.app.Service;
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothPan;
25 import android.bluetooth.BluetoothProfile;
26 import android.bluetooth.BluetoothProfile.ServiceListener;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.SharedPreferences;
32 import android.net.ConnectivityManager;
33 import android.os.IBinder;
34 import android.os.ResultReceiver;
35 import android.os.SystemClock;
36 import android.text.TextUtils;
37 import android.util.ArrayMap;
38 import android.util.Log;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.settingslib.TetherUtil;
42 
43 import java.util.ArrayList;
44 import java.util.List;
45 
46 public class TetherService extends Service {
47     private static final String TAG = "TetherService";
48     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
49 
50     @VisibleForTesting
51     public static final String EXTRA_RESULT = "EntitlementResult";
52 
53     // Activity results to match the activity provision protocol.
54     // Default to something not ok.
55     private static final int RESULT_DEFAULT = Activity.RESULT_CANCELED;
56     private static final int RESULT_OK = Activity.RESULT_OK;
57 
58     private static final String TETHER_CHOICE = "TETHER_TYPE";
59     private static final int MS_PER_HOUR = 60 * 60 * 1000;
60 
61     private static final String PREFS = "tetherPrefs";
62     private static final String KEY_TETHERS = "currentTethers";
63 
64     private int mCurrentTypeIndex;
65     private boolean mInProvisionCheck;
66     private ArrayList<Integer> mCurrentTethers;
67     private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
68 
69     @Override
onBind(Intent intent)70     public IBinder onBind(Intent intent) {
71         return null;
72     }
73 
74     @Override
onCreate()75     public void onCreate() {
76         super.onCreate();
77         if (DEBUG) Log.d(TAG, "Creating TetherService");
78         String provisionResponse = getResources().getString(
79                 com.android.internal.R.string.config_mobile_hotspot_provision_response);
80         registerReceiver(mReceiver, new IntentFilter(provisionResponse),
81                 android.Manifest.permission.CONNECTIVITY_INTERNAL, null);
82         SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
83         mCurrentTethers = stringToTethers(prefs.getString(KEY_TETHERS, ""));
84         mCurrentTypeIndex = 0;
85         mPendingCallbacks = new ArrayMap<>(3);
86         mPendingCallbacks.put(ConnectivityManager.TETHERING_WIFI, new ArrayList<ResultReceiver>());
87         mPendingCallbacks.put(ConnectivityManager.TETHERING_USB, new ArrayList<ResultReceiver>());
88         mPendingCallbacks.put(
89                 ConnectivityManager.TETHERING_BLUETOOTH, new ArrayList<ResultReceiver>());
90     }
91 
92     @Override
onStartCommand(Intent intent, int flags, int startId)93     public int onStartCommand(Intent intent, int flags, int startId) {
94         if (intent.hasExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE)) {
95             int type = intent.getIntExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE,
96                     ConnectivityManager.TETHERING_INVALID);
97             ResultReceiver callback =
98                     intent.getParcelableExtra(ConnectivityManager.EXTRA_PROVISION_CALLBACK);
99             if (callback != null) {
100                 List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
101                 if (callbacksForType != null) {
102                     callbacksForType.add(callback);
103                 } else {
104                     // Invalid tether type. Just ignore this request and report failure.
105                     callback.send(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, null);
106                     stopSelf();
107                     return START_NOT_STICKY;
108                 }
109             }
110 
111             if (!mCurrentTethers.contains(type)) {
112                 if (DEBUG) Log.d(TAG, "Adding tether " + type);
113                 mCurrentTethers.add(type);
114             }
115         }
116 
117         if (intent.hasExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE)) {
118             if (!mInProvisionCheck) {
119                 int type = intent.getIntExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE,
120                         ConnectivityManager.TETHERING_INVALID);
121                 int index = mCurrentTethers.indexOf(type);
122                 if (DEBUG) Log.d(TAG, "Removing tether " + type + ", index " + index);
123                 if (index >= 0) {
124                     removeTypeAtIndex(index);
125                 }
126                 cancelAlarmIfNecessary();
127             } else {
128                 if (DEBUG) Log.d(TAG, "Don't cancel alarm during provisioning");
129             }
130         }
131 
132         // Only set the alarm if we have one tether, meaning the one just added,
133         // to avoid setting it when it was already set previously for another
134         // type.
135         if (intent.getBooleanExtra(ConnectivityManager.EXTRA_SET_ALARM, false)
136                 && mCurrentTethers.size() == 1) {
137             scheduleAlarm();
138         }
139 
140         if (intent.getBooleanExtra(ConnectivityManager.EXTRA_RUN_PROVISION, false)) {
141             startProvisioning(mCurrentTypeIndex);
142         } else if (!mInProvisionCheck) {
143             // If we aren't running any provisioning, no reason to stay alive.
144             if (DEBUG) Log.d(TAG, "Stopping self.  startid: " + startId);
145             stopSelf();
146             return START_NOT_STICKY;
147         }
148         // We want to be started if we are killed accidently, so that we can be sure we finish
149         // the check.
150         return START_REDELIVER_INTENT;
151     }
152 
153     @Override
onDestroy()154     public void onDestroy() {
155         if (mInProvisionCheck) {
156             Log.e(TAG, "TetherService getting destroyed while mid-provisioning"
157                     + mCurrentTethers.get(mCurrentTypeIndex));
158         }
159         SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
160         prefs.edit().putString(KEY_TETHERS, tethersToString(mCurrentTethers)).commit();
161 
162         if (DEBUG) Log.d(TAG, "Destroying TetherService");
163         unregisterReceiver(mReceiver);
164         super.onDestroy();
165     }
166 
removeTypeAtIndex(int index)167     private void removeTypeAtIndex(int index) {
168         mCurrentTethers.remove(index);
169         // If we are currently in the middle of a check, we may need to adjust the
170         // index accordingly.
171         if (DEBUG) Log.d(TAG, "mCurrentTypeIndex: " + mCurrentTypeIndex);
172         if (index <= mCurrentTypeIndex && mCurrentTypeIndex > 0) {
173             mCurrentTypeIndex--;
174         }
175     }
176 
stringToTethers(String tethersStr)177     private ArrayList<Integer> stringToTethers(String tethersStr) {
178         ArrayList<Integer> ret = new ArrayList<Integer>();
179         if (TextUtils.isEmpty(tethersStr)) return ret;
180 
181         String[] tethersSplit = tethersStr.split(",");
182         for (int i = 0; i < tethersSplit.length; i++) {
183             ret.add(Integer.parseInt(tethersSplit[i]));
184         }
185         return ret;
186     }
187 
tethersToString(ArrayList<Integer> tethers)188     private String tethersToString(ArrayList<Integer> tethers) {
189         final StringBuffer buffer = new StringBuffer();
190         final int N = tethers.size();
191         for (int i = 0; i < N; i++) {
192             if (i != 0) {
193                 buffer.append(',');
194             }
195             buffer.append(tethers.get(i));
196         }
197 
198         return buffer.toString();
199     }
200 
disableWifiTethering()201     private void disableWifiTethering() {
202         TetherUtil.setWifiTethering(false, this);
203     }
204 
disableUsbTethering()205     private void disableUsbTethering() {
206         ConnectivityManager cm =
207                 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
208         cm.setUsbTethering(false);
209     }
210 
disableBtTethering()211     private void disableBtTethering() {
212         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
213         if (adapter != null) {
214             adapter.getProfileProxy(this, new ServiceListener() {
215                 @Override
216                 public void onServiceDisconnected(int profile) { }
217 
218                 @Override
219                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
220                     ((BluetoothPan) proxy).setBluetoothTethering(false);
221                     adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
222                 }
223             }, BluetoothProfile.PAN);
224         }
225     }
226 
startProvisioning(int index)227     private void startProvisioning(int index) {
228         if (index < mCurrentTethers.size()) {
229             String provisionAction = getResources().getString(
230                     com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
231             if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + provisionAction + " type: "
232                     + mCurrentTethers.get(index));
233             Intent intent = new Intent(provisionAction);
234             int type = mCurrentTethers.get(index);
235             intent.putExtra(TETHER_CHOICE, type);
236             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
237 
238             sendBroadcast(intent);
239             mInProvisionCheck = true;
240         }
241     }
242 
scheduleAlarm()243     private void scheduleAlarm() {
244         Intent intent = new Intent(this, TetherService.class);
245         intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
246 
247         PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
248         AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
249         int period = getResources().getInteger(
250                 com.android.internal.R.integer.config_mobile_hotspot_provision_check_period);
251         long periodMs = period * MS_PER_HOUR;
252         long firstTime = SystemClock.elapsedRealtime() + periodMs;
253         if (DEBUG) Log.d(TAG, "Scheduling alarm at interval " + periodMs);
254         alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstTime, periodMs,
255                 pendingIntent);
256     }
257 
258     /**
259      * Cancels the recheck alarm only if no tethering is currently active.
260      *
261      * Runs in the background, to get access to bluetooth service that takes time to bind.
262      */
cancelRecheckAlarmIfNecessary(final Context context, int type)263     public static void cancelRecheckAlarmIfNecessary(final Context context, int type) {
264         Intent intent = new Intent(context, TetherService.class);
265         intent.putExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE, type);
266         context.startService(intent);
267     }
268 
cancelAlarmIfNecessary()269     private void cancelAlarmIfNecessary() {
270         if (mCurrentTethers.size() != 0) {
271             if (DEBUG) Log.d(TAG, "Tethering still active, not cancelling alarm");
272             return;
273         }
274         Intent intent = new Intent(this, TetherService.class);
275         PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
276         AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
277         alarmManager.cancel(pendingIntent);
278         if (DEBUG) Log.d(TAG, "Tethering no longer active, canceling recheck");
279     }
280 
fireCallbacksForType(int type, int result)281     private void fireCallbacksForType(int type, int result) {
282         List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
283         if (callbacksForType == null) {
284             return;
285         }
286         int errorCode = result == RESULT_OK ? ConnectivityManager.TETHER_ERROR_NO_ERROR :
287                 ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
288         for (ResultReceiver callback : callbacksForType) {
289           if (DEBUG) Log.d(TAG, "Firing result: " + errorCode + " to callback");
290           callback.send(errorCode, null);
291         }
292         callbacksForType.clear();
293     }
294 
295     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
296         @Override
297         public void onReceive(Context context, Intent intent) {
298             if (DEBUG) Log.d(TAG, "Got provision result " + intent);
299             String provisionResponse = getResources().getString(
300                     com.android.internal.R.string.config_mobile_hotspot_provision_response);
301 
302             if (provisionResponse.equals(intent.getAction())) {
303                 if (!mInProvisionCheck) {
304                     Log.e(TAG, "Unexpected provision response " + intent);
305                     return;
306                 }
307                 int checkType = mCurrentTethers.get(mCurrentTypeIndex);
308                 mInProvisionCheck = false;
309                 int result = intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT);
310                 if (result != RESULT_OK) {
311                     switch (checkType) {
312                         case ConnectivityManager.TETHERING_WIFI:
313                             disableWifiTethering();
314                             break;
315                         case ConnectivityManager.TETHERING_BLUETOOTH:
316                             disableBtTethering();
317                             break;
318                         case ConnectivityManager.TETHERING_USB:
319                             disableUsbTethering();
320                             break;
321                     }
322                 }
323                 fireCallbacksForType(checkType, result);
324 
325                 if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
326                     // We are done with all checks, time to die.
327                     stopSelf();
328                 } else {
329                     // Start the next check in our list.
330                     startProvisioning(mCurrentTypeIndex);
331                 }
332             }
333         }
334     };
335 
336 }
337