1 /* 2 * Copyright (C) 2016 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 package com.android.car; 17 18 import static android.car.CarProjectionManager.ProjectionAccessPointCallback.ERROR_GENERIC; 19 import static android.car.projection.ProjectionStatus.PROJECTION_STATE_INACTIVE; 20 import static android.car.projection.ProjectionStatus.PROJECTION_STATE_READY_TO_PROJECT; 21 import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE; 22 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON; 23 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; 24 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; 25 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; 26 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; 27 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; 28 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; 29 30 import android.annotation.Nullable; 31 import android.app.ActivityOptions; 32 import android.bluetooth.BluetoothDevice; 33 import android.car.CarProjectionManager; 34 import android.car.CarProjectionManager.ProjectionAccessPointCallback; 35 import android.car.ICarProjection; 36 import android.car.ICarProjectionKeyEventHandler; 37 import android.car.ICarProjectionStatusListener; 38 import android.car.projection.ProjectionOptions; 39 import android.car.projection.ProjectionStatus; 40 import android.car.projection.ProjectionStatus.ProjectionState; 41 import android.content.BroadcastReceiver; 42 import android.content.ComponentName; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.IntentFilter; 46 import android.content.ServiceConnection; 47 import android.content.pm.PackageManager; 48 import android.content.res.Resources; 49 import android.graphics.Rect; 50 import android.net.MacAddress; 51 import android.net.wifi.SoftApConfiguration; 52 import android.net.wifi.WifiClient; 53 import android.net.wifi.WifiManager; 54 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; 55 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation; 56 import android.net.wifi.WifiScanner; 57 import android.os.Binder; 58 import android.os.Bundle; 59 import android.os.Handler; 60 import android.os.HandlerExecutor; 61 import android.os.IBinder; 62 import android.os.Message; 63 import android.os.Messenger; 64 import android.os.RemoteException; 65 import android.os.UserHandle; 66 import android.text.TextUtils; 67 import android.util.IndentingPrintWriter; 68 import android.util.Slog; 69 70 import com.android.car.BinderInterfaceContainer.BinderInterface; 71 import com.android.internal.annotations.GuardedBy; 72 import com.android.internal.util.Preconditions; 73 74 import java.lang.ref.WeakReference; 75 import java.net.NetworkInterface; 76 import java.net.SocketException; 77 import java.util.ArrayList; 78 import java.util.BitSet; 79 import java.util.HashMap; 80 import java.util.List; 81 82 /** 83 * Car projection service allows to bound to projected app to boost it priority. 84 * It also enables projected applications to handle voice action requests. 85 */ 86 class CarProjectionService extends ICarProjection.Stub implements CarServiceBase, 87 BinderInterfaceContainer.BinderEventHandler<ICarProjectionKeyEventHandler>, 88 CarProjectionManager.ProjectionKeyEventHandler { 89 private static final String TAG = CarLog.tagFor(CarProjectionService.class); 90 private static final boolean DBG = true; 91 92 private final CarInputService mCarInputService; 93 private final CarBluetoothService mCarBluetoothService; 94 private final Context mContext; 95 private final WifiManager mWifiManager; 96 private final Handler mHandler; 97 private final Object mLock = new Object(); 98 99 @GuardedBy("mLock") 100 private final HashMap<IBinder, WirelessClient> mWirelessClients = new HashMap<>(); 101 102 @GuardedBy("mLock") 103 private @Nullable LocalOnlyHotspotReservation mLocalOnlyHotspotReservation; 104 105 106 @GuardedBy("mLock") 107 private @Nullable ProjectionSoftApCallback mSoftApCallback; 108 109 @GuardedBy("mLock") 110 private final HashMap<IBinder, ProjectionReceiverClient> mProjectionReceiverClients = 111 new HashMap<>(); 112 113 @Nullable 114 private String mApBssid; 115 116 @GuardedBy("mLock") 117 private @Nullable WifiScanner mWifiScanner; 118 119 @GuardedBy("mLock") 120 private @ProjectionState int mCurrentProjectionState = PROJECTION_STATE_INACTIVE; 121 122 @GuardedBy("mLock") 123 private ProjectionOptions mProjectionOptions; 124 125 @GuardedBy("mLock") 126 private @Nullable String mCurrentProjectionPackage; 127 128 private final BinderInterfaceContainer<ICarProjectionStatusListener> 129 mProjectionStatusListeners = new BinderInterfaceContainer<>(); 130 131 @GuardedBy("mLock") 132 private final ProjectionKeyEventHandlerContainer mKeyEventHandlers; 133 134 private static final int WIFI_MODE_TETHERED = 1; 135 private static final int WIFI_MODE_LOCALONLY = 2; 136 137 // Could be one of the WIFI_MODE_* constants. 138 // TODO: read this from user settings, support runtime switch 139 private int mWifiMode; 140 141 private final ServiceConnection mConnection = new ServiceConnection() { 142 @Override 143 public void onServiceConnected(ComponentName className, IBinder service) { 144 synchronized (mLock) { 145 mBound = true; 146 } 147 } 148 149 @Override 150 public void onServiceDisconnected(ComponentName className) { 151 // Service has crashed. 152 Slog.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className); 153 synchronized (mLock) { 154 mRegisteredService = null; 155 } 156 unbindServiceIfBound(); 157 } 158 }; 159 160 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 161 @Override 162 public void onReceive(Context context, Intent intent) { 163 int currState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED); 164 int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE, 165 WIFI_AP_STATE_DISABLED); 166 int errorCode = intent.getIntExtra(EXTRA_WIFI_AP_FAILURE_REASON, 0); 167 String ifaceName = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME); 168 int mode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, 169 WifiManager.IFACE_IP_MODE_UNSPECIFIED); 170 handleWifiApStateChange(currState, prevState, errorCode, ifaceName, mode); 171 } 172 }; 173 174 private boolean mBound; 175 private Intent mRegisteredService; 176 CarProjectionService(Context context, @Nullable Handler handler, CarInputService carInputService, CarBluetoothService carBluetoothService)177 CarProjectionService(Context context, @Nullable Handler handler, 178 CarInputService carInputService, CarBluetoothService carBluetoothService) { 179 mContext = context; 180 mHandler = handler == null ? new Handler() : handler; 181 mCarInputService = carInputService; 182 mCarBluetoothService = carBluetoothService; 183 mKeyEventHandlers = new ProjectionKeyEventHandlerContainer(this); 184 mWifiManager = context.getSystemService(WifiManager.class); 185 186 final Resources res = mContext.getResources(); 187 setAccessPointTethering(res.getBoolean(R.bool.config_projectionAccessPointTethering)); 188 } 189 190 @Override registerProjectionRunner(Intent serviceIntent)191 public void registerProjectionRunner(Intent serviceIntent) { 192 ICarImpl.assertProjectionPermission(mContext); 193 // We assume one active projection app running in the system at one time. 194 synchronized (mLock) { 195 if (serviceIntent.filterEquals(mRegisteredService) && mBound) { 196 return; 197 } 198 if (mRegisteredService != null) { 199 Slog.w(CarLog.TAG_PROJECTION, "Registering new service[" + serviceIntent 200 + "] while old service[" + mRegisteredService + "] is still running"); 201 } 202 unbindServiceIfBound(); 203 } 204 bindToService(serviceIntent); 205 } 206 207 @Override unregisterProjectionRunner(Intent serviceIntent)208 public void unregisterProjectionRunner(Intent serviceIntent) { 209 ICarImpl.assertProjectionPermission(mContext); 210 synchronized (mLock) { 211 if (!serviceIntent.filterEquals(mRegisteredService)) { 212 Slog.w(CarLog.TAG_PROJECTION, "Request to unbind unregistered service[" 213 + serviceIntent + "]. Registered service[" + mRegisteredService + "]"); 214 return; 215 } 216 mRegisteredService = null; 217 } 218 unbindServiceIfBound(); 219 } 220 bindToService(Intent serviceIntent)221 private void bindToService(Intent serviceIntent) { 222 synchronized (mLock) { 223 mRegisteredService = serviceIntent; 224 } 225 UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid()); 226 mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE, 227 userHandle); 228 } 229 unbindServiceIfBound()230 private void unbindServiceIfBound() { 231 synchronized (mLock) { 232 if (!mBound) { 233 return; 234 } 235 mBound = false; 236 mRegisteredService = null; 237 } 238 mContext.unbindService(mConnection); 239 } 240 241 @Override registerKeyEventHandler( ICarProjectionKeyEventHandler eventHandler, byte[] eventMask)242 public void registerKeyEventHandler( 243 ICarProjectionKeyEventHandler eventHandler, byte[] eventMask) { 244 ICarImpl.assertProjectionPermission(mContext); 245 BitSet events = BitSet.valueOf(eventMask); 246 Preconditions.checkArgument( 247 events.length() <= CarProjectionManager.NUM_KEY_EVENTS, 248 "Unknown handled event"); 249 synchronized (mLock) { 250 ProjectionKeyEventHandler info = mKeyEventHandlers.get(eventHandler); 251 if (info == null) { 252 info = new ProjectionKeyEventHandler(mKeyEventHandlers, eventHandler, events); 253 mKeyEventHandlers.addBinderInterface(info); 254 } else { 255 info.setHandledEvents(events); 256 } 257 258 updateInputServiceHandlerLocked(); 259 } 260 } 261 262 @Override unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler)263 public void unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler) { 264 ICarImpl.assertProjectionPermission(mContext); 265 synchronized (mLock) { 266 mKeyEventHandlers.removeBinder(eventHandler); 267 updateInputServiceHandlerLocked(); 268 } 269 } 270 271 @Override startProjectionAccessPoint(final Messenger messenger, IBinder binder)272 public void startProjectionAccessPoint(final Messenger messenger, IBinder binder) 273 throws RemoteException { 274 ICarImpl.assertProjectionPermission(mContext); 275 //TODO: check if access point already started with the desired configuration. 276 registerWirelessClient(WirelessClient.of(messenger, binder)); 277 startAccessPoint(); 278 } 279 280 @Override stopProjectionAccessPoint(IBinder token)281 public void stopProjectionAccessPoint(IBinder token) { 282 ICarImpl.assertProjectionPermission(mContext); 283 Slog.i(TAG, "Received stop access point request from " + token); 284 285 boolean shouldReleaseAp; 286 synchronized (mLock) { 287 if (!unregisterWirelessClientLocked(token)) { 288 Slog.w(TAG, "Client " + token + " was not registered"); 289 return; 290 } 291 shouldReleaseAp = mWirelessClients.isEmpty(); 292 } 293 294 if (shouldReleaseAp) { 295 stopAccessPoint(); 296 } 297 } 298 299 @Override getAvailableWifiChannels(int band)300 public int[] getAvailableWifiChannels(int band) { 301 ICarImpl.assertProjectionPermission(mContext); 302 WifiScanner scanner; 303 synchronized (mLock) { 304 // Lazy initialization 305 if (mWifiScanner == null) { 306 mWifiScanner = mContext.getSystemService(WifiScanner.class); 307 } 308 scanner = mWifiScanner; 309 } 310 if (scanner == null) { 311 Slog.w(TAG, "Unable to get WifiScanner"); 312 return new int[0]; 313 } 314 315 List<Integer> channels = scanner.getAvailableChannels(band); 316 if (channels == null || channels.isEmpty()) { 317 Slog.w(TAG, "WifiScanner reported no available channels"); 318 return new int[0]; 319 } 320 321 int[] array = new int[channels.size()]; 322 for (int i = 0; i < channels.size(); i++) { 323 array[i] = channels.get(i); 324 } 325 return array; 326 } 327 328 /** 329 * Request to disconnect the given profile on the given device, and prevent it from reconnecting 330 * until either the request is released, or the process owning the given token dies. 331 * 332 * @param device The device on which to inhibit a profile. 333 * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit. 334 * @param token A {@link IBinder} to be used as an identity for the request. If the process 335 * owning the token dies, the request will automatically be released. 336 * @return True if the profile was successfully inhibited, false if an error occurred. 337 */ 338 @Override requestBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)339 public boolean requestBluetoothProfileInhibit( 340 BluetoothDevice device, int profile, IBinder token) { 341 if (DBG) { 342 Slog.d(TAG, "requestBluetoothProfileInhibit device=" + device + " profile=" + profile 343 + " from uid " + Binder.getCallingUid()); 344 } 345 ICarImpl.assertProjectionPermission(mContext); 346 try { 347 if (device == null) { 348 // Will be caught by AIDL and thrown to caller. 349 throw new NullPointerException("Device must not be null"); 350 } 351 if (token == null) { 352 throw new NullPointerException("Token must not be null"); 353 } 354 return mCarBluetoothService.requestProfileInhibit(device, profile, token); 355 } catch (RuntimeException e) { 356 Slog.e(TAG, "Error in requestBluetoothProfileInhibit", e); 357 throw e; 358 } 359 } 360 361 /** 362 * Release an inhibit request made by {@link #requestBluetoothProfileInhibit}, and reconnect the 363 * profile if no other inhibit requests are active. 364 * 365 * @param device The device on which to release the inhibit request. 366 * @param profile The profile on which to release the inhibit request. 367 * @param token The token provided in the original call to 368 * {@link #requestBluetoothProfileInhibit}. 369 * @return True if the request was released, false if an error occurred. 370 */ 371 @Override releaseBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)372 public boolean releaseBluetoothProfileInhibit( 373 BluetoothDevice device, int profile, IBinder token) { 374 if (DBG) { 375 Slog.d(TAG, "releaseBluetoothProfileInhibit device=" + device + " profile=" + profile 376 + " from uid " + Binder.getCallingUid()); 377 } 378 ICarImpl.assertProjectionPermission(mContext); 379 try { 380 if (device == null) { 381 // Will be caught by AIDL and thrown to caller. 382 throw new NullPointerException("Device must not be null"); 383 } 384 if (token == null) { 385 throw new NullPointerException("Token must not be null"); 386 } 387 return mCarBluetoothService.releaseProfileInhibit(device, profile, token); 388 } catch (RuntimeException e) { 389 Slog.e(TAG, "Error in releaseBluetoothProfileInhibit", e); 390 throw e; 391 } 392 } 393 394 @Override updateProjectionStatus(ProjectionStatus status, IBinder token)395 public void updateProjectionStatus(ProjectionStatus status, IBinder token) 396 throws RemoteException { 397 if (DBG) { 398 Slog.d(TAG, "updateProjectionStatus, status: " + status + ", token: " + token); 399 } 400 ICarImpl.assertProjectionPermission(mContext); 401 final String packageName = status.getPackageName(); 402 final int callingUid = Binder.getCallingUid(); 403 final int userHandleId = Binder.getCallingUserHandle().getIdentifier(); 404 final int packageUid; 405 406 try { 407 packageUid = 408 mContext.getPackageManager().getPackageUidAsUser(packageName, userHandleId); 409 } catch (PackageManager.NameNotFoundException e) { 410 throw new SecurityException("Package " + packageName + " does not exist", e); 411 } 412 413 if (callingUid != packageUid) { 414 throw new SecurityException( 415 "UID " + callingUid + " cannot update status for package " + packageName); 416 } 417 418 synchronized (mLock) { 419 ProjectionReceiverClient client = getOrCreateProjectionReceiverClientLocked(token); 420 client.mProjectionStatus = status; 421 422 // If the projection package that's reporting its projection state is the currently 423 // active projection package, update the state. If it is a different package, update the 424 // current projection state if the new package is reporting that it is projecting or if 425 // it is reporting that it's ready to project, and the current package has an inactive 426 // projection state. 427 if (status.isActive() 428 || (status.getState() == PROJECTION_STATE_READY_TO_PROJECT 429 && mCurrentProjectionState == PROJECTION_STATE_INACTIVE) 430 || TextUtils.equals(packageName, mCurrentProjectionPackage)) { 431 mCurrentProjectionState = status.getState(); 432 mCurrentProjectionPackage = packageName; 433 } 434 } 435 notifyProjectionStatusChanged(null /* notify all listeners */); 436 } 437 438 @Override registerProjectionStatusListener(ICarProjectionStatusListener listener)439 public void registerProjectionStatusListener(ICarProjectionStatusListener listener) 440 throws RemoteException { 441 ICarImpl.assertProjectionStatusPermission(mContext); 442 mProjectionStatusListeners.addBinder(listener); 443 444 // Immediately notify listener with the current status. 445 notifyProjectionStatusChanged(listener); 446 } 447 448 @Override unregisterProjectionStatusListener(ICarProjectionStatusListener listener)449 public void unregisterProjectionStatusListener(ICarProjectionStatusListener listener) 450 throws RemoteException { 451 ICarImpl.assertProjectionStatusPermission(mContext); 452 mProjectionStatusListeners.removeBinder(listener); 453 } 454 getOrCreateProjectionReceiverClientLocked( IBinder token)455 private ProjectionReceiverClient getOrCreateProjectionReceiverClientLocked( 456 IBinder token) throws RemoteException { 457 ProjectionReceiverClient client; 458 client = mProjectionReceiverClients.get(token); 459 if (client == null) { 460 client = new ProjectionReceiverClient(() -> unregisterProjectionReceiverClient(token)); 461 token.linkToDeath(client.mDeathRecipient, 0 /* flags */); 462 mProjectionReceiverClients.put(token, client); 463 } 464 return client; 465 } 466 unregisterProjectionReceiverClient(IBinder token)467 private void unregisterProjectionReceiverClient(IBinder token) { 468 synchronized (mLock) { 469 ProjectionReceiverClient client = mProjectionReceiverClients.remove(token); 470 if (client == null) { 471 Slog.w(TAG, "Projection receiver client for token " + token + " doesn't exist"); 472 return; 473 } 474 token.unlinkToDeath(client.mDeathRecipient, 0); 475 if (TextUtils.equals( 476 client.mProjectionStatus.getPackageName(), mCurrentProjectionPackage)) { 477 mCurrentProjectionPackage = null; 478 mCurrentProjectionState = PROJECTION_STATE_INACTIVE; 479 } 480 } 481 } 482 notifyProjectionStatusChanged( @ullable ICarProjectionStatusListener singleListenerToNotify)483 private void notifyProjectionStatusChanged( 484 @Nullable ICarProjectionStatusListener singleListenerToNotify) 485 throws RemoteException { 486 int currentState; 487 String currentPackage; 488 List<ProjectionStatus> statuses = new ArrayList<>(); 489 synchronized (mLock) { 490 for (ProjectionReceiverClient client : mProjectionReceiverClients.values()) { 491 statuses.add(client.mProjectionStatus); 492 } 493 currentState = mCurrentProjectionState; 494 currentPackage = mCurrentProjectionPackage; 495 } 496 497 if (DBG) { 498 Slog.d(TAG, "Notify projection status change, state: " + currentState + ", pkg: " 499 + currentPackage + ", listeners: " + mProjectionStatusListeners.size() 500 + ", listenerToNotify: " + singleListenerToNotify); 501 } 502 503 if (singleListenerToNotify == null) { 504 for (BinderInterface<ICarProjectionStatusListener> listener : 505 mProjectionStatusListeners.getInterfaces()) { 506 try { 507 listener.binderInterface.onProjectionStatusChanged( 508 currentState, currentPackage, statuses); 509 } catch (RemoteException ex) { 510 Slog.e(TAG, "Error calling to projection status listener", ex); 511 } 512 } 513 } else { 514 singleListenerToNotify.onProjectionStatusChanged( 515 currentState, currentPackage, statuses); 516 } 517 } 518 519 @Override getProjectionOptions()520 public Bundle getProjectionOptions() { 521 ICarImpl.assertProjectionPermission(mContext); 522 synchronized (mLock) { 523 if (mProjectionOptions == null) { 524 mProjectionOptions = createProjectionOptionsBuilder() 525 .build(); 526 } 527 } 528 return mProjectionOptions.toBundle(); 529 } 530 createProjectionOptionsBuilder()531 private ProjectionOptions.Builder createProjectionOptionsBuilder() { 532 Resources res = mContext.getResources(); 533 534 ProjectionOptions.Builder builder = ProjectionOptions.builder(); 535 536 ActivityOptions activityOptions = createActivityOptions(res); 537 if (activityOptions != null) { 538 builder.setProjectionActivityOptions(activityOptions); 539 } 540 541 String consentActivity = res.getString(R.string.config_projectionConsentActivity); 542 if (!TextUtils.isEmpty(consentActivity)) { 543 builder.setConsentActivity(ComponentName.unflattenFromString(consentActivity)); 544 } 545 546 builder.setUiMode(res.getInteger(R.integer.config_projectionUiMode)); 547 return builder; 548 } 549 550 @Nullable createActivityOptions(Resources res)551 private static ActivityOptions createActivityOptions(Resources res) { 552 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 553 boolean changed = false; 554 int displayId = res.getInteger(R.integer.config_projectionActivityDisplayId); 555 if (displayId != -1) { 556 activityOptions.setLaunchDisplayId(displayId); 557 changed = true; 558 } 559 int[] rawBounds = res.getIntArray(R.array.config_projectionActivityLaunchBounds); 560 if (rawBounds != null && rawBounds.length == 4) { 561 Rect bounds = new Rect(rawBounds[0], rawBounds[1], rawBounds[2], rawBounds[3]); 562 activityOptions.setLaunchBounds(bounds); 563 changed = true; 564 } 565 return changed ? activityOptions : null; 566 } 567 startAccessPoint()568 private void startAccessPoint() { 569 synchronized (mLock) { 570 switch (mWifiMode) { 571 case WIFI_MODE_LOCALONLY: { 572 startLocalOnlyApLocked(); 573 break; 574 } 575 case WIFI_MODE_TETHERED: { 576 startTetheredApLocked(); 577 break; 578 } 579 default: { 580 Slog.wtf(TAG, "Unexpected Access Point mode during starting: " + mWifiMode); 581 break; 582 } 583 } 584 } 585 } 586 stopAccessPoint()587 private void stopAccessPoint() { 588 sendApStopped(); 589 590 synchronized (mLock) { 591 switch (mWifiMode) { 592 case WIFI_MODE_LOCALONLY: { 593 stopLocalOnlyApLocked(); 594 break; 595 } 596 case WIFI_MODE_TETHERED: { 597 stopTetheredApLocked(); 598 break; 599 } 600 default: { 601 Slog.wtf(TAG, "Unexpected Access Point mode during stopping : " + mWifiMode); 602 } 603 } 604 } 605 } 606 startTetheredApLocked()607 private void startTetheredApLocked() { 608 Slog.d(TAG, "startTetheredApLocked"); 609 610 if (mSoftApCallback == null) { 611 mSoftApCallback = new ProjectionSoftApCallback(); 612 mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback); 613 ensureApConfiguration(); 614 } 615 616 if (!mWifiManager.startTetheredHotspot(null /* use existing config*/)) { 617 // The indicates that AP might be already started. 618 if (mWifiManager.getWifiApState() == WIFI_AP_STATE_ENABLED) { 619 sendApStarted(mWifiManager.getSoftApConfiguration()); 620 } else { 621 Slog.e(TAG, "Failed to start soft AP"); 622 sendApFailed(ERROR_GENERIC); 623 } 624 } 625 } 626 stopTetheredApLocked()627 private void stopTetheredApLocked() { 628 Slog.d(TAG, "stopTetheredAp"); 629 630 if (mSoftApCallback != null) { 631 mWifiManager.unregisterSoftApCallback(mSoftApCallback); 632 mSoftApCallback = null; 633 if (!mWifiManager.stopSoftAp()) { 634 Slog.w(TAG, "Failed to request soft AP to stop."); 635 } 636 } 637 } 638 startLocalOnlyApLocked()639 private void startLocalOnlyApLocked() { 640 if (mLocalOnlyHotspotReservation != null) { 641 Slog.i(TAG, "Local-only hotspot is already registered."); 642 sendApStarted(mLocalOnlyHotspotReservation.getSoftApConfiguration()); 643 return; 644 } 645 646 Slog.i(TAG, "Requesting to start local-only hotspot."); 647 mWifiManager.startLocalOnlyHotspot(new LocalOnlyHotspotCallback() { 648 @Override 649 public void onStarted(LocalOnlyHotspotReservation reservation) { 650 Slog.d(TAG, "Local-only hotspot started"); 651 synchronized (mLock) { 652 mLocalOnlyHotspotReservation = reservation; 653 } 654 sendApStarted(reservation.getSoftApConfiguration()); 655 } 656 657 @Override 658 public void onStopped() { 659 Slog.i(TAG, "Local-only hotspot stopped."); 660 synchronized (mLock) { 661 if (mLocalOnlyHotspotReservation != null) { 662 // We must explicitly released old reservation object, otherwise it may 663 // unexpectedly stop LOHS later because it overrode finalize() method. 664 mLocalOnlyHotspotReservation.close(); 665 } 666 mLocalOnlyHotspotReservation = null; 667 } 668 sendApStopped(); 669 } 670 671 @Override 672 public void onFailed(int localonlyHostspotFailureReason) { 673 Slog.w(TAG, "Local-only hotspot failed, reason: " 674 + localonlyHostspotFailureReason); 675 synchronized (mLock) { 676 mLocalOnlyHotspotReservation = null; 677 } 678 int reason; 679 switch (localonlyHostspotFailureReason) { 680 case LocalOnlyHotspotCallback.ERROR_NO_CHANNEL: 681 reason = ProjectionAccessPointCallback.ERROR_NO_CHANNEL; 682 break; 683 case LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED: 684 reason = ProjectionAccessPointCallback.ERROR_TETHERING_DISALLOWED; 685 break; 686 case LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE: 687 reason = ProjectionAccessPointCallback.ERROR_INCOMPATIBLE_MODE; 688 break; 689 default: 690 reason = ERROR_GENERIC; 691 692 } 693 sendApFailed(reason); 694 } 695 }, mHandler); 696 } 697 stopLocalOnlyApLocked()698 private void stopLocalOnlyApLocked() { 699 Slog.i(TAG, "stopLocalOnlyApLocked"); 700 701 if (mLocalOnlyHotspotReservation == null) { 702 Slog.w(TAG, "Requested to stop local-only hotspot which was already stopped."); 703 return; 704 } 705 706 mLocalOnlyHotspotReservation.close(); 707 mLocalOnlyHotspotReservation = null; 708 } 709 sendApStarted(SoftApConfiguration softApConfiguration)710 private void sendApStarted(SoftApConfiguration softApConfiguration) { 711 SoftApConfiguration localSoftApConfig = 712 new SoftApConfiguration.Builder(softApConfiguration) 713 .setBssid(MacAddress.fromString(mApBssid)) 714 .build(); 715 Message message = Message.obtain(); 716 message.what = CarProjectionManager.PROJECTION_AP_STARTED; 717 message.obj = localSoftApConfig; 718 Slog.i(TAG, "Sending PROJECTION_AP_STARTED, ssid: " 719 + localSoftApConfig.getSsid() 720 + ", apBand: " + localSoftApConfig.getBand() 721 + ", apChannel: " + localSoftApConfig.getChannel() 722 + ", bssid: " + localSoftApConfig.getBssid()); 723 sendApStatusMessage(message); 724 } 725 sendApStopped()726 private void sendApStopped() { 727 Message message = Message.obtain(); 728 message.what = CarProjectionManager.PROJECTION_AP_STOPPED; 729 sendApStatusMessage(message); 730 unregisterWirelessClients(); 731 } 732 sendApFailed(int reason)733 private void sendApFailed(int reason) { 734 Message message = Message.obtain(); 735 message.what = CarProjectionManager.PROJECTION_AP_FAILED; 736 message.arg1 = reason; 737 sendApStatusMessage(message); 738 unregisterWirelessClients(); 739 } 740 sendApStatusMessage(Message message)741 private void sendApStatusMessage(Message message) { 742 List<WirelessClient> clients; 743 synchronized (mLock) { 744 clients = new ArrayList<>(mWirelessClients.values()); 745 } 746 for (WirelessClient client : clients) { 747 client.send(message); 748 } 749 } 750 751 @Override init()752 public void init() { 753 mContext.registerReceiver( 754 mBroadcastReceiver, new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)); 755 } 756 handleWifiApStateChange(int currState, int prevState, int errorCode, String ifaceName, int mode)757 private void handleWifiApStateChange(int currState, int prevState, int errorCode, 758 String ifaceName, int mode) { 759 if (currState == WIFI_AP_STATE_ENABLING || currState == WIFI_AP_STATE_ENABLED) { 760 Slog.d(TAG, 761 "handleWifiApStateChange, curState: " + currState + ", prevState: " + prevState 762 + ", errorCode: " + errorCode + ", ifaceName: " + ifaceName + ", mode: " 763 + mode); 764 765 try { 766 NetworkInterface iface = NetworkInterface.getByName(ifaceName); 767 byte[] bssid = iface.getHardwareAddress(); 768 mApBssid = String.format("%02x:%02x:%02x:%02x:%02x:%02x", 769 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); 770 } catch (SocketException e) { 771 Slog.e(TAG, e.toString(), e); 772 } 773 } 774 } 775 776 @Override release()777 public void release() { 778 synchronized (mLock) { 779 mKeyEventHandlers.clear(); 780 } 781 mContext.unregisterReceiver(mBroadcastReceiver); 782 } 783 784 @Override onBinderDeath( BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> iface)785 public void onBinderDeath( 786 BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> iface) { 787 unregisterKeyEventHandler(iface.binderInterface); 788 } 789 790 @Override dump(IndentingPrintWriter writer)791 public void dump(IndentingPrintWriter writer) { 792 writer.println("**CarProjectionService**"); 793 synchronized (mLock) { 794 writer.println("Registered key event handlers:"); 795 for (BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> 796 handler : mKeyEventHandlers.getInterfaces()) { 797 ProjectionKeyEventHandler 798 projectionKeyEventHandler = (ProjectionKeyEventHandler) handler; 799 writer.print(" "); 800 writer.println(projectionKeyEventHandler.toString()); 801 } 802 803 writer.println("Local-only hotspot reservation: " + mLocalOnlyHotspotReservation); 804 writer.println("Wireless clients: " + mWirelessClients.size()); 805 writer.println("Current wifi mode: " + mWifiMode); 806 writer.println("SoftApCallback: " + mSoftApCallback); 807 writer.println("Bound to projection app: " + mBound); 808 writer.println("Registered Service: " + mRegisteredService); 809 writer.println("Current projection state: " + mCurrentProjectionState); 810 writer.println("Current projection package: " + mCurrentProjectionPackage); 811 writer.println("Projection status: " + mProjectionReceiverClients); 812 writer.println("Projection status listeners: " 813 + mProjectionStatusListeners.getInterfaces()); 814 writer.println("WifiScanner: " + mWifiScanner); 815 } 816 } 817 818 @Override onKeyEvent(@arProjectionManager.KeyEventNum int keyEvent)819 public void onKeyEvent(@CarProjectionManager.KeyEventNum int keyEvent) { 820 Slog.d(TAG, "Dispatching key event: " + keyEvent); 821 synchronized (mLock) { 822 for (BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> 823 eventHandlerInterface : mKeyEventHandlers.getInterfaces()) { 824 ProjectionKeyEventHandler eventHandler = 825 (ProjectionKeyEventHandler) eventHandlerInterface; 826 827 if (eventHandler.canHandleEvent(keyEvent)) { 828 try { 829 // oneway 830 eventHandler.binderInterface.onKeyEvent(keyEvent); 831 } catch (RemoteException e) { 832 Slog.e(TAG, "Cannot dispatch event to client", e); 833 } 834 } 835 } 836 } 837 } 838 839 @GuardedBy("mLock") updateInputServiceHandlerLocked()840 private void updateInputServiceHandlerLocked() { 841 BitSet newEvents = computeHandledEventsLocked(); 842 843 if (!newEvents.isEmpty()) { 844 mCarInputService.setProjectionKeyEventHandler(this, newEvents); 845 } else { 846 mCarInputService.setProjectionKeyEventHandler(null, null); 847 } 848 } 849 850 @GuardedBy("mLock") computeHandledEventsLocked()851 private BitSet computeHandledEventsLocked() { 852 BitSet rv = new BitSet(); 853 for (BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> 854 handlerInterface : mKeyEventHandlers.getInterfaces()) { 855 rv.or(((ProjectionKeyEventHandler) handlerInterface).mHandledEvents); 856 } 857 return rv; 858 } 859 setUiMode(Integer uiMode)860 void setUiMode(Integer uiMode) { 861 synchronized (mLock) { 862 mProjectionOptions = createProjectionOptionsBuilder() 863 .setUiMode(uiMode) 864 .build(); 865 } 866 } 867 setAccessPointTethering(boolean tetherEnabled)868 void setAccessPointTethering(boolean tetherEnabled) { 869 synchronized (mLock) { 870 mWifiMode = tetherEnabled ? WIFI_MODE_TETHERED : WIFI_MODE_LOCALONLY; 871 } 872 } 873 874 private static class ProjectionKeyEventHandlerContainer 875 extends BinderInterfaceContainer<ICarProjectionKeyEventHandler> { ProjectionKeyEventHandlerContainer(CarProjectionService service)876 ProjectionKeyEventHandlerContainer(CarProjectionService service) { 877 super(service); 878 } 879 get(ICarProjectionKeyEventHandler projectionCallback)880 ProjectionKeyEventHandler get(ICarProjectionKeyEventHandler projectionCallback) { 881 return (ProjectionKeyEventHandler) getBinderInterface(projectionCallback); 882 } 883 } 884 885 private static class ProjectionKeyEventHandler extends 886 BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> { 887 private BitSet mHandledEvents; 888 ProjectionKeyEventHandler( ProjectionKeyEventHandlerContainer holder, ICarProjectionKeyEventHandler binder, BitSet handledEvents)889 private ProjectionKeyEventHandler( 890 ProjectionKeyEventHandlerContainer holder, 891 ICarProjectionKeyEventHandler binder, 892 BitSet handledEvents) { 893 super(holder, binder); 894 mHandledEvents = handledEvents; 895 } 896 canHandleEvent(int event)897 private boolean canHandleEvent(int event) { 898 return mHandledEvents.get(event); 899 } 900 setHandledEvents(BitSet handledEvents)901 private void setHandledEvents(BitSet handledEvents) { 902 mHandledEvents = handledEvents; 903 } 904 905 @Override toString()906 public String toString() { 907 return "ProjectionKeyEventHandler{events=" + mHandledEvents + "}"; 908 } 909 } 910 registerWirelessClient(WirelessClient client)911 private void registerWirelessClient(WirelessClient client) throws RemoteException { 912 synchronized (mLock) { 913 if (unregisterWirelessClientLocked(client.token)) { 914 Slog.i(TAG, "Client was already registered, override it."); 915 } 916 mWirelessClients.put(client.token, client); 917 } 918 client.token.linkToDeath(new WirelessClientDeathRecipient(this, client), 0); 919 } 920 unregisterWirelessClients()921 private void unregisterWirelessClients() { 922 synchronized (mLock) { 923 for (WirelessClient client: mWirelessClients.values()) { 924 client.token.unlinkToDeath(client.deathRecipient, 0); 925 } 926 mWirelessClients.clear(); 927 } 928 } 929 unregisterWirelessClientLocked(IBinder token)930 private boolean unregisterWirelessClientLocked(IBinder token) { 931 WirelessClient client = mWirelessClients.remove(token); 932 if (client != null) { 933 token.unlinkToDeath(client.deathRecipient, 0); 934 } 935 936 return client != null; 937 } 938 ensureApConfiguration()939 private void ensureApConfiguration() { 940 // Always prefer 5GHz configuration whenever it is available. 941 SoftApConfiguration apConfig = mWifiManager.getSoftApConfiguration(); 942 if (apConfig != null && apConfig.getBand() != SoftApConfiguration.BAND_5GHZ 943 && mWifiManager.is5GHzBandSupported()) { 944 mWifiManager.setSoftApConfiguration(new SoftApConfiguration.Builder(apConfig) 945 .setBand(SoftApConfiguration.BAND_5GHZ).build()); 946 } 947 } 948 949 private class ProjectionSoftApCallback implements WifiManager.SoftApCallback { 950 private boolean mCurrentStateCall = true; 951 952 @Override onStateChanged(int state, int softApFailureReason)953 public void onStateChanged(int state, int softApFailureReason) { 954 Slog.i(TAG, "ProjectionSoftApCallback, onStateChanged, state: " + state 955 + ", failed reason: " + softApFailureReason 956 + ", currentStateCall: " + mCurrentStateCall); 957 if (mCurrentStateCall) { 958 // When callback gets registered framework always sends the current state as the 959 // first call. We should ignore current state call to be in par with 960 // local-only behavior. 961 mCurrentStateCall = false; 962 return; 963 } 964 965 switch (state) { 966 case WifiManager.WIFI_AP_STATE_ENABLED: { 967 sendApStarted(mWifiManager.getSoftApConfiguration()); 968 break; 969 } 970 case WifiManager.WIFI_AP_STATE_DISABLED: { 971 sendApStopped(); 972 break; 973 } 974 case WifiManager.WIFI_AP_STATE_FAILED: { 975 Slog.w(TAG, "WIFI_AP_STATE_FAILED, reason: " + softApFailureReason); 976 int reason; 977 switch (softApFailureReason) { 978 case WifiManager.SAP_START_FAILURE_NO_CHANNEL: 979 reason = ProjectionAccessPointCallback.ERROR_NO_CHANNEL; 980 break; 981 default: 982 reason = ProjectionAccessPointCallback.ERROR_GENERIC; 983 } 984 sendApFailed(reason); 985 break; 986 } 987 } 988 } 989 990 @Override onConnectedClientsChanged(List<WifiClient> clients)991 public void onConnectedClientsChanged(List<WifiClient> clients) { 992 if (DBG) { 993 Slog.d(TAG, "ProjectionSoftApCallback, onConnectedClientsChanged with " 994 + clients.size() + " clients"); 995 } 996 } 997 } 998 999 private static class WirelessClient { 1000 public final Messenger messenger; 1001 public final IBinder token; 1002 public @Nullable DeathRecipient deathRecipient; 1003 WirelessClient(Messenger messenger, IBinder token)1004 private WirelessClient(Messenger messenger, IBinder token) { 1005 this.messenger = messenger; 1006 this.token = token; 1007 } 1008 of(Messenger messenger, IBinder token)1009 private static WirelessClient of(Messenger messenger, IBinder token) { 1010 return new WirelessClient(messenger, token); 1011 } 1012 send(Message message)1013 void send(Message message) { 1014 try { 1015 Slog.d(TAG, "Sending message " + message.what + " to " + this); 1016 messenger.send(message); 1017 } catch (RemoteException e) { 1018 Slog.e(TAG, "Failed to send message", e); 1019 } 1020 } 1021 1022 @Override toString()1023 public String toString() { 1024 return getClass().getSimpleName() 1025 + "{token= " + token 1026 + ", deathRecipient=" + deathRecipient + "}"; 1027 } 1028 } 1029 1030 private static class WirelessClientDeathRecipient implements DeathRecipient { 1031 final WeakReference<CarProjectionService> mServiceRef; 1032 final WirelessClient mClient; 1033 WirelessClientDeathRecipient(CarProjectionService service, WirelessClient client)1034 WirelessClientDeathRecipient(CarProjectionService service, WirelessClient client) { 1035 mServiceRef = new WeakReference<>(service); 1036 mClient = client; 1037 mClient.deathRecipient = this; 1038 } 1039 1040 @Override binderDied()1041 public void binderDied() { 1042 Slog.w(TAG, "Wireless client " + mClient + " died."); 1043 CarProjectionService service = mServiceRef.get(); 1044 if (service == null) return; 1045 1046 synchronized (service.mLock) { 1047 service.unregisterWirelessClientLocked(mClient.token); 1048 } 1049 } 1050 } 1051 1052 private static class ProjectionReceiverClient { 1053 private final DeathRecipient mDeathRecipient; 1054 private ProjectionStatus mProjectionStatus; 1055 ProjectionReceiverClient(DeathRecipient deathRecipient)1056 ProjectionReceiverClient(DeathRecipient deathRecipient) { 1057 mDeathRecipient = deathRecipient; 1058 } 1059 1060 @Override toString()1061 public String toString() { 1062 return "ProjectionReceiverClient{" 1063 + "mDeathRecipient=" + mDeathRecipient 1064 + ", mProjectionStatus=" + mProjectionStatus 1065 + '}'; 1066 } 1067 } 1068 } 1069