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 import static android.net.wifi.WifiManager.WIFI_FREQUENCY_BAND_5GHZ; 30 31 import android.annotation.Nullable; 32 import android.app.ActivityOptions; 33 import android.bluetooth.BluetoothDevice; 34 import android.car.CarProjectionManager; 35 import android.car.CarProjectionManager.ProjectionAccessPointCallback; 36 import android.car.CarProjectionManager.ProjectionKeyEventHandler; 37 import android.car.ICarProjection; 38 import android.car.ICarProjectionKeyEventHandler; 39 import android.car.ICarProjectionStatusListener; 40 import android.car.projection.ProjectionOptions; 41 import android.car.projection.ProjectionStatus; 42 import android.car.projection.ProjectionStatus.ProjectionState; 43 import android.content.BroadcastReceiver; 44 import android.content.ComponentName; 45 import android.content.Context; 46 import android.content.Intent; 47 import android.content.IntentFilter; 48 import android.content.ServiceConnection; 49 import android.content.pm.PackageManager; 50 import android.content.res.Resources; 51 import android.graphics.Rect; 52 import android.net.wifi.WifiClient; 53 import android.net.wifi.WifiConfiguration; 54 import android.net.wifi.WifiManager; 55 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; 56 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation; 57 import android.net.wifi.WifiScanner; 58 import android.os.Binder; 59 import android.os.Bundle; 60 import android.os.Handler; 61 import android.os.HandlerExecutor; 62 import android.os.IBinder; 63 import android.os.Message; 64 import android.os.Messenger; 65 import android.os.RemoteException; 66 import android.os.UserHandle; 67 import android.text.TextUtils; 68 import android.util.Log; 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.io.PrintWriter; 75 import java.lang.ref.WeakReference; 76 import java.net.NetworkInterface; 77 import java.net.SocketException; 78 import java.util.ArrayList; 79 import java.util.BitSet; 80 import java.util.HashMap; 81 import java.util.List; 82 83 /** 84 * Car projection service allows to bound to projected app to boost it priority. 85 * It also enables projected applications to handle voice action requests. 86 */ 87 class CarProjectionService extends ICarProjection.Stub implements CarServiceBase, 88 BinderInterfaceContainer.BinderEventHandler<ICarProjectionKeyEventHandler>, 89 CarProjectionManager.ProjectionKeyEventHandler { 90 private static final String TAG = CarLog.TAG_PROJECTION; 91 private static final boolean DBG = true; 92 93 private final CarInputService mCarInputService; 94 private final CarBluetoothService mCarBluetoothService; 95 private final Context mContext; 96 private final WifiManager mWifiManager; 97 private final Handler mHandler; 98 private final Object mLock = new Object(); 99 100 @GuardedBy("mLock") 101 private final HashMap<IBinder, WirelessClient> mWirelessClients = new HashMap<>(); 102 103 @GuardedBy("mLock") 104 private @Nullable LocalOnlyHotspotReservation mLocalOnlyHotspotReservation; 105 106 107 @GuardedBy("mLock") 108 private @Nullable ProjectionSoftApCallback mSoftApCallback; 109 110 @GuardedBy("mLock") 111 private final HashMap<IBinder, ProjectionReceiverClient> mProjectionReceiverClients = 112 new HashMap<>(); 113 114 @Nullable 115 private String mApBssid; 116 117 @GuardedBy("mLock") 118 private @Nullable WifiScanner mWifiScanner; 119 120 @GuardedBy("mLock") 121 private @ProjectionState int mCurrentProjectionState = PROJECTION_STATE_INACTIVE; 122 123 @GuardedBy("mLock") 124 private ProjectionOptions mProjectionOptions; 125 126 @GuardedBy("mLock") 127 private @Nullable String mCurrentProjectionPackage; 128 129 private final BinderInterfaceContainer<ICarProjectionStatusListener> 130 mProjectionStatusListeners = new BinderInterfaceContainer<>(); 131 132 @GuardedBy("mLock") 133 private final ProjectionKeyEventHandlerContainer mKeyEventHandlers; 134 135 private static final int WIFI_MODE_TETHERED = 1; 136 private static final int WIFI_MODE_LOCALONLY = 2; 137 138 // Could be one of the WIFI_MODE_* constants. 139 // TODO: read this from user settings, support runtime switch 140 private int mWifiMode; 141 142 private final ServiceConnection mConnection = new ServiceConnection() { 143 @Override 144 public void onServiceConnected(ComponentName className, IBinder service) { 145 synchronized (mLock) { 146 mBound = true; 147 } 148 } 149 150 @Override 151 public void onServiceDisconnected(ComponentName className) { 152 // Service has crashed. 153 Log.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className); 154 synchronized (mLock) { 155 mRegisteredService = null; 156 } 157 unbindServiceIfBound(); 158 } 159 }; 160 161 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 162 @Override 163 public void onReceive(Context context, Intent intent) { 164 int currState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED); 165 int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE, 166 WIFI_AP_STATE_DISABLED); 167 int errorCode = intent.getIntExtra(EXTRA_WIFI_AP_FAILURE_REASON, 0); 168 String ifaceName = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME); 169 int mode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, 170 WifiManager.IFACE_IP_MODE_UNSPECIFIED); 171 handleWifiApStateChange(currState, prevState, errorCode, ifaceName, mode); 172 } 173 }; 174 175 private boolean mBound; 176 private Intent mRegisteredService; 177 CarProjectionService(Context context, @Nullable Handler handler, CarInputService carInputService, CarBluetoothService carBluetoothService)178 CarProjectionService(Context context, @Nullable Handler handler, 179 CarInputService carInputService, CarBluetoothService carBluetoothService) { 180 mContext = context; 181 mHandler = handler == null ? new Handler() : handler; 182 mCarInputService = carInputService; 183 mCarBluetoothService = carBluetoothService; 184 mKeyEventHandlers = new ProjectionKeyEventHandlerContainer(this); 185 mWifiManager = context.getSystemService(WifiManager.class); 186 187 final Resources res = mContext.getResources(); 188 setAccessPointTethering(res.getBoolean(R.bool.config_projectionAccessPointTethering)); 189 } 190 191 @Override registerProjectionRunner(Intent serviceIntent)192 public void registerProjectionRunner(Intent serviceIntent) { 193 ICarImpl.assertProjectionPermission(mContext); 194 // We assume one active projection app running in the system at one time. 195 synchronized (mLock) { 196 if (serviceIntent.filterEquals(mRegisteredService) && mBound) { 197 return; 198 } 199 if (mRegisteredService != null) { 200 Log.w(CarLog.TAG_PROJECTION, "Registering new service[" + serviceIntent 201 + "] while old service[" + mRegisteredService + "] is still running"); 202 } 203 unbindServiceIfBound(); 204 } 205 bindToService(serviceIntent); 206 } 207 208 @Override unregisterProjectionRunner(Intent serviceIntent)209 public void unregisterProjectionRunner(Intent serviceIntent) { 210 ICarImpl.assertProjectionPermission(mContext); 211 synchronized (mLock) { 212 if (!serviceIntent.filterEquals(mRegisteredService)) { 213 Log.w(CarLog.TAG_PROJECTION, "Request to unbind unregistered service[" 214 + serviceIntent + "]. Registered service[" + mRegisteredService + "]"); 215 return; 216 } 217 mRegisteredService = null; 218 } 219 unbindServiceIfBound(); 220 } 221 bindToService(Intent serviceIntent)222 private void bindToService(Intent serviceIntent) { 223 synchronized (mLock) { 224 mRegisteredService = serviceIntent; 225 } 226 UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid()); 227 mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE, 228 userHandle); 229 } 230 unbindServiceIfBound()231 private void unbindServiceIfBound() { 232 synchronized (mLock) { 233 if (!mBound) { 234 return; 235 } 236 mBound = false; 237 mRegisteredService = null; 238 } 239 mContext.unbindService(mConnection); 240 } 241 242 @Override registerKeyEventHandler( ICarProjectionKeyEventHandler eventHandler, byte[] eventMask)243 public void registerKeyEventHandler( 244 ICarProjectionKeyEventHandler eventHandler, byte[] eventMask) { 245 ICarImpl.assertProjectionPermission(mContext); 246 BitSet events = BitSet.valueOf(eventMask); 247 Preconditions.checkArgument( 248 events.length() <= CarProjectionManager.NUM_KEY_EVENTS, 249 "Unknown handled event"); 250 synchronized (mLock) { 251 ProjectionKeyEventHandler info = mKeyEventHandlers.get(eventHandler); 252 if (info == null) { 253 info = new ProjectionKeyEventHandler(mKeyEventHandlers, eventHandler, events); 254 mKeyEventHandlers.addBinderInterface(info); 255 } else { 256 info.setHandledEvents(events); 257 } 258 259 updateInputServiceHandlerLocked(); 260 } 261 } 262 263 @Override unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler)264 public void unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler) { 265 ICarImpl.assertProjectionPermission(mContext); 266 synchronized (mLock) { 267 mKeyEventHandlers.removeBinder(eventHandler); 268 updateInputServiceHandlerLocked(); 269 } 270 } 271 272 @Override startProjectionAccessPoint(final Messenger messenger, IBinder binder)273 public void startProjectionAccessPoint(final Messenger messenger, IBinder binder) 274 throws RemoteException { 275 ICarImpl.assertProjectionPermission(mContext); 276 //TODO: check if access point already started with the desired configuration. 277 registerWirelessClient(WirelessClient.of(messenger, binder)); 278 startAccessPoint(); 279 } 280 281 @Override stopProjectionAccessPoint(IBinder token)282 public void stopProjectionAccessPoint(IBinder token) { 283 ICarImpl.assertProjectionPermission(mContext); 284 Log.i(TAG, "Received stop access point request from " + token); 285 286 boolean shouldReleaseAp; 287 synchronized (mLock) { 288 if (!unregisterWirelessClientLocked(token)) { 289 Log.w(TAG, "Client " + token + " was not registered"); 290 return; 291 } 292 shouldReleaseAp = mWirelessClients.isEmpty(); 293 } 294 295 if (shouldReleaseAp) { 296 stopAccessPoint(); 297 } 298 } 299 300 @Override getAvailableWifiChannels(int band)301 public int[] getAvailableWifiChannels(int band) { 302 ICarImpl.assertProjectionPermission(mContext); 303 WifiScanner scanner; 304 synchronized (mLock) { 305 // Lazy initialization 306 if (mWifiScanner == null) { 307 mWifiScanner = mContext.getSystemService(WifiScanner.class); 308 } 309 scanner = mWifiScanner; 310 } 311 if (scanner == null) { 312 Log.w(TAG, "Unable to get WifiScanner"); 313 return new int[0]; 314 } 315 316 List<Integer> channels = scanner.getAvailableChannels(band); 317 if (channels == null || channels.isEmpty()) { 318 Log.w(TAG, "WifiScanner reported no available channels"); 319 return new int[0]; 320 } 321 322 int[] array = new int[channels.size()]; 323 for (int i = 0; i < channels.size(); i++) { 324 array[i] = channels.get(i); 325 } 326 return array; 327 } 328 329 /** 330 * Request to disconnect the given profile on the given device, and prevent it from reconnecting 331 * until either the request is released, or the process owning the given token dies. 332 * 333 * @param device The device on which to inhibit a profile. 334 * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit. 335 * @param token A {@link IBinder} to be used as an identity for the request. If the process 336 * owning the token dies, the request will automatically be released. 337 * @return True if the profile was successfully inhibited, false if an error occurred. 338 */ 339 @Override requestBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)340 public boolean requestBluetoothProfileInhibit( 341 BluetoothDevice device, int profile, IBinder token) { 342 if (DBG) { 343 Log.d(TAG, "requestBluetoothProfileInhibit device=" + device + " profile=" + profile 344 + " from uid " + Binder.getCallingUid()); 345 } 346 ICarImpl.assertProjectionPermission(mContext); 347 try { 348 if (device == null) { 349 // Will be caught by AIDL and thrown to caller. 350 throw new NullPointerException("Device must not be null"); 351 } 352 if (token == null) { 353 throw new NullPointerException("Token must not be null"); 354 } 355 return mCarBluetoothService.requestProfileInhibit(device, profile, token); 356 } catch (RuntimeException e) { 357 Log.e(TAG, "Error in requestBluetoothProfileInhibit", e); 358 throw e; 359 } 360 } 361 362 /** 363 * Release an inhibit request made by {@link #requestBluetoothProfileInhibit}, and reconnect the 364 * profile if no other inhibit requests are active. 365 * 366 * @param device The device on which to release the inhibit request. 367 * @param profile The profile on which to release the inhibit request. 368 * @param token The token provided in the original call to 369 * {@link #requestBluetoothProfileInhibit}. 370 * @return True if the request was released, false if an error occurred. 371 */ 372 @Override releaseBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)373 public boolean releaseBluetoothProfileInhibit( 374 BluetoothDevice device, int profile, IBinder token) { 375 if (DBG) { 376 Log.d(TAG, "releaseBluetoothProfileInhibit device=" + device + " profile=" + profile 377 + " from uid " + Binder.getCallingUid()); 378 } 379 ICarImpl.assertProjectionPermission(mContext); 380 try { 381 if (device == null) { 382 // Will be caught by AIDL and thrown to caller. 383 throw new NullPointerException("Device must not be null"); 384 } 385 if (token == null) { 386 throw new NullPointerException("Token must not be null"); 387 } 388 return mCarBluetoothService.releaseProfileInhibit(device, profile, token); 389 } catch (RuntimeException e) { 390 Log.e(TAG, "Error in releaseBluetoothProfileInhibit", e); 391 throw e; 392 } 393 } 394 395 @Override updateProjectionStatus(ProjectionStatus status, IBinder token)396 public void updateProjectionStatus(ProjectionStatus status, IBinder token) 397 throws RemoteException { 398 if (DBG) { 399 Log.d(TAG, "updateProjectionStatus, status: " + status + ", token: " + token); 400 } 401 ICarImpl.assertProjectionPermission(mContext); 402 final String packageName = status.getPackageName(); 403 final int callingUid = Binder.getCallingUid(); 404 final int userHandleId = Binder.getCallingUserHandle().getIdentifier(); 405 final int packageUid; 406 407 try { 408 packageUid = 409 mContext.getPackageManager().getPackageUidAsUser(packageName, userHandleId); 410 } catch (PackageManager.NameNotFoundException e) { 411 throw new SecurityException("Package " + packageName + " does not exist", e); 412 } 413 414 if (callingUid != packageUid) { 415 throw new SecurityException( 416 "UID " + callingUid + " cannot update status for package " + packageName); 417 } 418 419 synchronized (mLock) { 420 ProjectionReceiverClient client = getOrCreateProjectionReceiverClientLocked(token); 421 client.mProjectionStatus = status; 422 423 // If the projection package that's reporting its projection state is the currently 424 // active projection package, update the state. If it is a different package, update the 425 // current projection state if the new package is reporting that it is projecting or if 426 // it is reporting that it's ready to project, and the current package has an inactive 427 // projection state. 428 if (status.isActive() 429 || (status.getState() == PROJECTION_STATE_READY_TO_PROJECT 430 && mCurrentProjectionState == PROJECTION_STATE_INACTIVE) 431 || TextUtils.equals(packageName, mCurrentProjectionPackage)) { 432 mCurrentProjectionState = status.getState(); 433 mCurrentProjectionPackage = packageName; 434 } 435 } 436 notifyProjectionStatusChanged(null /* notify all listeners */); 437 } 438 439 @Override registerProjectionStatusListener(ICarProjectionStatusListener listener)440 public void registerProjectionStatusListener(ICarProjectionStatusListener listener) 441 throws RemoteException { 442 ICarImpl.assertProjectionStatusPermission(mContext); 443 mProjectionStatusListeners.addBinder(listener); 444 445 // Immediately notify listener with the current status. 446 notifyProjectionStatusChanged(listener); 447 } 448 449 @Override unregisterProjectionStatusListener(ICarProjectionStatusListener listener)450 public void unregisterProjectionStatusListener(ICarProjectionStatusListener listener) 451 throws RemoteException { 452 ICarImpl.assertProjectionStatusPermission(mContext); 453 mProjectionStatusListeners.removeBinder(listener); 454 } 455 getOrCreateProjectionReceiverClientLocked( IBinder token)456 private ProjectionReceiverClient getOrCreateProjectionReceiverClientLocked( 457 IBinder token) throws RemoteException { 458 ProjectionReceiverClient client; 459 client = mProjectionReceiverClients.get(token); 460 if (client == null) { 461 client = new ProjectionReceiverClient(() -> unregisterProjectionReceiverClient(token)); 462 token.linkToDeath(client.mDeathRecipient, 0 /* flags */); 463 mProjectionReceiverClients.put(token, client); 464 } 465 return client; 466 } 467 unregisterProjectionReceiverClient(IBinder token)468 private void unregisterProjectionReceiverClient(IBinder token) { 469 synchronized (mLock) { 470 ProjectionReceiverClient client = mProjectionReceiverClients.remove(token); 471 if (client == null) { 472 Log.w(TAG, "Projection receiver client for token " + token + " doesn't exist"); 473 return; 474 } 475 token.unlinkToDeath(client.mDeathRecipient, 0); 476 if (TextUtils.equals( 477 client.mProjectionStatus.getPackageName(), mCurrentProjectionPackage)) { 478 mCurrentProjectionPackage = null; 479 mCurrentProjectionState = PROJECTION_STATE_INACTIVE; 480 } 481 } 482 } 483 notifyProjectionStatusChanged( @ullable ICarProjectionStatusListener singleListenerToNotify)484 private void notifyProjectionStatusChanged( 485 @Nullable ICarProjectionStatusListener singleListenerToNotify) 486 throws RemoteException { 487 int currentState; 488 String currentPackage; 489 List<ProjectionStatus> statuses = new ArrayList<>(); 490 synchronized (mLock) { 491 for (ProjectionReceiverClient client : mProjectionReceiverClients.values()) { 492 statuses.add(client.mProjectionStatus); 493 } 494 currentState = mCurrentProjectionState; 495 currentPackage = mCurrentProjectionPackage; 496 } 497 498 if (DBG) { 499 Log.d(TAG, "Notify projection status change, state: " + currentState + ", pkg: " 500 + currentPackage + ", listeners: " + mProjectionStatusListeners.size() 501 + ", listenerToNotify: " + singleListenerToNotify); 502 } 503 504 if (singleListenerToNotify == null) { 505 for (BinderInterface<ICarProjectionStatusListener> listener : 506 mProjectionStatusListeners.getInterfaces()) { 507 try { 508 listener.binderInterface.onProjectionStatusChanged( 509 currentState, currentPackage, statuses); 510 } catch (RemoteException ex) { 511 Log.e(TAG, "Error calling to projection status listener", ex); 512 } 513 } 514 } else { 515 singleListenerToNotify.onProjectionStatusChanged( 516 currentState, currentPackage, statuses); 517 } 518 } 519 520 @Override getProjectionOptions()521 public Bundle getProjectionOptions() { 522 ICarImpl.assertProjectionPermission(mContext); 523 synchronized (mLock) { 524 if (mProjectionOptions == null) { 525 mProjectionOptions = createProjectionOptionsBuilder() 526 .build(); 527 } 528 } 529 return mProjectionOptions.toBundle(); 530 } 531 createProjectionOptionsBuilder()532 private ProjectionOptions.Builder createProjectionOptionsBuilder() { 533 Resources res = mContext.getResources(); 534 535 ProjectionOptions.Builder builder = ProjectionOptions.builder(); 536 537 ActivityOptions activityOptions = createActivityOptions(res); 538 if (activityOptions != null) { 539 builder.setProjectionActivityOptions(activityOptions); 540 } 541 542 String consentActivity = res.getString(R.string.config_projectionConsentActivity); 543 if (!TextUtils.isEmpty(consentActivity)) { 544 builder.setConsentActivity(ComponentName.unflattenFromString(consentActivity)); 545 } 546 547 builder.setUiMode(res.getInteger(R.integer.config_projectionUiMode)); 548 return builder; 549 } 550 551 @Nullable createActivityOptions(Resources res)552 private static ActivityOptions createActivityOptions(Resources res) { 553 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 554 boolean changed = false; 555 int displayId = res.getInteger(R.integer.config_projectionActivityDisplayId); 556 if (displayId != -1) { 557 activityOptions.setLaunchDisplayId(displayId); 558 changed = true; 559 } 560 int[] rawBounds = res.getIntArray(R.array.config_projectionActivityLaunchBounds); 561 if (rawBounds != null && rawBounds.length == 4) { 562 Rect bounds = new Rect(rawBounds[0], rawBounds[1], rawBounds[2], rawBounds[3]); 563 activityOptions.setLaunchBounds(bounds); 564 changed = true; 565 } 566 return changed ? activityOptions : null; 567 } 568 startAccessPoint()569 private void startAccessPoint() { 570 synchronized (mLock) { 571 switch (mWifiMode) { 572 case WIFI_MODE_LOCALONLY: { 573 startLocalOnlyApLocked(); 574 break; 575 } 576 case WIFI_MODE_TETHERED: { 577 startTetheredApLocked(); 578 break; 579 } 580 default: { 581 Log.wtf(TAG, "Unexpected Access Point mode during starting: " + mWifiMode); 582 break; 583 } 584 } 585 } 586 } 587 stopAccessPoint()588 private void stopAccessPoint() { 589 sendApStopped(); 590 591 synchronized (mLock) { 592 switch (mWifiMode) { 593 case WIFI_MODE_LOCALONLY: { 594 stopLocalOnlyApLocked(); 595 break; 596 } 597 case WIFI_MODE_TETHERED: { 598 stopTetheredApLocked(); 599 break; 600 } 601 default: { 602 Log.wtf(TAG, "Unexpected Access Point mode during stopping : " + mWifiMode); 603 } 604 } 605 } 606 } 607 startTetheredApLocked()608 private void startTetheredApLocked() { 609 Log.d(TAG, "startTetheredApLocked"); 610 611 if (mSoftApCallback == null) { 612 mSoftApCallback = new ProjectionSoftApCallback(); 613 mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback); 614 ensureApConfiguration(); 615 } 616 617 if (!mWifiManager.startSoftAp(null /* use existing config*/)) { 618 // The indicates that AP might be already started. 619 if (mWifiManager.getWifiApState() == WIFI_AP_STATE_ENABLED) { 620 sendApStarted(mWifiManager.getWifiApConfiguration()); 621 } else { 622 Log.e(TAG, "Failed to start soft AP"); 623 sendApFailed(ERROR_GENERIC); 624 } 625 } 626 } 627 stopTetheredApLocked()628 private void stopTetheredApLocked() { 629 Log.d(TAG, "stopTetheredAp"); 630 631 if (mSoftApCallback != null) { 632 mWifiManager.unregisterSoftApCallback(mSoftApCallback); 633 mSoftApCallback = null; 634 if (!mWifiManager.stopSoftAp()) { 635 Log.w(TAG, "Failed to request soft AP to stop."); 636 } 637 } 638 } 639 startLocalOnlyApLocked()640 private void startLocalOnlyApLocked() { 641 if (mLocalOnlyHotspotReservation != null) { 642 Log.i(TAG, "Local-only hotspot is already registered."); 643 sendApStarted(mLocalOnlyHotspotReservation.getWifiConfiguration()); 644 return; 645 } 646 647 Log.i(TAG, "Requesting to start local-only hotspot."); 648 mWifiManager.startLocalOnlyHotspot(new LocalOnlyHotspotCallback() { 649 @Override 650 public void onStarted(LocalOnlyHotspotReservation reservation) { 651 Log.d(TAG, "Local-only hotspot started"); 652 synchronized (mLock) { 653 mLocalOnlyHotspotReservation = reservation; 654 } 655 sendApStarted(reservation.getWifiConfiguration()); 656 } 657 658 @Override 659 public void onStopped() { 660 Log.i(TAG, "Local-only hotspot stopped."); 661 synchronized (mLock) { 662 if (mLocalOnlyHotspotReservation != null) { 663 // We must explicitly released old reservation object, otherwise it may 664 // unexpectedly stop LOHS later because it overrode finalize() method. 665 mLocalOnlyHotspotReservation.close(); 666 } 667 mLocalOnlyHotspotReservation = null; 668 } 669 sendApStopped(); 670 } 671 672 @Override 673 public void onFailed(int localonlyHostspotFailureReason) { 674 Log.w(TAG, "Local-only hotspot failed, reason: " 675 + localonlyHostspotFailureReason); 676 synchronized (mLock) { 677 mLocalOnlyHotspotReservation = null; 678 } 679 int reason; 680 switch (localonlyHostspotFailureReason) { 681 case LocalOnlyHotspotCallback.ERROR_NO_CHANNEL: 682 reason = ProjectionAccessPointCallback.ERROR_NO_CHANNEL; 683 break; 684 case LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED: 685 reason = ProjectionAccessPointCallback.ERROR_TETHERING_DISALLOWED; 686 break; 687 case LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE: 688 reason = ProjectionAccessPointCallback.ERROR_INCOMPATIBLE_MODE; 689 break; 690 default: 691 reason = ERROR_GENERIC; 692 693 } 694 sendApFailed(reason); 695 } 696 }, mHandler); 697 } 698 stopLocalOnlyApLocked()699 private void stopLocalOnlyApLocked() { 700 Log.i(TAG, "stopLocalOnlyApLocked"); 701 702 if (mLocalOnlyHotspotReservation == null) { 703 Log.w(TAG, "Requested to stop local-only hotspot which was already stopped."); 704 return; 705 } 706 707 mLocalOnlyHotspotReservation.close(); 708 mLocalOnlyHotspotReservation = null; 709 } 710 sendApStarted(WifiConfiguration wifiConfiguration)711 private void sendApStarted(WifiConfiguration wifiConfiguration) { 712 WifiConfiguration localWifiConfig = new WifiConfiguration(wifiConfiguration); 713 localWifiConfig.BSSID = mApBssid; 714 715 Message message = Message.obtain(); 716 message.what = CarProjectionManager.PROJECTION_AP_STARTED; 717 message.obj = localWifiConfig; 718 Log.i(TAG, "Sending PROJECTION_AP_STARTED, ssid: " 719 + localWifiConfig.getPrintableSsid() 720 + ", apBand: " + localWifiConfig.apBand 721 + ", apChannel: " + localWifiConfig.apChannel 722 + ", bssid: " + localWifiConfig.BSSID); 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 Log.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 Log.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(PrintWriter writer)791 public void dump(PrintWriter 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 Log.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 Log.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 Log.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 WifiConfiguration apConfig = mWifiManager.getWifiApConfiguration(); 942 if (apConfig != null && apConfig.apBand != WIFI_FREQUENCY_BAND_5GHZ 943 && mWifiManager.is5GHzBandSupported()) { 944 apConfig.apBand = WIFI_FREQUENCY_BAND_5GHZ; 945 mWifiManager.setWifiApConfiguration(apConfig); 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 Log.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.getWifiApConfiguration()); 968 break; 969 } 970 case WifiManager.WIFI_AP_STATE_DISABLED: { 971 sendApStopped(); 972 break; 973 } 974 case WifiManager.WIFI_AP_STATE_FAILED: { 975 Log.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 Log.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 Log.d(TAG, "Sending message " + message.what + " to " + this); 1016 messenger.send(message); 1017 } catch (RemoteException e) { 1018 Log.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 Log.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