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