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