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