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