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