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.WifiAvailableChannel.OP_MODE_SAP; 22 import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE; 23 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON; 24 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; 25 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; 26 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; 27 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; 28 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; 29 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; 30 31 import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY; 32 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 33 34 import android.annotation.Nullable; 35 import android.app.ActivityOptions; 36 import android.bluetooth.BluetoothDevice; 37 import android.car.CarProjectionManager; 38 import android.car.CarProjectionManager.ProjectionAccessPointCallback; 39 import android.car.ICarProjection; 40 import android.car.ICarProjectionKeyEventHandler; 41 import android.car.ICarProjectionStatusListener; 42 import android.car.builtin.content.pm.PackageManagerHelper; 43 import android.car.builtin.util.Slogf; 44 import android.car.feature.FeatureFlags; 45 import android.car.feature.FeatureFlagsImpl; 46 import android.car.projection.ProjectionOptions; 47 import android.car.projection.ProjectionStatus; 48 import android.car.projection.ProjectionStatus.ProjectionState; 49 import android.content.BroadcastReceiver; 50 import android.content.ComponentName; 51 import android.content.Context; 52 import android.content.Intent; 53 import android.content.IntentFilter; 54 import android.content.ServiceConnection; 55 import android.content.SharedPreferences; 56 import android.content.pm.PackageManager; 57 import android.content.res.Resources; 58 import android.graphics.Rect; 59 import android.net.MacAddress; 60 import android.net.wifi.SoftApConfiguration; 61 import android.net.wifi.WifiClient; 62 import android.net.wifi.WifiManager; 63 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; 64 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation; 65 import android.net.wifi.WifiScanner; 66 import android.os.Binder; 67 import android.os.Bundle; 68 import android.os.Handler; 69 import android.os.IBinder; 70 import android.os.Message; 71 import android.os.Messenger; 72 import android.os.RemoteException; 73 import android.os.UserHandle; 74 import android.text.TextUtils; 75 import android.util.SparseIntArray; 76 import android.util.proto.ProtoOutputStream; 77 import android.net.wifi.WifiAvailableChannel; 78 79 import com.android.car.BinderInterfaceContainer.BinderInterface; 80 import com.android.car.bluetooth.CarBluetoothService; 81 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 82 import com.android.car.internal.os.HandlerExecutor; 83 import com.android.car.internal.util.IndentingPrintWriter; 84 import com.android.internal.annotations.GuardedBy; 85 import com.android.internal.annotations.VisibleForTesting; 86 import com.android.internal.util.Preconditions; 87 88 import java.lang.ref.WeakReference; 89 import java.net.NetworkInterface; 90 import java.net.SocketException; 91 import java.util.ArrayList; 92 import java.util.BitSet; 93 import java.util.HashMap; 94 import java.util.List; 95 import java.util.Objects; 96 import java.util.Optional; 97 98 /** 99 * Car projection service allows to bound to projected app to boost it priority. 100 * It also enables projected applications to handle voice action requests. 101 */ 102 class CarProjectionService extends ICarProjection.Stub implements CarServiceBase, 103 BinderInterfaceContainer.BinderEventHandler<ICarProjectionKeyEventHandler>, 104 CarProjectionManager.ProjectionKeyEventHandler { 105 private static final String TAG = CarLog.tagFor(CarProjectionService.class); 106 private static final boolean DBG = true; 107 108 private final CarInputService mCarInputService; 109 private final CarBluetoothService mCarBluetoothService; 110 private final Context mContext; 111 private final WifiManager mWifiManager; 112 private final Handler mHandler; 113 private final Object mLock = new Object(); 114 115 @GuardedBy("mLock") 116 private final HashMap<IBinder, WirelessClient> mWirelessClients = new HashMap<>(); 117 118 @GuardedBy("mLock") 119 private @Nullable LocalOnlyHotspotReservation mLocalOnlyHotspotReservation; 120 121 @GuardedBy("mLock") 122 private @Nullable ProjectionSoftApCallback mSoftApCallback; 123 124 @GuardedBy("mLock") 125 private @Nullable LocalOnlyProjectionSoftApCallback mLocalOnlySoftApCallback; 126 127 @GuardedBy("mLock") 128 private final HashMap<IBinder, ProjectionReceiverClient> mProjectionReceiverClients = 129 new HashMap<>(); 130 131 @Nullable 132 private MacAddress mApBssid; 133 134 @GuardedBy("mLock") 135 private @Nullable WifiScanner mWifiScanner; 136 137 @GuardedBy("mLock") 138 private @ProjectionState int mCurrentProjectionState = PROJECTION_STATE_INACTIVE; 139 140 @GuardedBy("mLock") 141 private ProjectionOptions mProjectionOptions; 142 143 @GuardedBy("mLock") 144 private @Nullable String mCurrentProjectionPackage; 145 146 private final BinderInterfaceContainer<ICarProjectionStatusListener> 147 mProjectionStatusListeners = new BinderInterfaceContainer<>(); 148 149 @GuardedBy("mLock") 150 private final ProjectionKeyEventHandlerContainer mKeyEventHandlers; 151 152 @GuardedBy("mLock") 153 private @Nullable SoftApConfiguration mApConfiguration; 154 155 private FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); 156 157 private static final String SHARED_PREF_NAME = "com.android.car.car_projection_service"; 158 private static final String KEY_AP_CONFIG_SSID = "ap_config_ssid"; 159 private static final String KEY_AP_CONFIG_BSSID = "ap_config_bssid"; 160 private static final String KEY_AP_CONFIG_PASSPHRASE = "ap_config_passphrase"; 161 private static final String KEY_AP_CONFIG_SECURITY_TYPE = "ap_config_security_type"; 162 163 private static final int WIFI_MODE_TETHERED = 1; 164 private static final int WIFI_MODE_LOCALONLY = 2; 165 166 // Could be one of the WIFI_MODE_* constants. 167 // TODO: read this from user settings, support runtime switch 168 private int mWifiMode; 169 170 private boolean mStableLocalOnlyHotspotConfig; 171 172 private final ServiceConnection mConnection = new ServiceConnection() { 173 @Override 174 public void onServiceConnected(ComponentName className, IBinder service) { 175 synchronized (mLock) { 176 mBound = true; 177 } 178 } 179 180 @Override 181 public void onServiceDisconnected(ComponentName className) { 182 // Service has crashed. 183 Slogf.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className); 184 synchronized (mLock) { 185 mRegisteredService = null; 186 } 187 unbindServiceIfBound(); 188 } 189 }; 190 191 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 192 @Override 193 public void onReceive(Context context, Intent intent) { 194 int currState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED); 195 int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE, 196 WIFI_AP_STATE_DISABLED); 197 int errorCode = intent.getIntExtra(EXTRA_WIFI_AP_FAILURE_REASON, 0); 198 String ifaceName = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME); 199 int mode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, 200 WifiManager.IFACE_IP_MODE_UNSPECIFIED); 201 handleWifiApStateChange(currState, prevState, errorCode, ifaceName, mode); 202 } 203 }; 204 205 private boolean mBound; 206 private Intent mRegisteredService; 207 CarProjectionService(Context context, @Nullable Handler handler, CarInputService carInputService, CarBluetoothService carBluetoothService)208 CarProjectionService(Context context, @Nullable Handler handler, 209 CarInputService carInputService, CarBluetoothService carBluetoothService) { 210 mContext = context; 211 mHandler = handler == null ? new Handler() : handler; 212 mCarInputService = carInputService; 213 mCarBluetoothService = carBluetoothService; 214 mKeyEventHandlers = new ProjectionKeyEventHandlerContainer(this); 215 mWifiManager = context.getSystemService(WifiManager.class); 216 217 final Resources res = mContext.getResources(); 218 setAccessPointTethering(res.getBoolean(R.bool.config_projectionAccessPointTethering)); 219 setStableLocalOnlyHotspotConfig( 220 res.getBoolean(R.bool.config_stableLocalOnlyHotspotConfig)); 221 } 222 223 @Override registerProjectionRunner(Intent serviceIntent)224 public void registerProjectionRunner(Intent serviceIntent) { 225 CarServiceUtils.assertProjectionPermission(mContext); 226 // We assume one active projection app running in the system at one time. 227 synchronized (mLock) { 228 if (serviceIntent.filterEquals(mRegisteredService) && mBound) { 229 return; 230 } 231 if (mRegisteredService != null) { 232 Slogf.w(CarLog.TAG_PROJECTION, "Registering new service[" + serviceIntent 233 + "] while old service[" + mRegisteredService + "] is still running"); 234 } 235 unbindServiceIfBound(); 236 } 237 bindToService(serviceIntent); 238 } 239 240 @Override unregisterProjectionRunner(Intent serviceIntent)241 public void unregisterProjectionRunner(Intent serviceIntent) { 242 CarServiceUtils.assertProjectionPermission(mContext); 243 synchronized (mLock) { 244 if (!serviceIntent.filterEquals(mRegisteredService)) { 245 Slogf.w(CarLog.TAG_PROJECTION, "Request to unbind unregistered service[" 246 + serviceIntent + "]. Registered service[" + mRegisteredService + "]"); 247 return; 248 } 249 mRegisteredService = null; 250 } 251 unbindServiceIfBound(); 252 } 253 bindToService(Intent serviceIntent)254 private void bindToService(Intent serviceIntent) { 255 synchronized (mLock) { 256 mRegisteredService = serviceIntent; 257 } 258 UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid()); 259 mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE, 260 userHandle); 261 } 262 unbindServiceIfBound()263 private void unbindServiceIfBound() { 264 synchronized (mLock) { 265 if (!mBound) { 266 return; 267 } 268 mBound = false; 269 mRegisteredService = null; 270 } 271 mContext.unbindService(mConnection); 272 } 273 274 @Override registerKeyEventHandler( ICarProjectionKeyEventHandler eventHandler, byte[] eventMask)275 public void registerKeyEventHandler( 276 ICarProjectionKeyEventHandler eventHandler, byte[] eventMask) { 277 CarServiceUtils.assertProjectionPermission(mContext); 278 BitSet events = BitSet.valueOf(eventMask); 279 Preconditions.checkArgument( 280 events.length() <= CarProjectionManager.NUM_KEY_EVENTS, 281 "Unknown handled event"); 282 synchronized (mLock) { 283 ProjectionKeyEventHandler info = mKeyEventHandlers.get(eventHandler); 284 if (info == null) { 285 info = new ProjectionKeyEventHandler(mKeyEventHandlers, eventHandler, events); 286 mKeyEventHandlers.addBinderInterface(info); 287 } else { 288 info.setHandledEvents(events); 289 } 290 291 updateInputServiceHandlerLocked(); 292 } 293 } 294 295 @Override unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler)296 public void unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler) { 297 CarServiceUtils.assertProjectionPermission(mContext); 298 synchronized (mLock) { 299 mKeyEventHandlers.removeBinder(eventHandler); 300 updateInputServiceHandlerLocked(); 301 } 302 } 303 304 @Override startProjectionAccessPoint(final Messenger messenger, IBinder binder)305 public void startProjectionAccessPoint(final Messenger messenger, IBinder binder) 306 throws RemoteException { 307 CarServiceUtils.assertProjectionPermission(mContext); 308 //TODO: check if access point already started with the desired configuration. 309 registerWirelessClient(WirelessClient.of(messenger, binder)); 310 startAccessPoint(); 311 } 312 313 @Override stopProjectionAccessPoint(IBinder token)314 public void stopProjectionAccessPoint(IBinder token) { 315 CarServiceUtils.assertProjectionPermission(mContext); 316 Slogf.i(TAG, "Received stop access point request from " + token); 317 318 boolean shouldReleaseAp; 319 synchronized (mLock) { 320 if (!unregisterWirelessClientLocked(token)) { 321 Slogf.w(TAG, "Client " + token + " was not registered"); 322 return; 323 } 324 shouldReleaseAp = mWirelessClients.isEmpty(); 325 } 326 327 if (shouldReleaseAp) { 328 stopAccessPoint(); 329 } 330 } 331 332 @Override getAvailableWifiChannels(int band)333 public int[] getAvailableWifiChannels(int band) { 334 CarServiceUtils.assertProjectionPermission(mContext); 335 List<Integer> channels; 336 337 // Use {@link WifiManager} to get channels as {@link WifiScanner} fails to retrieve 338 // channels when wifi is off. 339 if (mFeatureFlags.useWifiManagerForAvailableChannels()) { 340 List<WifiAvailableChannel> availableChannels; 341 342 try { 343 availableChannels = mWifiManager.getUsableChannels(band, OP_MODE_SAP); 344 } catch (UnsupportedOperationException e) { 345 Slogf.w(TAG, "Unable to query channels from WifiManager", e); 346 return EMPTY_INT_ARRAY; 347 } 348 349 channels = new ArrayList<>(); 350 for (int i = 0; i < availableChannels.size(); i++) { 351 WifiAvailableChannel channel = availableChannels.get(i); 352 channels.add(channel.getFrequencyMhz()); 353 } 354 } else { 355 WifiScanner scanner; 356 synchronized (mLock) { 357 // Lazy initialization 358 if (mWifiScanner == null) { 359 mWifiScanner = mContext.getSystemService(WifiScanner.class); 360 } 361 scanner = mWifiScanner; 362 } 363 if (scanner == null) { 364 Slogf.w(TAG, "Unable to get WifiScanner"); 365 return EMPTY_INT_ARRAY; 366 } 367 368 channels = scanner.getAvailableChannels(band); 369 } 370 371 if (channels == null || channels.isEmpty()) { 372 Slogf.w(TAG, "No available channels reported"); 373 return EMPTY_INT_ARRAY; 374 } 375 376 int[] array = new int[channels.size()]; 377 for (int i = 0; i < channels.size(); i++) { 378 array[i] = channels.get(i); 379 } 380 return array; 381 } 382 383 /** 384 * Request to disconnect the given profile on the given device, and prevent it from reconnecting 385 * until either the request is released, or the process owning the given token dies. 386 * 387 * @param device The device on which to inhibit a profile. 388 * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit. 389 * @param token A {@link IBinder} to be used as an identity for the request. If the process 390 * owning the token dies, the request will automatically be released. 391 * @return True if the profile was successfully inhibited, false if an error occurred. 392 */ 393 @Override requestBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)394 public boolean requestBluetoothProfileInhibit( 395 BluetoothDevice device, int profile, IBinder token) { 396 if (DBG) { 397 Slogf.d(TAG, "requestBluetoothProfileInhibit device=" + device + " profile=" + profile 398 + " from uid " + Binder.getCallingUid()); 399 } 400 CarServiceUtils.assertProjectionPermission(mContext); 401 try { 402 if (device == null) { 403 // Will be caught by AIDL and thrown to caller. 404 throw new NullPointerException("Device must not be null"); 405 } 406 if (token == null) { 407 throw new NullPointerException("Token must not be null"); 408 } 409 return mCarBluetoothService.requestProfileInhibit(device, profile, token); 410 } catch (RuntimeException e) { 411 Slogf.e(TAG, "Error in requestBluetoothProfileInhibit", e); 412 throw e; 413 } 414 } 415 416 /** 417 * Release an inhibit request made by {@link #requestBluetoothProfileInhibit}, and reconnect the 418 * profile if no other inhibit requests are active. 419 * 420 * @param device The device on which to release the inhibit request. 421 * @param profile The profile on which to release the inhibit request. 422 * @param token The token provided in the original call to 423 * {@link #requestBluetoothProfileInhibit}. 424 * @return True if the request was released, false if an error occurred. 425 */ 426 @Override releaseBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)427 public boolean releaseBluetoothProfileInhibit( 428 BluetoothDevice device, int profile, IBinder token) { 429 if (DBG) { 430 Slogf.d(TAG, "releaseBluetoothProfileInhibit device=" + device + " profile=" + profile 431 + " from uid " + Binder.getCallingUid()); 432 } 433 CarServiceUtils.assertProjectionPermission(mContext); 434 try { 435 if (device == null) { 436 // Will be caught by AIDL and thrown to caller. 437 throw new NullPointerException("Device must not be null"); 438 } 439 if (token == null) { 440 throw new NullPointerException("Token must not be null"); 441 } 442 return mCarBluetoothService.releaseProfileInhibit(device, profile, token); 443 } catch (RuntimeException e) { 444 Slogf.e(TAG, "Error in releaseBluetoothProfileInhibit", e); 445 throw e; 446 } 447 } 448 449 /** 450 * Checks whether a request to disconnect the given profile on the given device has been made 451 * and if the inhibit request is still active. 452 * 453 * @param device The device on which to verify the inhibit request. 454 * @param profile The profile on which to verify the inhibit request. 455 * @param token The token provided in the original call to 456 * {@link #requestBluetoothProfileInhibit}. 457 * @return True if inhibit was requested and is still active, false if an error occurred or 458 * inactive. 459 */ 460 @Override isBluetoothProfileInhibited( BluetoothDevice device, int profile, IBinder token)461 public boolean isBluetoothProfileInhibited( 462 BluetoothDevice device, int profile, IBinder token) { 463 if (DBG) { 464 Slogf.d(TAG, "isBluetoothProfileInhibited device=" + device + " profile=" + profile 465 + " from uid " + Binder.getCallingUid()); 466 } 467 CarServiceUtils.assertProjectionPermission(mContext); 468 Objects.requireNonNull(device, "Device must not be null"); 469 Objects.requireNonNull(token, "Token must not be null"); 470 471 return mCarBluetoothService.isProfileInhibited(device, profile, token); 472 } 473 474 @Override updateProjectionStatus(ProjectionStatus status, IBinder token)475 public void updateProjectionStatus(ProjectionStatus status, IBinder token) 476 throws RemoteException { 477 if (DBG) { 478 Slogf.d(TAG, "updateProjectionStatus, status: " + status + ", token: " + token); 479 } 480 CarServiceUtils.assertProjectionPermission(mContext); 481 final String packageName = status.getPackageName(); 482 final int callingUid = Binder.getCallingUid(); 483 final int userHandleId = Binder.getCallingUserHandle().getIdentifier(); 484 final int packageUid; 485 486 try { 487 packageUid = PackageManagerHelper.getPackageUidAsUser(mContext.getPackageManager(), 488 packageName, userHandleId); 489 } catch (PackageManager.NameNotFoundException e) { 490 throw new SecurityException("Package " + packageName + " does not exist", e); 491 } 492 493 if (callingUid != packageUid) { 494 throw new SecurityException( 495 "UID " + callingUid + " cannot update status for package " + packageName); 496 } 497 498 synchronized (mLock) { 499 ProjectionReceiverClient client = getOrCreateProjectionReceiverClientLocked(token); 500 client.mProjectionStatus = status; 501 502 // If the projection package that's reporting its projection state is the currently 503 // active projection package, update the state. If it is a different package, update the 504 // current projection state if the new package is reporting that it is projecting or if 505 // it is reporting that it's ready to project, and the current package has an inactive 506 // projection state. 507 if (status.isActive() 508 || (status.getState() == PROJECTION_STATE_READY_TO_PROJECT 509 && mCurrentProjectionState == PROJECTION_STATE_INACTIVE) 510 || TextUtils.equals(packageName, mCurrentProjectionPackage)) { 511 mCurrentProjectionState = status.getState(); 512 mCurrentProjectionPackage = packageName; 513 } 514 } 515 notifyProjectionStatusChanged(null /* notify all listeners */); 516 } 517 518 @Override registerProjectionStatusListener(ICarProjectionStatusListener listener)519 public void registerProjectionStatusListener(ICarProjectionStatusListener listener) 520 throws RemoteException { 521 CarServiceUtils.assertProjectionStatusPermission(mContext); 522 mProjectionStatusListeners.addBinder(listener); 523 524 // Immediately notify listener with the current status. 525 notifyProjectionStatusChanged(listener); 526 } 527 528 @Override unregisterProjectionStatusListener(ICarProjectionStatusListener listener)529 public void unregisterProjectionStatusListener(ICarProjectionStatusListener listener) 530 throws RemoteException { 531 CarServiceUtils.assertProjectionStatusPermission(mContext); 532 mProjectionStatusListeners.removeBinder(listener); 533 } 534 535 @GuardedBy("mLock") getOrCreateProjectionReceiverClientLocked( IBinder token)536 private ProjectionReceiverClient getOrCreateProjectionReceiverClientLocked( 537 IBinder token) throws RemoteException { 538 ProjectionReceiverClient client; 539 client = mProjectionReceiverClients.get(token); 540 if (client == null) { 541 client = new ProjectionReceiverClient(() -> unregisterProjectionReceiverClient(token)); 542 token.linkToDeath(client.mDeathRecipient, 0 /* flags */); 543 mProjectionReceiverClients.put(token, client); 544 } 545 return client; 546 } 547 unregisterProjectionReceiverClient(IBinder token)548 private void unregisterProjectionReceiverClient(IBinder token) { 549 synchronized (mLock) { 550 ProjectionReceiverClient client = mProjectionReceiverClients.remove(token); 551 if (client == null) { 552 Slogf.w(TAG, "Projection receiver client for token " + token + " doesn't exist"); 553 return; 554 } 555 token.unlinkToDeath(client.mDeathRecipient, 0); 556 if (TextUtils.equals( 557 client.mProjectionStatus.getPackageName(), mCurrentProjectionPackage)) { 558 mCurrentProjectionPackage = null; 559 mCurrentProjectionState = PROJECTION_STATE_INACTIVE; 560 } 561 } 562 } 563 notifyProjectionStatusChanged( @ullable ICarProjectionStatusListener singleListenerToNotify)564 private void notifyProjectionStatusChanged( 565 @Nullable ICarProjectionStatusListener singleListenerToNotify) 566 throws RemoteException { 567 int currentState; 568 String currentPackage; 569 List<ProjectionStatus> statuses = new ArrayList<>(); 570 synchronized (mLock) { 571 for (ProjectionReceiverClient client : mProjectionReceiverClients.values()) { 572 statuses.add(client.mProjectionStatus); 573 } 574 currentState = mCurrentProjectionState; 575 currentPackage = mCurrentProjectionPackage; 576 } 577 578 if (DBG) { 579 Slogf.d(TAG, "Notify projection status change, state: " + currentState + ", pkg: " 580 + currentPackage + ", listeners: " + mProjectionStatusListeners.size() 581 + ", listenerToNotify: " + singleListenerToNotify); 582 } 583 584 if (singleListenerToNotify == null) { 585 for (BinderInterface<ICarProjectionStatusListener> listener : 586 mProjectionStatusListeners.getInterfaces()) { 587 try { 588 listener.binderInterface.onProjectionStatusChanged( 589 currentState, currentPackage, statuses); 590 } catch (RemoteException ex) { 591 Slogf.e(TAG, "Error calling to projection status listener", ex); 592 } 593 } 594 } else { 595 singleListenerToNotify.onProjectionStatusChanged( 596 currentState, currentPackage, statuses); 597 } 598 } 599 600 @Override getProjectionOptions()601 public Bundle getProjectionOptions() { 602 CarServiceUtils.assertProjectionPermission(mContext); 603 synchronized (mLock) { 604 if (mProjectionOptions == null) { 605 mProjectionOptions = createProjectionOptionsBuilder() 606 .build(); 607 } 608 return mProjectionOptions.toBundle(); 609 } 610 } 611 createProjectionOptionsBuilder()612 private ProjectionOptions.Builder createProjectionOptionsBuilder() { 613 Resources res = mContext.getResources(); 614 615 ProjectionOptions.Builder builder = ProjectionOptions.builder(); 616 617 ActivityOptions activityOptions = createActivityOptions(res); 618 if (activityOptions != null) { 619 builder.setProjectionActivityOptions(activityOptions); 620 } 621 622 String consentActivity = res.getString(R.string.config_projectionConsentActivity); 623 if (!TextUtils.isEmpty(consentActivity)) { 624 builder.setConsentActivity(ComponentName.unflattenFromString(consentActivity)); 625 } 626 627 builder.setUiMode(res.getInteger(R.integer.config_projectionUiMode)); 628 629 int apMode = ProjectionOptions.AP_MODE_NOT_SPECIFIED; 630 if (mWifiMode == WIFI_MODE_TETHERED) { 631 apMode = ProjectionOptions.AP_MODE_TETHERED; 632 } else if (mWifiMode == WIFI_MODE_LOCALONLY) { 633 apMode = mStableLocalOnlyHotspotConfig 634 ? ProjectionOptions.AP_MODE_LOHS_STATIC_CREDENTIALS 635 : ProjectionOptions.AP_MODE_LOHS_DYNAMIC_CREDENTIALS; 636 } 637 builder.setAccessPointMode(apMode); 638 639 return builder; 640 } 641 642 @Nullable createActivityOptions(Resources res)643 private static ActivityOptions createActivityOptions(Resources res) { 644 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 645 boolean changed = false; 646 int displayId = res.getInteger(R.integer.config_projectionActivityDisplayId); 647 if (displayId != -1) { 648 activityOptions.setLaunchDisplayId(displayId); 649 changed = true; 650 } 651 int[] rawBounds = res.getIntArray(R.array.config_projectionActivityLaunchBounds); 652 if (rawBounds != null && rawBounds.length == 4) { 653 Rect bounds = new Rect(rawBounds[0], rawBounds[1], rawBounds[2], rawBounds[3]); 654 activityOptions.setLaunchBounds(bounds); 655 changed = true; 656 } 657 return changed ? activityOptions : null; 658 } 659 startAccessPoint()660 private void startAccessPoint() { 661 synchronized (mLock) { 662 switch (mWifiMode) { 663 case WIFI_MODE_LOCALONLY: { 664 startLocalOnlyApLocked(); 665 break; 666 } 667 case WIFI_MODE_TETHERED: { 668 startTetheredApLocked(); 669 break; 670 } 671 default: { 672 Slogf.wtf(TAG, "Unexpected Access Point mode during starting: " + mWifiMode); 673 break; 674 } 675 } 676 } 677 } 678 stopAccessPoint()679 private void stopAccessPoint() { 680 sendApStopped(); 681 682 synchronized (mLock) { 683 switch (mWifiMode) { 684 case WIFI_MODE_LOCALONLY: { 685 stopLocalOnlyApLocked(); 686 break; 687 } 688 case WIFI_MODE_TETHERED: { 689 stopTetheredApLocked(); 690 break; 691 } 692 default: { 693 Slogf.wtf(TAG, "Unexpected Access Point mode during stopping : " + mWifiMode); 694 } 695 } 696 } 697 } 698 699 @GuardedBy("mLock") startTetheredApLocked()700 private void startTetheredApLocked() { 701 Slogf.d(TAG, "startTetheredApLocked"); 702 703 if (mSoftApCallback == null) { 704 mSoftApCallback = new ProjectionSoftApCallback(); 705 mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback); 706 ensureApConfiguration(); 707 } 708 709 if (!mWifiManager.startTetheredHotspot(null /* use existing config*/)) { 710 // The indicates that AP might be already started. 711 if (mWifiManager.getWifiApState() == WIFI_AP_STATE_ENABLED) { 712 sendApStarted(mWifiManager.getSoftApConfiguration()); 713 } else { 714 Slogf.e(TAG, "Failed to start soft AP"); 715 sendApFailed(ERROR_GENERIC); 716 } 717 } 718 } 719 720 @GuardedBy("mLock") stopTetheredApLocked()721 private void stopTetheredApLocked() { 722 Slogf.d(TAG, "stopTetheredAp"); 723 724 if (mSoftApCallback != null) { 725 mWifiManager.unregisterSoftApCallback(mSoftApCallback); 726 mSoftApCallback = null; 727 if (!mWifiManager.stopSoftAp()) { 728 Slogf.w(TAG, "Failed to request soft AP to stop."); 729 } 730 } 731 } 732 733 @Override resetProjectionAccessPointCredentials()734 public void resetProjectionAccessPointCredentials() { 735 CarServiceUtils.assertProjectionPermission(mContext); 736 737 if (!mStableLocalOnlyHotspotConfig) { 738 Slogf.i(TAG, "Resetting local-only hotspot credentials ignored as credentials do" 739 + " not persist."); 740 return; 741 } 742 743 Slogf.i(TAG, "Clearing local-only hotspot credentials."); 744 getSharedPreferences() 745 .edit() 746 .clear() 747 .apply(); 748 749 synchronized (mLock) { 750 mApConfiguration = null; 751 } 752 } 753 754 @GuardedBy("mLock") startLocalOnlyApLocked()755 private void startLocalOnlyApLocked() { 756 if (mLocalOnlyHotspotReservation != null) { 757 Slogf.i(TAG, "Local-only hotspot is already registered."); 758 sendApStarted(mLocalOnlyHotspotReservation.getSoftApConfiguration()); 759 return; 760 } 761 762 Optional<SoftApConfiguration> optionalApConfig = 763 mStableLocalOnlyHotspotConfig ? restoreApConfiguration() : Optional.empty(); 764 765 if (!optionalApConfig.isPresent()) { 766 Slogf.i(TAG, "Requesting to start local-only hotspot."); 767 mWifiManager.startLocalOnlyHotspot(new ProjectionLocalOnlyHotspotCallback(), mHandler); 768 } else { 769 Slogf.i(TAG, "Requesting to start local-only hotspot with stable configuration."); 770 mWifiManager.startLocalOnlyHotspot( 771 optionalApConfig.get(), 772 new HandlerExecutor(mHandler), 773 new ProjectionLocalOnlyHotspotCallback()); 774 } 775 } 776 getSharedPreferences()777 private SharedPreferences getSharedPreferences() { 778 return mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); 779 } 780 persistApConfiguration(final SoftApConfiguration apConfig)781 private void persistApConfiguration(final SoftApConfiguration apConfig) { 782 synchronized (mLock) { 783 if (apConfig.equals(mApConfiguration)) { 784 return; // Configuration didn't change - nothing to store. 785 } 786 mApConfiguration = apConfig; 787 } 788 789 getSharedPreferences() 790 .edit() 791 .putString(KEY_AP_CONFIG_SSID, apConfig.getSsid()) 792 .putString(KEY_AP_CONFIG_BSSID, macAddressToString(apConfig.getBssid())) 793 .putString(KEY_AP_CONFIG_PASSPHRASE, apConfig.getPassphrase()) 794 .putInt(KEY_AP_CONFIG_SECURITY_TYPE, apConfig.getSecurityType()) 795 .apply(); 796 Slogf.i(TAG, "Access Point configuration saved."); 797 } 798 799 @VisibleForTesting restoreApConfiguration()800 Optional<SoftApConfiguration> restoreApConfiguration() { 801 synchronized (mLock) { 802 if (mApConfiguration != null) { 803 return Optional.of(mApConfiguration); 804 } 805 } 806 807 final SharedPreferences pref = getSharedPreferences(); 808 if (pref == null 809 || !pref.contains(KEY_AP_CONFIG_SSID) 810 || !pref.contains(KEY_AP_CONFIG_BSSID) 811 || !pref.contains(KEY_AP_CONFIG_PASSPHRASE) 812 || !pref.contains(KEY_AP_CONFIG_SECURITY_TYPE)) { 813 Slogf.i(TAG, "AP configuration doesn't exist."); 814 return Optional.empty(); 815 } 816 817 SoftApConfiguration apConfig = new SoftApConfiguration.Builder() 818 .setSsid(pref.getString(KEY_AP_CONFIG_SSID, "")) 819 .setBssid(MacAddress.fromString(pref.getString(KEY_AP_CONFIG_BSSID, ""))) 820 .setPassphrase( 821 pref.getString(KEY_AP_CONFIG_PASSPHRASE, ""), 822 pref.getInt(KEY_AP_CONFIG_SECURITY_TYPE, 0)) 823 .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE) 824 .build(); 825 826 synchronized (mLock) { 827 mApConfiguration = apConfig; 828 } 829 return Optional.of(apConfig); 830 } 831 832 @GuardedBy("mLock") stopLocalOnlyApLocked()833 private void stopLocalOnlyApLocked() { 834 Slogf.i(TAG, "stopLocalOnlyApLocked"); 835 836 if (mLocalOnlyHotspotReservation == null) { 837 Slogf.w(TAG, "Requested to stop local-only hotspot which was already stopped."); 838 return; 839 } 840 841 unregisterLocalOnlyHotspotSoftApCallbackLocked(); 842 843 mLocalOnlyHotspotReservation.close(); 844 mLocalOnlyHotspotReservation = null; 845 } 846 sendApStarted(SoftApConfiguration softApConfiguration)847 private void sendApStarted(SoftApConfiguration softApConfiguration) { 848 if (mFeatureFlags.setBssidOnApStarted() && mApBssid != null) { 849 softApConfiguration = new SoftApConfiguration.Builder(softApConfiguration) 850 .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE) 851 .setBssid(mApBssid) 852 .build(); 853 } 854 855 Message message = Message.obtain(); 856 message.what = CarProjectionManager.PROJECTION_AP_STARTED; 857 message.obj = softApConfiguration; 858 Slogf.i(TAG, "Sending PROJECTION_AP_STARTED, ssid: " 859 + softApConfiguration.getSsid() 860 + ", apBand: " + softApConfiguration.getBand() 861 + ", apChannel: " + softApConfiguration.getChannel() 862 + ", bssid: " + softApConfiguration.getBssid()); 863 sendApStatusMessage(message); 864 } 865 sendApStopped()866 private void sendApStopped() { 867 Message message = Message.obtain(); 868 message.what = CarProjectionManager.PROJECTION_AP_STOPPED; 869 sendApStatusMessage(message); 870 unregisterWirelessClients(); 871 } 872 sendApFailed(int reason)873 private void sendApFailed(int reason) { 874 Message message = Message.obtain(); 875 message.what = CarProjectionManager.PROJECTION_AP_FAILED; 876 message.arg1 = reason; 877 sendApStatusMessage(message); 878 unregisterWirelessClients(); 879 } 880 sendApStatusMessage(Message message)881 private void sendApStatusMessage(Message message) { 882 List<WirelessClient> clients; 883 synchronized (mLock) { 884 clients = new ArrayList<>(mWirelessClients.values()); 885 } 886 for (WirelessClient client : clients) { 887 client.send(message); 888 } 889 } 890 891 @Override init()892 public void init() { 893 mContext.registerReceiver( 894 mBroadcastReceiver, new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION), 895 Context.RECEIVER_NOT_EXPORTED); 896 } 897 handleWifiApStateChange(int currState, int prevState, int errorCode, String ifaceName, int mode)898 private void handleWifiApStateChange(int currState, int prevState, int errorCode, 899 String ifaceName, int mode) { 900 if (currState == WIFI_AP_STATE_ENABLING || currState == WIFI_AP_STATE_ENABLED) { 901 Slogf.d(TAG, 902 "handleWifiApStateChange, curState: " + currState + ", prevState: " + prevState 903 + ", errorCode: " + errorCode + ", ifaceName: " + ifaceName + ", mode: " 904 + mode); 905 906 try { 907 NetworkInterface iface = NetworkInterface.getByName(ifaceName); 908 if (iface == null) { 909 Slogf.e(TAG, "Can't find NetworkInterface: " + ifaceName); 910 } else { 911 setAccessPointBssid(MacAddress.fromBytes(iface.getHardwareAddress())); 912 } 913 } catch (SocketException e) { 914 Slogf.e(TAG, e.toString(), e); 915 } 916 } 917 } 918 919 @VisibleForTesting setAccessPointBssid(MacAddress bssid)920 void setAccessPointBssid(MacAddress bssid) { 921 mApBssid = bssid; 922 } 923 924 @Override release()925 public void release() { 926 synchronized (mLock) { 927 mKeyEventHandlers.clear(); 928 } 929 mContext.unregisterReceiver(mBroadcastReceiver); 930 } 931 932 @Override onBinderDeath( BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> iface)933 public void onBinderDeath( 934 BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> iface) { 935 unregisterKeyEventHandler(iface.binderInterface); 936 } 937 938 @Override 939 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)940 public void dump(IndentingPrintWriter writer) { 941 writer.println("**CarProjectionService**"); 942 synchronized (mLock) { 943 writer.println("Registered key event handlers:"); 944 for (BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> 945 handler : mKeyEventHandlers.getInterfaces()) { 946 ProjectionKeyEventHandler 947 projectionKeyEventHandler = (ProjectionKeyEventHandler) handler; 948 writer.print(" "); 949 writer.println(projectionKeyEventHandler.toString()); 950 } 951 952 writer.println("Local-only hotspot reservation: " + mLocalOnlyHotspotReservation); 953 writer.println("Stable local-only hotspot configuration: " 954 + mStableLocalOnlyHotspotConfig); 955 writer.println("Wireless clients: " + mWirelessClients.size()); 956 writer.println("Current wifi mode: " + mWifiMode); 957 writer.println("SoftApCallback: " + mSoftApCallback); 958 writer.println("Bound to projection app: " + mBound); 959 writer.println("Registered Service: " + mRegisteredService); 960 writer.println("Current projection state: " + mCurrentProjectionState); 961 writer.println("Current projection package: " + mCurrentProjectionPackage); 962 writer.println("Projection status: " + mProjectionReceiverClients); 963 writer.println("Projection status listeners: " 964 + mProjectionStatusListeners.getInterfaces()); 965 writer.println("WifiScanner: " + mWifiScanner); 966 } 967 } 968 969 @Override 970 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)971 public void dumpProto(ProtoOutputStream proto) {} 972 973 @Override onKeyEvent(@arProjectionManager.KeyEventNum int keyEvent)974 public void onKeyEvent(@CarProjectionManager.KeyEventNum int keyEvent) { 975 Slogf.d(TAG, "Dispatching key event: " + keyEvent); 976 synchronized (mLock) { 977 for (BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> 978 eventHandlerInterface : mKeyEventHandlers.getInterfaces()) { 979 ProjectionKeyEventHandler eventHandler = 980 (ProjectionKeyEventHandler) eventHandlerInterface; 981 982 if (eventHandler.canHandleEvent(keyEvent)) { 983 try { 984 // oneway 985 eventHandler.binderInterface.onKeyEvent(keyEvent); 986 } catch (RemoteException e) { 987 Slogf.e(TAG, "Cannot dispatch event to client", e); 988 } 989 } 990 } 991 } 992 } 993 994 @GuardedBy("mLock") updateInputServiceHandlerLocked()995 private void updateInputServiceHandlerLocked() { 996 BitSet newEvents = computeHandledEventsLocked(); 997 998 if (!newEvents.isEmpty()) { 999 mCarInputService.setProjectionKeyEventHandler(this, newEvents); 1000 } else { 1001 mCarInputService.setProjectionKeyEventHandler(null, null); 1002 } 1003 } 1004 1005 @GuardedBy("mLock") computeHandledEventsLocked()1006 private BitSet computeHandledEventsLocked() { 1007 BitSet rv = new BitSet(); 1008 for (BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> 1009 handlerInterface : mKeyEventHandlers.getInterfaces()) { 1010 rv.or(((ProjectionKeyEventHandler) handlerInterface).mHandledEvents); 1011 } 1012 return rv; 1013 } 1014 setUiMode(Integer uiMode)1015 void setUiMode(Integer uiMode) { 1016 synchronized (mLock) { 1017 mProjectionOptions = createProjectionOptionsBuilder() 1018 .setUiMode(uiMode) 1019 .build(); 1020 } 1021 } 1022 setAccessPointTethering(boolean tetherEnabled)1023 void setAccessPointTethering(boolean tetherEnabled) { 1024 synchronized (mLock) { 1025 mWifiMode = tetherEnabled ? WIFI_MODE_TETHERED : WIFI_MODE_LOCALONLY; 1026 } 1027 } 1028 setStableLocalOnlyHotspotConfig(boolean stableConfig)1029 void setStableLocalOnlyHotspotConfig(boolean stableConfig) { 1030 synchronized (mLock) { 1031 mStableLocalOnlyHotspotConfig = stableConfig; 1032 } 1033 } 1034 1035 private static class ProjectionKeyEventHandlerContainer 1036 extends BinderInterfaceContainer<ICarProjectionKeyEventHandler> { ProjectionKeyEventHandlerContainer(CarProjectionService service)1037 ProjectionKeyEventHandlerContainer(CarProjectionService service) { 1038 super(service); 1039 } 1040 get(ICarProjectionKeyEventHandler projectionCallback)1041 ProjectionKeyEventHandler get(ICarProjectionKeyEventHandler projectionCallback) { 1042 return (ProjectionKeyEventHandler) getBinderInterface(projectionCallback); 1043 } 1044 } 1045 1046 private static class ProjectionKeyEventHandler extends 1047 BinderInterfaceContainer.BinderInterface<ICarProjectionKeyEventHandler> { 1048 private BitSet mHandledEvents; 1049 ProjectionKeyEventHandler( ProjectionKeyEventHandlerContainer holder, ICarProjectionKeyEventHandler binder, BitSet handledEvents)1050 private ProjectionKeyEventHandler( 1051 ProjectionKeyEventHandlerContainer holder, 1052 ICarProjectionKeyEventHandler binder, 1053 BitSet handledEvents) { 1054 super(holder, binder); 1055 mHandledEvents = handledEvents; 1056 } 1057 canHandleEvent(int event)1058 private boolean canHandleEvent(int event) { 1059 return mHandledEvents.get(event); 1060 } 1061 setHandledEvents(BitSet handledEvents)1062 private void setHandledEvents(BitSet handledEvents) { 1063 mHandledEvents = handledEvents; 1064 } 1065 1066 @Override toString()1067 public String toString() { 1068 return "ProjectionKeyEventHandler{events=" + mHandledEvents + "}"; 1069 } 1070 } 1071 registerWirelessClient(WirelessClient client)1072 private void registerWirelessClient(WirelessClient client) throws RemoteException { 1073 synchronized (mLock) { 1074 if (unregisterWirelessClientLocked(client.token)) { 1075 Slogf.i(TAG, "Client was already registered, override it."); 1076 } 1077 mWirelessClients.put(client.token, client); 1078 } 1079 client.token.linkToDeath(new WirelessClientDeathRecipient(this, client), 0); 1080 } 1081 unregisterWirelessClients()1082 private void unregisterWirelessClients() { 1083 synchronized (mLock) { 1084 for (WirelessClient client: mWirelessClients.values()) { 1085 client.token.unlinkToDeath(client.deathRecipient, 0); 1086 } 1087 mWirelessClients.clear(); 1088 } 1089 } 1090 1091 @GuardedBy("mLock") unregisterWirelessClientLocked(IBinder token)1092 private boolean unregisterWirelessClientLocked(IBinder token) { 1093 WirelessClient client = mWirelessClients.remove(token); 1094 if (client != null) { 1095 token.unlinkToDeath(client.deathRecipient, 0); 1096 } 1097 1098 return client != null; 1099 } 1100 ensureApConfiguration()1101 private void ensureApConfiguration() { 1102 // Always prefer 5GHz configuration whenever it is available. 1103 SoftApConfiguration apConfig = mWifiManager.getSoftApConfiguration(); 1104 if (apConfig == null) { 1105 throw new NullPointerException("getSoftApConfiguration returned null"); 1106 } 1107 if (!mWifiManager.is5GHzBandSupported()) return; // Not an error, but nothing to do. 1108 SparseIntArray channels = apConfig.getChannels(); 1109 1110 // 5GHz is already enabled. 1111 if (channels.get(SoftApConfiguration.BAND_5GHZ, -1) != -1) return; 1112 1113 if (mWifiManager.isBridgedApConcurrencySupported()) { 1114 // Enable dual band if supported. 1115 mWifiManager.setSoftApConfiguration(new SoftApConfiguration.Builder(apConfig) 1116 .setBands(new int[] {SoftApConfiguration.BAND_2GHZ, 1117 SoftApConfiguration.BAND_5GHZ}).build()); 1118 } else { 1119 // Only enable 5GHz if dual band AP isn't supported. 1120 mWifiManager.setSoftApConfiguration(new SoftApConfiguration.Builder(apConfig) 1121 .setBands(new int[] {SoftApConfiguration.BAND_5GHZ}).build()); 1122 } 1123 } 1124 1125 /** 1126 * Sets fake feature flag for unit testing. 1127 */ 1128 @VisibleForTesting setFeatureFlags(FeatureFlags fakeFeatureFlags)1129 public void setFeatureFlags(FeatureFlags fakeFeatureFlags) { 1130 mFeatureFlags = fakeFeatureFlags; 1131 } 1132 1133 @GuardedBy("mLock") unregisterLocalOnlyHotspotSoftApCallbackLocked()1134 private void unregisterLocalOnlyHotspotSoftApCallbackLocked() { 1135 if (mFeatureFlags.registerLocalOnlyHotspotSoftApCallback() 1136 && mLocalOnlySoftApCallback != null) { 1137 mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(mLocalOnlySoftApCallback); 1138 mLocalOnlySoftApCallback = null; 1139 } 1140 } 1141 1142 private class ProjectionSoftApCallback implements WifiManager.SoftApCallback { 1143 private boolean mCurrentStateCall = true; 1144 1145 @Override onStateChanged(int state, int softApFailureReason)1146 public void onStateChanged(int state, int softApFailureReason) { 1147 Slogf.i(TAG, "ProjectionSoftApCallback, onStateChanged, state: " + state 1148 + ", failed reason: " + softApFailureReason 1149 + ", currentStateCall: " + mCurrentStateCall); 1150 if (mCurrentStateCall) { 1151 // When callback gets registered framework always sends the current state as the 1152 // first call. We should ignore current state call to be in par with 1153 // local-only behavior. 1154 mCurrentStateCall = false; 1155 return; 1156 } 1157 1158 switch (state) { 1159 case WifiManager.WIFI_AP_STATE_ENABLED: { 1160 sendApStarted(mWifiManager.getSoftApConfiguration()); 1161 break; 1162 } 1163 case WifiManager.WIFI_AP_STATE_DISABLED: { 1164 sendApStopped(); 1165 break; 1166 } 1167 case WifiManager.WIFI_AP_STATE_FAILED: { 1168 Slogf.w(TAG, "WIFI_AP_STATE_FAILED, reason: " + softApFailureReason); 1169 int reason; 1170 switch (softApFailureReason) { 1171 case WifiManager.SAP_START_FAILURE_NO_CHANNEL: 1172 reason = ProjectionAccessPointCallback.ERROR_NO_CHANNEL; 1173 break; 1174 default: 1175 reason = ProjectionAccessPointCallback.ERROR_GENERIC; 1176 } 1177 sendApFailed(reason); 1178 break; 1179 } 1180 default: 1181 break; 1182 } 1183 } 1184 1185 @Override onConnectedClientsChanged(List<WifiClient> clients)1186 public void onConnectedClientsChanged(List<WifiClient> clients) { 1187 if (DBG) { 1188 Slogf.d(TAG, "ProjectionSoftApCallback, onConnectedClientsChanged with " 1189 + clients.size() + " clients"); 1190 } 1191 } 1192 } 1193 1194 private class LocalOnlyProjectionSoftApCallback implements WifiManager.SoftApCallback { 1195 @Override onStateChanged(int state, int softApFailureReason)1196 public void onStateChanged(int state, int softApFailureReason) { 1197 Slogf.i(TAG, "LocalOnlyProjectionSoftApCallback, onStateChanged, state: " + state 1198 + ", failed reason: " + softApFailureReason); 1199 1200 if (state == WifiManager.WIFI_AP_STATE_DISABLED 1201 || state == WifiManager.WIFI_AP_STATE_FAILED) { 1202 Slogf.i(TAG, "LOHS AP was disabled, trying to stop."); 1203 stopAccessPoint(); 1204 } 1205 } 1206 } 1207 1208 private static class WirelessClient { 1209 public final Messenger messenger; 1210 public final IBinder token; 1211 public @Nullable DeathRecipient deathRecipient; 1212 WirelessClient(Messenger messenger, IBinder token)1213 private WirelessClient(Messenger messenger, IBinder token) { 1214 this.messenger = messenger; 1215 this.token = token; 1216 } 1217 of(Messenger messenger, IBinder token)1218 private static WirelessClient of(Messenger messenger, IBinder token) { 1219 return new WirelessClient(messenger, token); 1220 } 1221 send(Message message)1222 void send(Message message) { 1223 try { 1224 Slogf.d(TAG, "Sending message " + message.what + " to " + this); 1225 messenger.send(message); 1226 } catch (RemoteException e) { 1227 Slogf.e(TAG, "Failed to send message", e); 1228 } 1229 } 1230 1231 @Override toString()1232 public String toString() { 1233 return getClass().getSimpleName() 1234 + "{token= " + token 1235 + ", deathRecipient=" + deathRecipient + "}"; 1236 } 1237 } 1238 1239 private static class WirelessClientDeathRecipient implements DeathRecipient { 1240 final WeakReference<CarProjectionService> mServiceRef; 1241 final WirelessClient mClient; 1242 WirelessClientDeathRecipient(CarProjectionService service, WirelessClient client)1243 WirelessClientDeathRecipient(CarProjectionService service, WirelessClient client) { 1244 mServiceRef = new WeakReference<>(service); 1245 mClient = client; 1246 mClient.deathRecipient = this; 1247 } 1248 1249 @Override binderDied()1250 public void binderDied() { 1251 Slogf.w(TAG, "Wireless client " + mClient + " died."); 1252 CarProjectionService service = mServiceRef.get(); 1253 if (service == null) return; 1254 1255 synchronized (service.mLock) { 1256 service.unregisterWirelessClientLocked(mClient.token); 1257 } 1258 } 1259 } 1260 1261 private static class ProjectionReceiverClient { 1262 private final DeathRecipient mDeathRecipient; 1263 private ProjectionStatus mProjectionStatus; 1264 ProjectionReceiverClient(DeathRecipient deathRecipient)1265 ProjectionReceiverClient(DeathRecipient deathRecipient) { 1266 mDeathRecipient = deathRecipient; 1267 } 1268 1269 @Override toString()1270 public String toString() { 1271 return "ProjectionReceiverClient{" 1272 + "mDeathRecipient=" + mDeathRecipient 1273 + ", mProjectionStatus=" + mProjectionStatus 1274 + '}'; 1275 } 1276 } 1277 macAddressToString(MacAddress macAddress)1278 private static String macAddressToString(MacAddress macAddress) { 1279 byte[] addr = macAddress.toByteArray(); 1280 return String.format("%02x:%02x:%02x:%02x:%02x:%02x", 1281 addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); 1282 } 1283 1284 private class ProjectionLocalOnlyHotspotCallback extends LocalOnlyHotspotCallback { 1285 @Override onStarted(LocalOnlyHotspotReservation reservation)1286 public void onStarted(LocalOnlyHotspotReservation reservation) { 1287 Slogf.d(TAG, "Local-only hotspot started"); 1288 boolean shouldPersistSoftApConfig; 1289 synchronized (mLock) { 1290 if (mFeatureFlags.registerLocalOnlyHotspotSoftApCallback() 1291 && mLocalOnlySoftApCallback == null) { 1292 mLocalOnlySoftApCallback = new LocalOnlyProjectionSoftApCallback(); 1293 mWifiManager.registerLocalOnlyHotspotSoftApCallback( 1294 new HandlerExecutor(mHandler), mLocalOnlySoftApCallback); 1295 } 1296 1297 mLocalOnlyHotspotReservation = reservation; 1298 shouldPersistSoftApConfig = mStableLocalOnlyHotspotConfig; 1299 } 1300 SoftApConfiguration.Builder softApConfigurationBuilder = 1301 new SoftApConfiguration.Builder(reservation.getSoftApConfiguration()) 1302 .setBssid(mApBssid); 1303 1304 if (mApBssid != null) { 1305 softApConfigurationBuilder 1306 .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE); 1307 } 1308 SoftApConfiguration softApConfiguration = softApConfigurationBuilder.build(); 1309 1310 if (shouldPersistSoftApConfig) { 1311 persistApConfiguration(softApConfiguration); 1312 } 1313 sendApStarted(softApConfiguration); 1314 } 1315 1316 @Override onStopped()1317 public void onStopped() { 1318 Slogf.i(TAG, "Local-only hotspot stopped."); 1319 synchronized (mLock) { 1320 unregisterLocalOnlyHotspotSoftApCallbackLocked(); 1321 1322 if (mLocalOnlyHotspotReservation != null) { 1323 // We must explicitly released old reservation object, otherwise it may 1324 // unexpectedly stop LOHS later because it overrode finalize() method. 1325 mLocalOnlyHotspotReservation.close(); 1326 } 1327 mLocalOnlyHotspotReservation = null; 1328 } 1329 sendApStopped(); 1330 } 1331 1332 @Override onFailed(int localonlyHostspotFailureReason)1333 public void onFailed(int localonlyHostspotFailureReason) { 1334 Slogf.w(TAG, "Local-only hotspot failed, reason: " 1335 + localonlyHostspotFailureReason); 1336 synchronized (mLock) { 1337 unregisterLocalOnlyHotspotSoftApCallbackLocked(); 1338 1339 mLocalOnlyHotspotReservation = null; 1340 } 1341 int reason; 1342 switch (localonlyHostspotFailureReason) { 1343 case LocalOnlyHotspotCallback.ERROR_NO_CHANNEL: 1344 reason = ProjectionAccessPointCallback.ERROR_NO_CHANNEL; 1345 break; 1346 case LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED: 1347 reason = ProjectionAccessPointCallback.ERROR_TETHERING_DISALLOWED; 1348 break; 1349 case LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE: 1350 reason = ProjectionAccessPointCallback.ERROR_INCOMPATIBLE_MODE; 1351 break; 1352 default: 1353 reason = ERROR_GENERIC; 1354 1355 } 1356 sendApFailed(reason); 1357 } 1358 } 1359 } 1360