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