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