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