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