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